Quadratic Bézier Curve: Calculate Points - math

I'd like to calculate a point on a quadratic curve. To use it with the canvas element of HTML5.
When I use the quadraticCurveTo() function in JavaScript, I have a source point, a target point and a control point.
How can I calculate a point on the created quadratic curve at let's say t=0.5 with "only" knowing this three points?

Use the quadratic Bézier formula, found, for instance, on the Wikipedia page for Bézier Curves:
In pseudo-code, that's
t = 0.5; // given example value
x = (1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x;
y = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y;
p[0] is the start point, p[1] is the control point, and p[2] is the end point. t is the parameter, which goes from 0 to 1.

In case somebody needs the cubic form:
//B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3
x = (1-t)*(1-t)*(1-t)*p0x + 3*(1-t)*(1-t)*t*p1x + 3*(1-t)*t*t*p2x + t*t*t*p3x;
y = (1-t)*(1-t)*(1-t)*p0y + 3*(1-t)*(1-t)*t*p1y + 3*(1-t)*t*t*p2y + t*t*t*p3y;

I created this demo :
// x = a * (1-t)³ + b * 3 * (1-t)²t + c * 3 * (1-t)t² + d * t³
//------------------------------------------------------------
// x = a - 3at + 3at² - at³
// + 3bt - 6bt² + 3bt³
// + 3ct² - 3ct³
// + dt³
//--------------------------------
// x = - at³ + 3bt³ - 3ct³ + dt³
// + 3at² - 6bt² + 3ct²
// - 3at + 3bt
// + a
//--------------------------------
// 0 = t³ (-a+3b-3c+d) + => A
// t² (3a-6b+3c) + => B
// t (-3a+3b) + => c
// a - x => D
//--------------------------------
var A = d - 3*c + 3*b - a,
B = 3*c - 6*b + 3*a,
C = 3*b - 3*a,
D = a-x;
// So we need to solve At³ + Bt² + Ct + D = 0
Full example here
may help someone.

I edited talkhabis answer (cubic curve) so the curve is displayed with the right coordinates. (Couldn't comment)
The Y-coordinates needed to be changed (-p[].y+150). (A new variable for that might be a nicer and more efficient solution, but you get the idea)
// Apply points to SVG and create the curve and controllers :
var path = document.getElementById('path'),
ctrl1 = document.getElementById('ctrl1'),
ctrl2 = document.getElementById('ctrl2'),
D = 'M ' + p0.x + ' ' + (-p0.y+150) +
'C ' + c0.x + ' ' + (-c0.y+150) +', ' + c1.x + ' ' + (-c1.y+150) + ', ' + p1.x + ' ' + (-p1.y+150);
path.setAttribute('d',D);
ctrl1.setAttribute('d','M'+p0.x+','+(-p0.y+150)+'L'+c0.x+','+(-c0.y+150));
ctrl2.setAttribute('d','M'+p1.x+','+(-p1.y+150)+'L'+c1.x+','+(-c1.y+150));
// Lets test the "Bezier Function"
var t = 0, point = document.getElementById('point');
setInterval(function(){
var p = Bezier(p0,c0,c1,p1,t);
point.setAttribute('cx',p.x);
point.setAttribute('cy',-p.y+150);
t += 0.01;
if(t>=1) t=0;
},50);
// OK ... Now tring to get "y" on cruve based on mouse "x" :
var svg = document.getElementById('svg'),
point2 = document.getElementById('point2');
svg.onmousemove = function(e){
var x = (e.pageX - 50)/2,
y = (e.pageY - 50)/2;
// "-50" because of "50px margin" on the left side
// and "/2" because the svg width is 300 units and 600 px => 300 = 600/2
// Get the x,y by mouse x
var p = YBX(p0,c0,c1,p1,x);
point2.setAttribute('cx',p.x);
point2.setAttribute('cy',-p.y+150);
}
http://jsfiddle.net/u214gco8/1/
I also created some C-Code to test the results for the cubic curve. Just enter the X and Y coordinates in the main function.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void bezierCurve(int x[] , int y[])
{
double xu = 0.0 , yu = 0.0 , u = 0.0 ;
int i = 0 ;
for(u = 0.0 ; u <= 1.0 ; u += 0.05)
{
xu = pow(1-u,3)*x[0]+3*u*pow(1-u,2)*x[1]+3*pow(u,2)*(1-u)*x[2]
+pow(u,3)*x[3];
yu = pow(1-u,3)*y[0]+3*u*pow(1-u,2)*y[1]+3*pow(u,2)*(1-u)*y[2]
+pow(u,3)*y[3];
printf("X: %i Y: %i \n" , (int)xu , (int)yu) ;
}
}
int main(void) {
int x[] = {0,75,50,300};
int y[] = {0,2,140,100};
bezierCurve(x,y);
return 0;
}
https://ideone.com/glLXcB

Just a note: If you are using the usual formulas presented here then don't expect t = 0.5 to return the point at half of the curve's length.. In most cases it won't.
More on this here under "§23 — Tracing a curve at fixed distance intervals" and here.

Related

Given a set of lines in 2d space, how to truncate them to be within bounds?

Background:
Heya! I'm trying to generate a circuit board which has a subset of San Francisco printed on it. Most of the pieces of this are done, and I'm generating images that look like this:
The problem is that I am rendering lines which extend outside my hardcoded cutoff boundary (I am rendering lines which one side is in and one side is out of bounds).
Question:
Given a set of lines like this:
# x1,y1, x2,y2
10,10,40,40
80,80,120,120
How can I modify the co-ordinates of each line such that it 'cuts off' at a specific bound?
In the case above, the second line (which in original form) extends to (120,120), should only extend to (100,100) assuming bounds of 100,100.
Thoughts
Based on what I remember from high-school math, I should plug something into the formula y=mx+b yeah? Even then, how would I deal with an infinite gradient or the like?
Thanks for any and all help :D Puesdocode/python/Go preferred, but explanations just as graciously recieved.
<3
Tom
Your best friend is the Cohen–Sutherland line clipping algorithm.
https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
Sat down and worked it out. My rudimentary approach was to:
Compute the slope of the line & the y-intercept
Check both points on all four sides to see if they exceed bounds, and if they do, recompute the necessary co-ordinate by plugging the bound into the formula y=mx+b.
Here is my Go code:
func boundLine(line *kcgen.Line) {
if line.Start.X == line.End.X {
panic("infinite slope not yet supported")
}
slope := (line.End.Y - line.Start.Y) / (line.End.X - line.Start.X)
b := line.End.Y - (slope * line.End.X) //y = mx + b which is equivalent to b = y - mx
if line.Start.X < (-*width/2) {
line.Start.Y = (slope * (-*width/2)) + b
line.Start.X = -*width/2
}
if line.End.X < (-*width/2) {
line.End.Y = (slope * (-*width/2)) + b
line.End.X = -*width/2
}
if line.Start.X > (*width/2) {
line.Start.Y = (slope * (*width/2)) + b
line.Start.X = *width/2
}
if line.End.X > (*width/2) {
line.End.Y = (slope * (*width/2)) + b
line.End.X = *width/2
}
if line.Start.Y < (-*height/2) {
line.Start.Y = -*height/2
line.Start.X = ((-*height/2) - b) / slope //y = mx + b equiv. (y-b)/m = x
}
if line.End.Y < (-*height/2) {
line.End.Y = -*height/2
line.End.X = ((-*height/2) - b) / slope //y = mx + b equiv. (y-b)/m = x
}
if line.Start.Y > (*height/2) {
line.Start.Y = *height/2
line.Start.X = ((*height/2) - b) / slope //y = mx + b equiv. (y-b)/m = x
}
if line.End.Y > (*height/2) {
line.End.Y = *height/2
line.End.X = ((*height/2) - b) / slope //y = mx + b equiv. (y-b)/m = x
}
}

Plotting a graph for 2 elements in scilab within a For loop

I am currently writing a program to show the relation between the Relaxation Factor and number of iterations it takes to achieve a solution using the Successive OverRelaxation Method
This is the concerned For loop:
for (w = 0:0.05:2)
T = -inv(D + w*L)*(w*U + (w -1) * D);
C = inv(D + w*L) * w *B ;
X = zeros(B);
for(i = 1:1:MaxIter)
X = T * X + C;
err = A * X - B;
if (abs(err) < abs(tol))
break
end
end
disp("Relaxation Factor = " + string(w) +" No. of iterations = " + string(i));
I wish to draw a plot showing the relation between Relaxation factor and no. of iterations(between w and i). How should I proceed?
Just proceed as follow
I=zeros(W)
for k = 1:size(W,"*")
w=W(k)
T = -inv(D + w*L)*(w*U + (w -1) * D);
C = inv(D + w*L) * w *B ;
X = zeros(B);
for(i = 1:1:MaxIter)
X = T * X + C;
err = A * X - B;
if (abs(err) < abs(tol))
I(k)=i
break
end
end
end
plot(W,I)

3d line-intersection code not working properly

I created this piece of code to get the intersection of two 3d line-segments.
Unfortunately the result of this code is inaccurate, the intersection-point is not always on both lines.
I am confused and unsure what I'm doing wrong.
Here is my code:
--dir = direction
--p1,p2 = represents the line
function GetIntersection(dirStart, dirEnd, p1, p2)
local s1_x, s1_y, s2_x, s2_y = dirEnd.x - dirStart.x, dirEnd.z - dirStart.z, p2.x - p1.x, p2.z - p1.z
local div = (-s2_x * s1_y) + (s1_x * s2_y)
if div == 0 then return nil end
local s = (-s1_y * (dirStart.x - p1.x) + s1_x * (dirStart.z - p1.z)) / div
local t = ( s2_x * (dirStart.z - p1.z) - s2_y * (dirStart.x - p1.x)) / div
if (s >= 0 and s <= 1 and t >= 0 and t <= 1) and (Vector(dirStart.x + (t * s1_x), 0, dirStart.z + (t * s1_y)) or nil) then
local v = Vector(dirStart.x + (t * s1_x),0,dirStart.z + (t * s1_y))
return v
end
end
This is example of Delphi code to find a distance between two skew lines in 3D. For your purposes it is necessary to check that result if small enough value (intersection does exist), check that s and t parameters are in range 0..1, then
calculate point using parameter s
Math of this approach is described in 'the shortest line...' section of Paul Bourke page
VecDiff if vector difference function, Dot id scalar product function
function LineLineDistance(const L0, L1: TLine3D; var s, t: Double): Double;
var
u: TPoint3D;
a, b, c, d, e, det, invdet:Double;
begin
u := VecDiff(L1.Base, L0.Base);
a := Dot(L0.Direction, L0.Direction);
b := Dot(L0.Direction, L1.Direction);
c := Dot(L1.Direction, L1.Direction);
d := Dot(L0.Direction, u);
e := Dot(L1.Direction, u);
det := a * c - b * b;
if det < eps then
Result := -1
else begin
invdet := 1 / det;
s := invdet * (b * e - c * d);
t := invdet * (a * e - b * d);
Result := Distance(PointAtParam(L0, s), PointAtParam(L1, t));
end;
end;
As far as I can tell your code is good. I've implemented this in javascript at https://jsfiddle.net/SalixAlba/kkrc9kcf/
and it seems to work for all the cases I can think of.
The only changes I've done is to change things to work in javascript rather than lua. The final condition was commented out
function GetIntersection(dirStart, dirEnd, p1, p2) {
var s1_x = dirEnd.x - dirStart.x;
var s1_y = dirEnd.z - dirStart.z;
var s2_x = p2.x - p1.x;
var s2_y = p2.z - p1.z;
var div = (-s2_x * s1_y) + (s1_x * s2_y);
if (div == 0)
return new Vector(0,0);
var s = (-s1_y * (dirStart.x - p1.x) + s1_x * (dirStart.z - p1.z)) / div;
var t = ( s2_x * (dirStart.z - p1.z) - s2_y * (dirStart.x - p1.x)) / div;
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
//and (Vector(dirStart.x + (t * s1_x), 0, dirStart.z + (t * s1_y)) or nil) then
var v = new Vector(
dirStart.x + (t * s1_x),
dirStart.z + (t * s1_y));
return v;
}
return new Vector(0,0);
}
Mathmatically it makes sense. If A,B and C,D are your two lines. Let s1 = B-A, s2 = C-D. A point of the line AB is given by A + t s1 and a point on the line CD is given by C + s s2. For an intersection we require
A + t s1 = C + s s2
or
(A-C) + t s1 = s s2
You two formula for s, t are found by taking the 2D cross product with each of the vectors s1 and s2
(A-C)^s1 + t s1^s1 = s s2^s1
(A-C)^s2 + t s1^s2 = s s2^s2
recalling s1^s1=s2^s2=0 and s2^s1= - s1^s2 we get
(A-C)^s1 = s s2^s1
(A-C)^s2 + t s1^s2 = 0
which can be solved to get s and t. This matches your equations.

Circle collision response

I'm working on an Android game and I need to bounce 2 circles of each other (like 2 pool balls bouncing off each other). The collision is an elastic collision, and I need to calculate only 1 circles (called a Particle in my code) new velocity after the collision (the other circle, called a Barrier in my code will remain stationary and will not move because of a collision).
I am using a formula that I found on Wikipedia (http://en.wikipedia.org/wiki/Elastic_collision), but my end result for the new velocity of the particle after the collision is exactly the same as the velocity before the collision?
This is def wrong but I cant see where I am going wrong. Can anyone spot where I am going wrong?
I have just used a Java program to simulate my velocities and locations for the 2 circles as I dont wanna try it in my main Android game at the moment for fear of "breaking something"
Here is what I have so far (like I mentioned, this is just a simulation in NetBeans for the moment and I will use a menthod in my Android game to keep things a bit tidier):
double randomNextDouble = (new Random()).nextDouble();
System.out.println("Random.nextDouble: " + randomNextDouble);
double mathPI = Math.PI * 2;
System.out.println("Math PI: " + mathPI);
// get a random direction for the Particle to move towards
double direction = (new Random()).nextDouble() * Math.PI * 2;
System.out.println("Direction: " + direction);
// Then set the Particle's velocity - Increase to make Particles move faster
int velocity = 10;
System.out.println("Velocity: " + velocity);
// Then calculate the xv and the yv
// Velocity value on the x and y axis
double xv = (velocity * Math.cos(direction));
double yv = (velocity * Math.sin(direction));
System.out.println("\nXV: " + xv + "\nYV: " + yv);
// Genareting a random number for the Particle and Barrier's positions on screen
double Xmin = 0;
double Xmax = 300;
double Ymin = 0;
double Ymax = 300;
double randomNumber1 = Xmin + (int)(Math.random() * ((Xmax - Xmin) + 1));
double randomNumber2 = Ymin + (int)(Math.random() * ((Ymax - Ymin) + 1));
double randomNumber3 = Xmin + (int)(Math.random() * ((Xmax - Xmin) + 1));
double randomNumber4 = Ymin + (int)(Math.random() * ((Ymax - Ymin) + 1));
// Setting the Particle and Barrier's radius
double particleRadius = 8;
double barrierRadius = 16;
// Setting up the Particle and Barrier's mass
double particleMass = 100;
double barrierMass = 200;
// Assigning a random number to the Particle to simulate its position on screen
double particleX = randomNumber1;
double particleY = randomNumber2;
System.out.println("\nParticle X: " + particleX + " Particle Y: " + particleY);
// Assigning a random number to the Barrier to simulate its position on screen
double barrierX = randomNumber3;
double barrierY = randomNumber4;
System.out.println("Barrier X: " + barrierX + " Barrier Y: " + barrierY);
double distanceXToBarrier = barrierX - particleX;
System.out.println("\nBarrier X - Particle X: " + distanceXToBarrier);
double distanceYToBarrier = barrierY - particleY;
System.out.println("Barrier Y - Particle Y: " + distanceYToBarrier);
// Get the distance between the Particle and the Barrier
// Used for collision detection
double distance = Math.sqrt((distanceXToBarrier * distanceXToBarrier) + (distanceYToBarrier * distanceYToBarrier));
System.out.println("\nDistance: " + distance);
// Check to see if the Particle and Barrier has collided
if (distance <= particleRadius + barrierRadius)
{
System.out.println("Distance is less than 2 Radii");
}
else
System.out.println("Distance is NOT less than 2 Radii");
// Velx = (v1.u) * u + (v1 - (v1.u) * u)
// Vely = (v1.u) * u + (v1 - (v1.u) * u)
// Where v1 = xv and yv respectively
// Break it into 2 equations
// (v1.u) * u AND
// (v1 - (v1.u) * u)
//
// u = normalised Vector
// To normalize you just devide the x, y, z coords by the length of the vector.
// This then gives you the Unit Vector.
//
//Normalize the vector
double particleXNormalized = particleX * (1.0 / distance);
double particleYNormalized = particleY * (1.0 / distance);
System.out.println("\nParticle X Normalised: " + particleXNormalized +
"\nParticle Y Normalised: " + particleYNormalized);
// Calculating the first part of the eqaution
// (v1.u)
double v1DotUForX = xv * particleXNormalized;
double v1DotUForY = yv * particleYNormalized;
System.out.println("\nv1.u for X: " + v1DotUForX +
"\nv1.u for Y: " + v1DotUForY);
// The first part of the equation
// (v1.u) * u
double part1X = v1DotUForX * particleXNormalized;
double part1Y = v1DotUForY * particleYNormalized;
System.out.println("\nPart 1 for X: " + part1X +
"\nPart 1 for Y: " + part1Y);
// The second part of the equation
// (v1 - (v1.u) * u)
double part2X = (xv - (v1DotUForX) * particleXNormalized);
double part2Y = (yv - (v1DotUForY) * particleYNormalized);
System.out.println("\nPart 2 for X: " + part2X +
"\nPart 2 for Y: " + part2Y);
// Solving for:
// (((mass 1 - mass2) / (mass1 + mass2) * (v1.u) * u + ((2mass2) / (mass1 + mass2) * ((v1.u) * u))) +
// (v1 - (v1.u) * u))
double newXV = ((((particleMass - barrierMass) / (particleMass + barrierMass)) * part1X) + (((2 * barrierMass) / (particleMass + barrierMass)) * part1X) + part2X);
double newYV = ((((particleMass - barrierMass) / (particleMass + barrierMass)) * part1Y) + (((2 * barrierMass) / (particleMass + barrierMass)) * part1Y) + part2Y);
System.out.println("\nNew XV: " + newXV + "\nNew YV: " + newYV);
Looking at your algorithm, you appear to have made errors in the implementation. Why are you normalizing the coordinates of the particle? Shouldn't you be doing that to the velocity? In the usual equations, u is velocity, not position.
And why do you give the particle a random velocity (xv, yv) that has nothing to do with the two random coordinates you set up for the particle and barrier? (Surely the velocity should be some multiple of (barrier - particle) vector?)

Mapping A Sphere To A Cube

There is a special way of mapping a cube to a sphere described here:
http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html
It is not your basic "normalize the point and you're done" approach and gives a much more evenly spaced mapping.
I've tried to do the inverse of the mapping going from sphere coords to cube coords and have been unable to come up the working equations. It's a rather complex system of equations with lots of square roots.
Any math geniuses want to take a crack at it?
Here's the equations in c++ code:
sx = x * sqrtf(1.0f - y * y * 0.5f - z * z * 0.5f + y * y * z * z / 3.0f);
sy = y * sqrtf(1.0f - z * z * 0.5f - x * x * 0.5f + z * z * x * x / 3.0f);
sz = z * sqrtf(1.0f - x * x * 0.5f - y * y * 0.5f + x * x * y * y / 3.0f);
sx,sy,sz are the sphere coords and x,y,z are the cube coords.
I want to give gmatt credit for this because he's done a lot of the work. The only difference in our answers is the equation for x.
To do the inverse mapping from sphere to cube first determine the cube face the sphere point projects to. This step is simple - just find the component of the sphere vector with the greatest length like so:
// map the given unit sphere position to a unit cube position
void cubizePoint(Vector3& position) {
double x,y,z;
x = position.x;
y = position.y;
z = position.z;
double fx, fy, fz;
fx = fabsf(x);
fy = fabsf(y);
fz = fabsf(z);
if (fy >= fx && fy >= fz) {
if (y > 0) {
// top face
position.y = 1.0;
}
else {
// bottom face
position.y = -1.0;
}
}
else if (fx >= fy && fx >= fz) {
if (x > 0) {
// right face
position.x = 1.0;
}
else {
// left face
position.x = -1.0;
}
}
else {
if (z > 0) {
// front face
position.z = 1.0;
}
else {
// back face
position.z = -1.0;
}
}
}
For each face - take the remaining cube vector components denoted as s and t and solve for them using these equations, which are based on the remaining sphere vector components denoted as a and b:
s = sqrt(-sqrt((2 a^2-2 b^2-3)^2-24 a^2)+2 a^2-2 b^2+3)/sqrt(2)
t = sqrt(-sqrt((2 a^2-2 b^2-3)^2-24 a^2)-2 a^2+2 b^2+3)/sqrt(2)
You should see that the inner square root is used in both equations so only do that part once.
Here's the final function with the equations thrown in and checks for 0.0 and -0.0 and the code to properly set the sign of the cube component - it should be equal to the sign of the sphere component.
void cubizePoint2(Vector3& position)
{
double x,y,z;
x = position.x;
y = position.y;
z = position.z;
double fx, fy, fz;
fx = fabsf(x);
fy = fabsf(y);
fz = fabsf(z);
const double inverseSqrt2 = 0.70710676908493042;
if (fy >= fx && fy >= fz) {
double a2 = x * x * 2.0;
double b2 = z * z * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);
if(x == 0.0 || x == -0.0) {
position.x = 0.0;
}
else {
position.x = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}
if(z == 0.0 || z == -0.0) {
position.z = 0.0;
}
else {
position.z = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}
if(position.x > 1.0) position.x = 1.0;
if(position.z > 1.0) position.z = 1.0;
if(x < 0) position.x = -position.x;
if(z < 0) position.z = -position.z;
if (y > 0) {
// top face
position.y = 1.0;
}
else {
// bottom face
position.y = -1.0;
}
}
else if (fx >= fy && fx >= fz) {
double a2 = y * y * 2.0;
double b2 = z * z * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);
if(y == 0.0 || y == -0.0) {
position.y = 0.0;
}
else {
position.y = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}
if(z == 0.0 || z == -0.0) {
position.z = 0.0;
}
else {
position.z = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}
if(position.y > 1.0) position.y = 1.0;
if(position.z > 1.0) position.z = 1.0;
if(y < 0) position.y = -position.y;
if(z < 0) position.z = -position.z;
if (x > 0) {
// right face
position.x = 1.0;
}
else {
// left face
position.x = -1.0;
}
}
else {
double a2 = x * x * 2.0;
double b2 = y * y * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);
if(x == 0.0 || x == -0.0) {
position.x = 0.0;
}
else {
position.x = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}
if(y == 0.0 || y == -0.0) {
position.y = 0.0;
}
else {
position.y = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}
if(position.x > 1.0) position.x = 1.0;
if(position.y > 1.0) position.y = 1.0;
if(x < 0) position.x = -position.x;
if(y < 0) position.y = -position.y;
if (z > 0) {
// front face
position.z = 1.0;
}
else {
// back face
position.z = -1.0;
}
}
So, this solution isn't nearly as pretty as the cube to sphere mapping, but it gets the job done!
Any suggestions to improve the efficiency or read ability of the code above are appreciated!
--- edit ---
I should mention that I have tested this and so far in my tests the code appears correct with the results being accurate to at least the 7th decimal place. And that was from when I was using floats, it's probably more accurate now with doubles.
--- edit ---
Here's an optimized glsl fragment shader version by Daniel to show that it doesn't have to be such a big scary function. Daniel uses this to filter sampling on cube maps! Great idea!
const float isqrt2 = 0.70710676908493042;
vec3 cubify(const in vec3 s)
{
float xx2 = s.x * s.x * 2.0;
float yy2 = s.y * s.y * 2.0;
vec2 v = vec2(xx2 – yy2, yy2 – xx2);
float ii = v.y – 3.0;
ii *= ii;
float isqrt = -sqrt(ii – 12.0 * xx2) + 3.0;
v = sqrt(v + isqrt);
v *= isqrt2;
return sign(s) * vec3(v, 1.0);
}
vec3 sphere2cube(const in vec3 sphere)
{
vec3 f = abs(sphere);
bool a = f.y >= f.x && f.y >= f.z;
bool b = f.x >= f.z;
return a ? cubify(sphere.xzy).xzy : b ? cubify(sphere.yzx).zxy : cubify(sphere);
}
After some rearranging you can get the "nice" forms
(1) 1/2 z^2 = (alpha) / ( y^2 - x^2) + 1
(2) 1/2 y^2 = (beta) / ( z^2 - x^2) + 1
(3) 1/2 x^2 = (gamma) / ( y^2 - z^2) + 1
where alpha = sx^2-sy^2 , beta = sx^2 - sz^2 and gamma = sz^2 - sy^2. Verify this yourself.
Now I neither have the motivation nor the time but from this point on its pretty straightforward to solve:
Substitute (1) into (2). Rearrange (2) until you get a polynomial (root) equation of the form
(4) a(x) * y^4 + b(x) * y^2 + c(x) = 0
this can be solved using the quadratic formula for y^2. Note that a(x),b(x),c(x) are some functions of x. The quadratic formula yields 2 roots for (4) which you will have to keep in mind.
Using (1),(2),(4) figure out an expression for z^2 in terms of only x^2.
Using (3) write out a polynomial root equation of the form:
(5) a * x^4 + b * x^2 + c = 0
where a,b,c are not functions but constants. Solve this using the quadratic formula. In total you will have 2*2=4 possible solutions for x^2,y^2,z^2 pair meaning you will
have 4*2=8 total solutions for possible x,y,z pairs satisfying these equations. Check conditions on each x,y,z pair and (hopefully) eliminate all but one (otherwise an inverse mapping does not exist.)
Good luck.
PS. It very well may be that the inverse mapping does not exist, think about the geometry: the sphere has surface area 4*pi*r^2 while the cube has surface area 6*d^2=6*(2r)^2=24r^2 so intuitively you have many more points on the cube that get mapped to the sphere. This means a many to one mapping, and any such mapping is not injective and hence is not bijective (i.e. the mapping has no inverse.) Sorry but I think you are out of luck.
----- edit --------------
if you follow the advice from MO, setting z=1 means you are looking at the solid square in the plane z=1.
Use your first two equations to solve for x,y, wolfram alpha gives the result:
x = (sqrt(6) s^2 sqrt(1/2 (sqrt((2 s^2-2 t^2-3)^2-24 t^2)+2 s^2-2 t^2-3)+3)-sqrt(6) t^2 sqrt(1/2 (sqrt((2 s^2-2 t^2-3)^2-24 t^2)+2 s^2-2 t^2-3)+3)-sqrt(3/2) sqrt((2 s^2-2 t^2-3)^2-24 t^2) sqrt(1/2 (sqrt((2 s^2-2 t^2-3)^2-24 t^2)+2 s^2-2 t^2-3)+3)+3 sqrt(3/2) sqrt(1/2 (sqrt((2 s^2-2 t^2-3)^2-24 t^2)+2 s^2-2 t^2-3)+3))/(6 s)
and
y = sqrt(-sqrt((2 s^2-2 t^2-3)^2-24 t^2)-2 s^2+2 t^2+3)/sqrt(2)
where above I use s=sx and t=sy, and I will use u=sz. Then you can use the third equation you have for u=sz. That is lets say that you want to map the top part of the sphere to the cube. Then for any 0 <= s,t <= 1 (where s,t are in the sphere's coordinate frame ) then the tuple (s,t,u) maps to (x,y,1) (here x,y are in the cubes coordinate frame.) The only thing left is for you to figure out what u is. You can figure this out by using s,t to solve for x,y then using x,y to solve for u.
Note that this will only map the top part of the cube to only the top plane of the cube z=1. You will have to do this for all 6 sides (x=1, y=1, z=0 ... etc ). I suggest using wolfram alpha to solve the resulting equations you get for each sub-case, because they will be as ugly or uglier as those above.
This answer contains the cube2sphere and sphere2cube without the restriction of a = 1. So the cube has side 2a from -a to a and the radius of the sphere is a.
I know it's been 10 years since this question was asked. Nevertheless, I am giving the answer in case someone needs it. The implementation is in Python,
I am using (x, y, z) for the cube coordinates, (p, q, r) for the sphere coordinates and the relevant underscore variables (x_, y_, z_) meaning they have been produced by using the inverse function.
import math
from random import randint # for testing
def sign_aux(x):
return lambda y: math.copysign(x, y)
sign = sign_aux(1) # no built-in sign function in python, I know...
def cube2sphere(x, y, z):
if (all([x == 0, y == 0, z == 0])):
return 0, 0, 0
def aux(x, y_2, z_2, a, a_2):
return x * math.sqrt(a_2 - y_2/2 - z_2/2 + y_2*z_2/(3*a_2))/a
x_2 = x*x
y_2 = y*y
z_2 = z*z
a = max(abs(x), abs(y), abs(z))
a_2 = a*a
return aux(x, y_2, z_2, a, a_2), aux(y, x_2, z_2, a, a_2), aux(z, x_2, y_2, a, a_2)
def sphere2cube(p, q, r):
if (all([p == 0, q == 0, r == 0])):
return 0, 0, 0
def aux(s, t, radius):
A = 3*radius*radius
R = 2*(s*s - t*t)
S = math.sqrt( max(0, (A+R)*(A+R) - 8*A*s*s) ) # use max 0 for accuraccy error
iot = math.sqrt(2)/2
s_ = sign(s) * iot * math.sqrt(max(0, A + R - S)) # use max 0 for accuraccy error
t_ = sign(t) * iot * math.sqrt(max(0, A - R - S)) # use max 0 for accuraccy error
return s_, t_
norm_p, norm_q, norm_r = abs(p), abs(q), abs(r)
norm_max = max(norm_p, norm_q, norm_r)
radius = math.sqrt(p*p + q*q + r*r)
if (norm_max == norm_p):
y, z = aux(q, r, radius)
x = sign(p) * radius
return x, y, z
if (norm_max == norm_q):
z, x = aux(r, p, radius)
y = sign(q) * radius
return x, y, z
x, y = aux(p, q, radius)
z = sign(r) * radius
return x, y, z
# measuring accuracy
max_mse = 0
for i in range(100000):
x = randint(-20, 20)
y = randint(-20, 20)
z = randint(-20, 20)
p, q, r = cube2sphere(x, y, z)
x_, y_, z_ = sphere2cube(p, q, r)
max_mse = max(max_mse, math.sqrt(((x-x_)**2 + (y-y_)**2 + (z-z_)**2))/3)
print(max_mse)
# 1.1239159602905078e-07
max_mse = 0
for i in range(100000):
p = randint(-20, 20)
q = randint(-20, 20)
r = randint(-20, 20)
x, y, z = sphere2cube(p, q, r)
p_, q_, r_ = cube2sphere(x, y, z)
max_mse = max(max_mse, math.sqrt(((p-p_)**2 + (q-q_)**2 + (r-r_)**2))/3)
print(max_mse)
# 9.832883321715792e-08
Also, I mapped some points to check the function visually and these are the results.
Here's one way you can think about it: for a given point P in the sphere, take the segment that starts at the origin, passes through P, and ends at the surface of the cube. Let L be the length of this segment. Now all you need to do is multiply P by L; this is equivalent to mapping ||P|| from the interval [0, 1] to the interval [0, L]. This mapping should be one-to-one - every point in the sphere goes to a unique point in the cube (and points on the surface stay on the surface). Note that this is assuming a unit sphere and cube; the idea should hold elsewhere, you'll just have a few scale factors involved.
I've glossed over the hard part (finding the segment), but this is a standard raycasting problem. There are some links here that explain how to compute this for an arbitrary ray versus axis-aligned bounding box; you can probably simplify things since your ray starts at the origin and goes to the unit cube. If you need help simplify the equations, let me know and I'll take a stab at it.
It looks like there is a much cleaner solution if you're not afraid of trig and pi, not sure if it's faster/comparable though.
Just take the remaining components after determining the face and do:
u = asin ( x ) / half_pi
v = asin ( y ) / half_pi
This is an intuitive leap, but this seems to back it up ( though not exactly the same topic ), so please correct me if I'm wrong.
I'm too lazy to post an illustration that explains why. :D

Resources