Interpolating values between interval, interpolation as per Bezier curve - math

To implement a 2D animation I am looking for interpolating values between two key frames with the velocity of change defined by a Bezier curve. The problem is Bezier curve is represented in parametric form whereas requirement is to be able to evaluate the value for a particular time.
To elaborate, lets say the value of 10 and 40 is to be interpolated across 4 seconds with the value changing not constantly but as defined by a bezier curve represented as 0,0 0.2,0.3 0.5,0.5 1,1.
Now if I am drawing at 24 frames per second, I need to evaluate the value for every frame. How can I do this ? I looked at De Casteljau algorithm and thought that dividing the curve into 24*4 pieces for 4 seconds would solve my problem but that sounds erroneous as time is along the "x" axis and not along the curve.
To further simplify
If I draw the curve in a plane, the x axis represents the time and the y axis the value I am looking for. What I actually require is to to be able to find out "y" corresponding to "x". Then I can divide x in 24 divisions and know the value for each frame

I was facing the same problem: Every animation package out there seems to use Bézier curves to control values over time, but there is no information out there on how to implement a Bézier curve as a y(x) function. So here is what I came up with.
A standard cubic Bézier curve in 2D space can be defined by the four points P0=(x0, y0) .. P3=(x3, y3).
P0 and P3 are the end points of the curve, while P1 and P2 are the handles affecting its shape. Using a parameter t ϵ [0, 1], the x and y coordinates for any given point along the curve can then be determined using the equations
A) x = (1-t)3x0 + 3t(1-t)2x1 + 3t2(1-t)x2 + t3x3 and
B) y = (1-t)3y0 + 3t(1-t)2y1 + 3t2(1-t)y2 + t3y3.
What we want is a function y(x) that, given an x coordinate, will return the corresponding y coordinate of the curve. For this to work, the curve must move monotonically from left to right, so that it doesn't occupy the same x coordinate more than once on different y positions. The easiest way to ensure this is to restrict the input points so that x0 < x3 and x1, x2 ϵ [x0, x3]. In other words, P0 must be to the left of P3 with the two handles between them.
In order to calculate y for a given x, we must first determine t from x. Getting y from t is then a simple matter of applying t to equation B.
I see two ways of determining t for a given y.
First, you might try a binary search for t. Start with a lower bound of 0 and an upper bound of 1 and calculate x for these values for t via equation A. Keep bisecting the interval until you get a reasonably close approximation. While this should work fine, it will neither be particularly fast nor very precise (at least not both at once).
The second approach is to actually solve equation A for t. That's a bit tough to implement because the equation is cubic. On the other hand, calculation becomes really fast and yields precise results.
Equation A can be rewritten as
(-x0+3x1-3x2+x3)t3 + (3x0-6x1+3x2)t2 + (-3x0+3x1)t + (x0-x) = 0.
Inserting your actual values for x0..x3, we get a cubic equation of the form at3 + bt2 + c*t + d = 0 for which we know there is only one solution within [0, 1]. We can now solve this equation using an algorithm like the one posted in this Stack Overflow answer.
The following is a little C# class demonstrating this approach. It should be simple enough to convert it to a language of your choice.
using System;
public class Point {
public Point(double x, double y) {
X = x;
Y = y;
}
public double X { get; private set; }
public double Y { get; private set; }
}
public class BezierCurve {
public BezierCurve(Point p0, Point p1, Point p2, Point p3) {
P0 = p0;
P1 = p1;
P2 = p2;
P3 = p3;
}
public Point P0 { get; private set; }
public Point P1 { get; private set; }
public Point P2 { get; private set; }
public Point P3 { get; private set; }
public double? GetY(double x) {
// Determine t
double t;
if (x == P0.X) {
// Handle corner cases explicitly to prevent rounding errors
t = 0;
} else if (x == P3.X) {
t = 1;
} else {
// Calculate t
double a = -P0.X + 3 * P1.X - 3 * P2.X + P3.X;
double b = 3 * P0.X - 6 * P1.X + 3 * P2.X;
double c = -3 * P0.X + 3 * P1.X;
double d = P0.X - x;
double? tTemp = SolveCubic(a, b, c, d);
if (tTemp == null) return null;
t = tTemp.Value;
}
// Calculate y from t
return Cubed(1 - t) * P0.Y
+ 3 * t * Squared(1 - t) * P1.Y
+ 3 * Squared(t) * (1 - t) * P2.Y
+ Cubed(t) * P3.Y;
}
// Solves the equation ax³+bx²+cx+d = 0 for x ϵ ℝ
// and returns the first result in [0, 1] or null.
private static double? SolveCubic(double a, double b, double c, double d) {
if (a == 0) return SolveQuadratic(b, c, d);
if (d == 0) return 0;
b /= a;
c /= a;
d /= a;
double q = (3.0 * c - Squared(b)) / 9.0;
double r = (-27.0 * d + b * (9.0 * c - 2.0 * Squared(b))) / 54.0;
double disc = Cubed(q) + Squared(r);
double term1 = b / 3.0;
if (disc > 0) {
double s = r + Math.Sqrt(disc);
s = (s < 0) ? -CubicRoot(-s) : CubicRoot(s);
double t = r - Math.Sqrt(disc);
t = (t < 0) ? -CubicRoot(-t) : CubicRoot(t);
double result = -term1 + s + t;
if (result >= 0 && result <= 1) return result;
} else if (disc == 0) {
double r13 = (r < 0) ? -CubicRoot(-r) : CubicRoot(r);
double result = -term1 + 2.0 * r13;
if (result >= 0 && result <= 1) return result;
result = -(r13 + term1);
if (result >= 0 && result <= 1) return result;
} else {
q = -q;
double dum1 = q * q * q;
dum1 = Math.Acos(r / Math.Sqrt(dum1));
double r13 = 2.0 * Math.Sqrt(q);
double result = -term1 + r13 * Math.Cos(dum1 / 3.0);
if (result >= 0 && result <= 1) return result;
result = -term1 + r13 * Math.Cos((dum1 + 2.0 * Math.PI) / 3.0);
if (result >= 0 && result <= 1) return result;
result = -term1 + r13 * Math.Cos((dum1 + 4.0 * Math.PI) / 3.0);
if (result >= 0 && result <= 1) return result;
}
return null;
}
// Solves the equation ax² + bx + c = 0 for x ϵ ℝ
// and returns the first result in [0, 1] or null.
private static double? SolveQuadratic(double a, double b, double c) {
double result = (-b + Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);
if (result >= 0 && result <= 1) return result;
result = (-b - Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);
if (result >= 0 && result <= 1) return result;
return null;
}
private static double Squared(double f) { return f * f; }
private static double Cubed(double f) { return f * f * f; }
private static double CubicRoot(double f) { return Math.Pow(f, 1.0 / 3.0); }
}

You have a few options:
Let's say your curve function F(t) takes a parameter t that ranges from 0 to 1 where F(0) is the beginning of the curve and F(1) is the end of the curve.
You could animate motion along the curve by incrementing t at a constant change per unit of time.
So t is defined by function T(time) = Constant*time
For example, if your frame is 1/24th of a second, and you want to move along the curve at a rate of 0.1 units of t per second, then each frame you increment t by 0.1 (t/s) * 1/24 (sec/frame).
A drawback here is that your actual speed or distance traveled per unit time will not be constant. It will depends on the positions of your control points.
If you want to scale speed along the curve uniformly you can modify the constant change in t per unit time. However, if you want speeds to vary dramatically you will find it difficult to control the shape of the curve. If you want the velocity at one endpoint to be much larger, you must move the control point further away, which in turn pulls the shape of the curve towards that point. If this is a problem, you may consider using a non constant function for t. There are a variety of approaches with different trade-offs, and we need to know more details about your problem to suggest a solution. For example, in the past I have allowed users to define the speed at each keyframe and used a lookup table to translate from time to parameter t such that there is a linear change in speed between keyframe speeds (it's complicated).
Another common hangup: If you are animating by connecting several Bezier curves, and you want the velocity to be continuous when moving between curves, then you will need to constrain your control points so they are symmetrical with the adjacent curve. Catmull-Rom splines are a common approach.

I've answered a similar question here. Basically if you know the control points before hand then you can transform the f(t) function into a y(x) function. To not have to do it all by hand you can use services like Wolfram Alpha to help you with the math.

Related

Find where line-segments intersect with a box

I am trying to figure out where a bunch of line-segments clip into a window around them. I saw the Liang–Barsky algorithm, but that seems to assume the segments already clip the edges of the window, which these do not.
Say I have a window from (0,0) to (26,16), and the following segments:
(7,6) - (16,3)
(10,6) - (19,6)
(13,10) - (21,3)
(16,12) - (19,14)
Illustration:
I imagine I need to extend the segments to a certain X or Y point, till they hit the edge of the window, but I don't know how.
How would I find the points where these segments (converted to lines?) clip into the edge of the window? I will be implementing this in C#, but this is pretty language-agnostic.
If you have two line segments P and Q with points
P0 - P1
Q0 - Q1
The line equations are
P = P0 + t(P1 - P0)
Q = Q0 + r(Q1 - Q0)
then to find out where they intersect after extension you need to solve the following equation for t and r
P0 + t(P1 - P0) = Q0 + r(Q1 - Q0)
The following code can do this. ( Extracted from my own code base )
public static (double t, double r )? SolveIntersect(this Segment2D P, Segment2D Q)
{
// a-d are the entries of a 2x2 matrix
var a = P.P1.X - P.P0.X;
var b = -Q.P1.X + Q.P0.X;
var c = P.P1.Y - P.P0.Y;
var d = -Q.P1.Y + Q.P0.Y;
var det = a*d - b*c;
if (Math.Abs( det ) < Utility.ZERO_TOLERANCE)
return null;
var x = Q.P0.X - P.P0.X;
var y = Q.P0.Y - P.P0.Y;
var t = 1/det*(d*x - b*y);
var r = 1/det*(-c*x + a*y);
return (t, r);
}
If null is returned from the function then it means the lines are parallel and cannot intersect. If a result is returned then you can do.
var result = SolveIntersect( P, Q );
if (result != null)
{
var ( t, r) = result.Value;
var p = P.P0 + t * (P.P1 - P.P0);
var q = Q.P0 + t * (Q.P1 - Q.P0);
// p and q are the same point of course
}
The extended line segments will generally intersect more than one box edge but only one of those intersections will be inside the box. You can check this easily.
bool IsInBox(Point corner0, Point corner1, Point test) =>
(test.X > corner0.X && test.X < corner1.X && test.Y > corner0.Y && test.Y < corner1.Y ;
That should give you all you need to extend you lines to the edge of your box.
I managed to figure this out.
I can extend my lines to the edge of the box by first finding the equations of my lines, then solving for the X and Y of each of the sides to get their corresponding point. This requires passing the max and min Y and the max and min X into the following functions, returning 4 values. If the point is outside the bounds of the box, it can be ignored.
My code is in C#, and is making extension methods for EMGU's LineSegment2D. This is a .NET wrapper for OpenCv.
My Code:
public static float GetYIntersection(this LineSegment2D line, float x)
{
Point p1 = line.P1;
Point p2 = line.P2;
float dx = p2.X - p1.X;
if(dx == 0)
{
return float.NaN;
}
float m = (p2.Y - p1.Y) / dx; //Slope
float b = p1.Y - (m * p1.X); //Y-Intercept
return m * x + b;
}
public static float GetXIntersection(this LineSegment2D line, float y)
{
Point p1 = line.P1;
Point p2 = line.P2;
float dx = p2.X - p1.X;
if (dx == 0)
{
return float.NaN;
}
float m = (p2.Y - p1.Y) / dx; //Slope
float b = p1.Y - (m * p1.X); //Y-Intercept
return (y - b) / m;
}
I can then take these points, check if they are in the bounds of the box, discard the ones that are not, remove duplicate points (line goes directly into corner). This will leave me with one x and one y value, which I can then pair to the corresponding min or max Y or X values I passed into the functions to make 2 points. I can then make my new segment with the two points.
Wiki description of Liang-Barsky algorithm is not bad, but code is flaw.
Note: this algorithm intended to throw out lines without intersection as soon as possible. If most of lines intersect the rectangle, then approach from your answer might be rather effective, otherwise L-B algorithm wins.
This page describes approach in details and contains concise effective code:
// Liang-Barsky function by Daniel White # http://www.skytopia.com/project/articles/compsci/clipping.html
// This function inputs 8 numbers, and outputs 4 new numbers (plus a boolean value to say whether the clipped line is drawn at all).
//
bool LiangBarsky (double edgeLeft, double edgeRight, double edgeBottom, double edgeTop, // Define the x/y clipping values for the border.
double x0src, double y0src, double x1src, double y1src, // Define the start and end points of the line.
double &x0clip, double &y0clip, double &x1clip, double &y1clip) // The output values, so declare these outside.
{
double t0 = 0.0; double t1 = 1.0;
double xdelta = x1src-x0src;
double ydelta = y1src-y0src;
double p,q,r;
for(int edge=0; edge<4; edge++) { // Traverse through left, right, bottom, top edges.
if (edge==0) { p = -xdelta; q = -(edgeLeft-x0src); }
if (edge==1) { p = xdelta; q = (edgeRight-x0src); }
if (edge==2) { p = -ydelta; q = -(edgeBottom-y0src);}
if (edge==3) { p = ydelta; q = (edgeTop-y0src); }
if(p==0 && q<0) return false; // Don't draw line at all. (parallel line outside)
r = q/p;
if(p<0) {
if(r>t1) return false; // Don't draw line at all.
else if(r>t0) t0=r; // Line is clipped!
} else if(p>0) {
if(r<t0) return false; // Don't draw line at all.
else if(r<t1) t1=r; // Line is clipped!
}
}
x0clip = x0src + t0*xdelta;
y0clip = y0src + t0*ydelta;
x1clip = x0src + t1*xdelta;
y1clip = y0src + t1*ydelta;
return true; // (clipped) line is drawn
}

Calculate min distance between a "line" and one "point"

I have a "linestring" (with init and end points) and a single "point" (two coordinates).
And I have implemented the following ActionSctipt code to use "haversine formula" to calculate the distance between two points (each point has x & y coordinates); this function can return the "distance" in "kms", "meters", "feets" or "miles":
private function distanceBetweenCoordinates(lat1:Number, lon1:Number, lat2:Number, lon2:Number, units:String = "miles"):Number {
var R:int = RADIUS_OF_EARTH_IN_MILES;
if (units == "km") {
R = RADIUS_OF_EARTH_IN_KM;
}
if (units == "meters") {
R = RADIUS_OF_EARTH_IN_M;
}
if (units == "feet") {
R = RADIUS_OF_EARTH_IN_FEET;
}
var dLat:Number = (lat2 - lat1) * Math.PI / 180;
var dLon:Number = (lon2 - lon1) * Math.PI / 180;
var lat1inRadians:Number = lat1 * Math.PI / 180;
var lat2inRadians:Number = lat2 * Math.PI / 180;
var a:Number = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1inRadians) * Math.cos(lat2inRadians);
var c:Number = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d:Number = R * c;
return d;
}
This code is functioning well. But I need to improving this code to allow calculate the minimum distance between a "single point" and one "linestring" (with 2 points).
How can I do?
I thought this solution:
* Divide the "linesting" for each point (Init and end)... and for each of these calculate the distance to the "single point"... and after I getting both "distances" return the minimum distance.
This solution is not the better, this is explained in the following image:
"d1" and "d2" distances are invalid... because only "d0" is the valid distance.
Please! help me!!! How can I improve the haversine formula to calculate the distance between a line and a single point in kilometres?
Thanks!!!!
In your case d0 distance is a height of triangle. It's Hb=2*A/b where A- Area & b-length of the base side (your linestring).
If given 3 points you can calculate the the distances between them (sides a, b, c of triangle). It will allow you to calculate triangle Area: A=sqrt(p*(p-a)*(p-b)*(p-c)) where p is half perimeter: p=(a+b+c)/2. So, now u have all variables u need to calculate the distance Hb (your "d0").

How can I generate a set of points evenly distributed along the perimeter of an ellipse?

If I want to generate a bunch of points distributed uniformly around a circle, I can do this (python):
r = 5 #radius
n = 20 #points to generate
circlePoints = [
(r * math.cos(theta), r * math.sin(theta))
for theta in (math.pi*2 * i/n for i in range(n))
]
However, the same logic doesn't generate uniform points on an ellipse: points on the "ends" are more closely spaced than points on the "sides".
r1 = 5
r2 = 10
n = 20 #points to generate
ellipsePoints = [
(r1 * math.cos(theta), r2 * math.sin(theta))
for theta in (math.pi*2 * i/n for i in range(n))
]
Is there an easy way to generate equally spaced points around an ellipse?
This is an old thread, but since I am seeking the same task of creating evenly spaced points along and ellipse and was not able to find an implementation, I offer this Java code that implements the pseudo code of Howard:
package com.math;
public class CalculatePoints {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*
*
dp(t) = sqrt( (r1*sin(t))^2 + (r2*cos(t))^2)
circ = sum(dp(t), t=0..2*Pi step 0.0001)
n = 20
nextPoint = 0
run = 0.0
for t=0..2*Pi step 0.0001
if n*run/circ >= nextPoint then
set point (r1*cos(t), r2*sin(t))
nextPoint = nextPoint + 1
next
run = run + dp(t)
next
*/
double r1 = 20.0;
double r2 = 10.0;
double theta = 0.0;
double twoPi = Math.PI*2.0;
double deltaTheta = 0.0001;
double numIntegrals = Math.round(twoPi/deltaTheta);
double circ=0.0;
double dpt=0.0;
/* integrate over the elipse to get the circumference */
for( int i=0; i < numIntegrals; i++ ) {
theta += i*deltaTheta;
dpt = computeDpt( r1, r2, theta);
circ += dpt;
}
System.out.println( "circumference = " + circ );
int n=20;
int nextPoint = 0;
double run = 0.0;
theta = 0.0;
for( int i=0; i < numIntegrals; i++ ) {
theta += deltaTheta;
double subIntegral = n*run/circ;
if( (int) subIntegral >= nextPoint ) {
double x = r1 * Math.cos(theta);
double y = r2 * Math.sin(theta);
System.out.println( "x=" + Math.round(x) + ", y=" + Math.round(y));
nextPoint++;
}
run += computeDpt(r1, r2, theta);
}
}
static double computeDpt( double r1, double r2, double theta ) {
double dp=0.0;
double dpt_sin = Math.pow(r1*Math.sin(theta), 2.0);
double dpt_cos = Math.pow( r2*Math.cos(theta), 2.0);
dp = Math.sqrt(dpt_sin + dpt_cos);
return dp;
}
}
(UPDATED: to reflect new packaging).
An efficient solution of this problem for Python can be found in the numeric branch FlyingCircus-Numeric, derivated from the FlyingCircus Python package.
Disclaimer: I am the main author of them.
Briefly, the (simplified) code looks (where a is the minor axis, and b is the major axis):
import numpy as np
import scipy as sp
import scipy.optimize
def angles_in_ellipse(
num,
a,
b):
assert(num > 0)
assert(a < b)
angles = 2 * np.pi * np.arange(num) / num
if a != b:
e2 = (1.0 - a ** 2.0 / b ** 2.0)
tot_size = sp.special.ellipeinc(2.0 * np.pi, e2)
arc_size = tot_size / num
arcs = np.arange(num) * arc_size
res = sp.optimize.root(
lambda x: (sp.special.ellipeinc(x, e2) - arcs), angles)
angles = res.x
return angles
It makes use of scipy.special.ellipeinc() which provides the numerical integral along the perimeter of the ellipse, and scipy.optimize.root()
for solving the equal-arcs length equation for the angles.
To test that it is actually working:
a = 10
b = 20
n = 16
phi = angles_in_ellipse(n, a, b)
print(np.round(np.rad2deg(phi), 2))
# [ 0. 17.55 36.47 59.13 90. 120.87 143.53 162.45 180. 197.55
# 216.47 239.13 270. 300.87 323.53 342.45]
e = (1.0 - a ** 2.0 / b ** 2.0) ** 0.5
arcs = sp.special.ellipeinc(phi, e)
print(np.round(np.diff(arcs), 4))
# [0.3022 0.2982 0.2855 0.2455 0.2455 0.2855 0.2982 0.3022 0.3022 0.2982
# 0.2855 0.2455 0.2455 0.2855 0.2982]
# plotting
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca()
ax.axes.set_aspect('equal')
ax.scatter(b * np.sin(phi), a * np.cos(phi))
plt.show()
You have to calculate the perimeter, then divide it into equal length arcs. The length of an arc of an ellipse is an elliptic integral and cannot be written in closed form so you need numerical computation.
The article on ellipses on wolfram gives you the formula needed to do this, but this is going to be ugly.
A possible (numerical) calculation can look as follows:
dp(t) = sqrt( (r1*sin(t))^2 + (r2*cos(t))^2)
circ = sum(dp(t), t=0..2*Pi step 0.0001)
n = 20
nextPoint = 0
run = 0.0
for t=0..2*Pi step 0.0001
if n*run/circ >= nextPoint then
set point (r1*cos(t), r2*sin(t))
nextPoint = nextPoint + 1
next
run = run + dp(t)
next
This is a simple numerical integration scheme. If you need better accuracy you might also use any other integration method.
I'm sure this thread is long dead by now, but I just came across this issue and this was the closest that came to a solution.
I started with Dave's answer here, but I noticed that it wasn't really answering the poster's question. It wasn't dividing the ellipse equally by arc lengths, but by angle.
Anyway, I made some adjustments to his (awesome) work to get the ellipse to divide equally by arc length instead (written in C# this time). If you look at the code, you'll see some of the same stuff -
void main()
{
List<Point> pointsInEllipse = new List<Point>();
// Distance in radians between angles measured on the ellipse
double deltaAngle = 0.001;
double circumference = GetLengthOfEllipse(deltaAngle);
double arcLength = 0.1;
double angle = 0;
// Loop until we get all the points out of the ellipse
for (int numPoints = 0; numPoints < circumference / arcLength; numPoints++)
{
angle = GetAngleForArcLengthRecursively(0, arcLength, angle, deltaAngle);
double x = r1 * Math.Cos(angle);
double y = r2 * Math.Sin(angle);
pointsInEllipse.Add(new Point(x, y));
}
}
private double GetLengthOfEllipse()
{
// Distance in radians between angles
double deltaAngle = 0.001;
double numIntegrals = Math.Round(Math.PI * 2.0 / deltaAngle);
double radiusX = (rectangleRight - rectangleLeft) / 2;
double radiusY = (rectangleBottom - rectangleTop) / 2;
// integrate over the elipse to get the circumference
for (int i = 0; i < numIntegrals; i++)
{
length += ComputeArcOverAngle(radiusX, radiusY, i * deltaAngle, deltaAngle);
}
return length;
}
private double GetAngleForArcLengthRecursively(double currentArcPos, double goalArcPos, double angle, double angleSeg)
{
// Calculate arc length at new angle
double nextSegLength = ComputeArcOverAngle(majorRadius, minorRadius, angle + angleSeg, angleSeg);
// If we've overshot, reduce the delta angle and try again
if (currentArcPos + nextSegLength > goalArcPos) {
return GetAngleForArcLengthRecursively(currentArcPos, goalArcPos, angle, angleSeg / 2);
// We're below the our goal value but not in range (
} else if (currentArcPos + nextSegLength < goalArcPos - ((goalArcPos - currentArcPos) * ARC_ACCURACY)) {
return GetAngleForArcLengthRecursively(currentArcPos + nextSegLength, goalArcPos, angle + angleSeg, angleSeg);
// current arc length is in range (within error), so return the angle
} else
return angle;
}
private double ComputeArcOverAngle(double r1, double r2, double angle, double angleSeg)
{
double distance = 0.0;
double dpt_sin = Math.Pow(r1 * Math.Sin(angle), 2.0);
double dpt_cos = Math.Pow(r2 * Math.Cos(angle), 2.0);
distance = Math.Sqrt(dpt_sin + dpt_cos);
// Scale the value of distance
return distance * angleSeg;
}
From my answer in BSE here .
I add it in stackoverflow as it is a different approach which does not rely on a fixed iteration steps but rely on a convergence of the distances between the points, to the mean distance.
So the calculation is shorter as it depends only on the wanted vertices amount and on the precision to reach (about 6 iterations for less than 0.01%).
The principle is :
0/ First step : calculate the points normally using a * cos(t) and b * sin(t)
1/ Calculate the lengths between vertices
2/ Adjust the angles variations depending on the gap between each distance to the mean distance
3/ Reposition the points
4/ Exit when the wanted precision is reached or return to 1/
import bpy, bmesh
from math import radians, sqrt, cos, sin
rad90 = radians( 90.0 )
rad180 = radians( 180.0 )
def createVertex( bm, x, y ): #uses bmesh to create a vertex
return bm.verts.new( [x, y, 0] )
def listSum( list, index ): #helper to sum on a list
sum = 0
for i in list:
sum = sum + i[index]
return sum
def calcLength( points ): #calculate the lenghts for consecutives points
prevPoint = points[0]
for point in points :
dx = point[0] - prevPoint[0]
dy = point[1] - prevPoint[1]
dist = sqrt( dx * dx + dy *dy )
point[3] = dist
prevPoint = point
def calcPos( points, a, b ): #calculate the positions following the angles
angle = 0
for i in range( 1, len(points) - 1 ):
point = points[i]
angle += point[2]
point[0] = a * cos( angle )
point[1] = b * sin( angle )
def adjust( points ): #adjust the angle by comparing each length to the mean length
totalLength = listSum( points, 3 )
averageLength = totalLength / (len(points) - 1)
maxRatio = 0
for i in range( 1, len(points) ):
point = points[i]
ratio = (averageLength - point[3]) / averageLength
point[2] = (1.0 + ratio) * point[2]
absRatio = abs( ratio )
if absRatio > maxRatio:
maxRatio = absRatio
return maxRatio
def ellipse( bm, a, b, steps, limit ):
delta = rad90 / steps
angle = 0.0
points = [] #will be a list of [ [x, y, angle, length], ...]
for step in range( steps + 1 ) :
x = a * cos( angle )
y = b * sin( angle )
points.append( [x, y, delta, 0.0] )
angle += delta
print( 'start' )
doContinue = True
while doContinue:
calcLength( points )
maxRatio = adjust( points )
calcPos( points, a, b )
doContinue = maxRatio > limit
print( maxRatio )
verts = []
for point in points:
verts.append( createVertex( bm, point[0], point[1] ) )
for i in range( 1, len(verts) ):
bm.edges.new( [verts[i - 1], verts[i]] )
A = 4
B = 6
bm = bmesh.new()
ellipse( bm, A, B, 32, 0.00001 )
mesh = bpy.context.object.data
bm.to_mesh(mesh)
mesh.update()
Do take into consideration the formula for ellipse perimeter as under if the ellipse is squashed. (If the minor axis is three times as small as the major axis)
tot_size = np.pi*(3*(a+b) -np.sqrt((3*a+b)*a+3*b))
Ellipse Perimeter
There is working MATLAB code available here. I replicate that below in case that link ever goes dead. Credits are due to the original author.
This code assumes that the major axis is a line segment from (x1, y1) to (x2, y2) and e is the eccentricity of the ellipse.
a = 1/2*sqrt((x2-x1)^2+(y2-y1)^2);
b = a*sqrt(1-e^2);
t = linspace(0,2*pi, 20);
X = a*cos(t);
Y = b*sin(t);
w = atan2(y2-y1,x2-x1);
x = (x1+x2)/2 + X*cos(w) - Y*sin(w);
y = (y1+y2)/2 + X*sin(w) + Y*cos(w);
plot(x,y,'o')
axis equal

How to scale down a range of numbers with a known min and max value

So I am trying to figure out how to take a range of numbers and scale the values down to fit a range. The reason for wanting to do this is that I am trying to draw ellipses in a java swing jpanel. I want the height and width of each ellipse to be in a range of say 1-30. I have methods that find the minimum and maximum values from my data set, but I won't have the min and max until runtime. Is there an easy way to do this?
Let's say you want to scale a range [min,max] to [a,b]. You're looking for a (continuous) function that satisfies
f(min) = a
f(max) = b
In your case, a would be 1 and b would be 30, but let's start with something simpler and try to map [min,max] into the range [0,1].
Putting min into a function and getting out 0 could be accomplished with
f(x) = x - min ===> f(min) = min - min = 0
So that's almost what we want. But putting in max would give us max - min when we actually want 1. So we'll have to scale it:
x - min max - min
f(x) = --------- ===> f(min) = 0; f(max) = --------- = 1
max - min max - min
which is what we want. So we need to do a translation and a scaling. Now if instead we want to get arbitrary values of a and b, we need something a little more complicated:
(b-a)(x - min)
f(x) = -------------- + a
max - min
You can verify that putting in min for x now gives a, and putting in max gives b.
You might also notice that (b-a)/(max-min) is a scaling factor between the size of the new range and the size of the original range. So really we are first translating x by -min, scaling it to the correct factor, and then translating it back up to the new minimum value of a.
Here's some JavaScript for copy-paste ease (this is irritate's answer):
function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
}
Applied like so, scaling the range 10-50 to a range between 0-100.
var unscaledNums = [10, 13, 25, 28, 43, 50];
var maxRange = Math.max.apply(Math, unscaledNums);
var minRange = Math.min.apply(Math, unscaledNums);
for (var i = 0; i < unscaledNums.length; i++) {
var unscaled = unscaledNums[i];
var scaled = scaleBetween(unscaled, 0, 100, minRange, maxRange);
console.log(scaled.toFixed(2));
}
0.00, 18.37, 48.98, 55.10, 85.71, 100.00
Edit:
I know I answered this a long time ago, but here's a cleaner function that I use now:
Array.prototype.scaleBetween = function(scaledMin, scaledMax) {
var max = Math.max.apply(Math, this);
var min = Math.min.apply(Math, this);
return this.map(num => (scaledMax-scaledMin)*(num-min)/(max-min)+scaledMin);
}
Applied like so:
[-4, 0, 5, 6, 9].scaleBetween(0, 100);
[0, 30.76923076923077, 69.23076923076923, 76.92307692307692, 100]
For convenience, here is Irritate's algorithm in a Java form. Add error checking, exception handling and tweak as necessary.
public class Algorithms {
public static double scale(final double valueIn, final double baseMin, final double baseMax, final double limitMin, final double limitMax) {
return ((limitMax - limitMin) * (valueIn - baseMin) / (baseMax - baseMin)) + limitMin;
}
}
Tester:
final double baseMin = 0.0;
final double baseMax = 360.0;
final double limitMin = 90.0;
final double limitMax = 270.0;
double valueIn = 0;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
valueIn = 360;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
valueIn = 180;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
90.0
270.0
180.0
Here's how I understand it:
What percent does x lie in a range
Let's assume you have a range from 0 to 100. Given an arbitrary number from that range, what "percent" from that range does it lie in? This should be pretty simple, 0 would be 0%, 50 would be 50% and 100 would be 100%.
Now, what if your range was 20 to 100? We cannot apply the same logic as above (divide by 100) because:
20 / 100
doesn't give us 0 (20 should be 0% now). This should be simple to fix, we just need to make the numerator 0 for the case of 20. We can do that by subtracting:
(20 - 20) / 100
However, this doesn't work for 100 anymore because:
(100 - 20) / 100
doesn't give us 100%. Again, we can fix this by subtracting from the denominator as well:
(100 - 20) / (100 - 20)
A more generalized equation for finding out what % x lies in a range would be:
(x - MIN) / (MAX - MIN)
Scale range to another range
Now that we know what percent a number lies in a range, we can apply it to map the number to another range. Let's go through an example.
old range = [200, 1000]
new range = [10, 20]
If we have a number in the old range, what would the number be in the new range? Let's say the number is 400. First, figure out what percent 400 is within the old range. We can apply our equation above.
(400 - 200) / (1000 - 200) = 0.25
So, 400 lies in 25% of the old range. We just need to figure out what number is 25% of the new range. Think about what 50% of [0, 20] is. It would be 10 right? How did you arrive at that answer? Well, we can just do:
20 * 0.5 = 10
But, what about from [10, 20]? We need to shift everything by 10 now. eg:
((20 - 10) * 0.5) + 10
a more generalized formula would be:
((MAX - MIN) * PERCENT) + MIN
To the original example of what 25% of [10, 20] is:
((20 - 10) * 0.25) + 10 = 12.5
So, 400 in the range [200, 1000] would map to 12.5 in the range [10, 20]
TLDR
To map x from old range to new range:
OLD PERCENT = (x - OLD MIN) / (OLD MAX - OLD MIN)
NEW X = ((NEW MAX - NEW MIN) * OLD PERCENT) + NEW MIN
I came across this solution but this does not really fit my need. So I digged a bit in the d3 source code. I personally would recommend to do it like d3.scale does.
So here you scale the domain to the range. The advantage is that you can flip signs to your target range. This is useful since the y axis on a computer screen goes top down so large values have a small y.
public class Rescale {
private final double range0,range1,domain0,domain1;
public Rescale(double domain0, double domain1, double range0, double range1) {
this.range0 = range0;
this.range1 = range1;
this.domain0 = domain0;
this.domain1 = domain1;
}
private double interpolate(double x) {
return range0 * (1 - x) + range1 * x;
}
private double uninterpolate(double x) {
double b = (domain1 - domain0) != 0 ? domain1 - domain0 : 1 / domain1;
return (x - domain0) / b;
}
public double rescale(double x) {
return interpolate(uninterpolate(x));
}
}
And here is the test where you can see what I mean
public class RescaleTest {
#Test
public void testRescale() {
Rescale r;
r = new Rescale(5,7,0,1);
Assert.assertTrue(r.rescale(5) == 0);
Assert.assertTrue(r.rescale(6) == 0.5);
Assert.assertTrue(r.rescale(7) == 1);
r = new Rescale(5,7,1,0);
Assert.assertTrue(r.rescale(5) == 1);
Assert.assertTrue(r.rescale(6) == 0.5);
Assert.assertTrue(r.rescale(7) == 0);
r = new Rescale(-3,3,0,1);
Assert.assertTrue(r.rescale(-3) == 0);
Assert.assertTrue(r.rescale(0) == 0.5);
Assert.assertTrue(r.rescale(3) == 1);
r = new Rescale(-3,3,-1,1);
Assert.assertTrue(r.rescale(-3) == -1);
Assert.assertTrue(r.rescale(0) == 0);
Assert.assertTrue(r.rescale(3) == 1);
}
}
I sometimes find a variation of this useful.
Wrapping the scale function in a class so that I do not need to pass around the min/max values if scaling the same ranges in several places
Adding two small checks that ensures that the result value stays within the expected range.
Example in JavaScript:
class Scaler {
constructor(inMin, inMax, outMin, outMax) {
this.inMin = inMin;
this.inMax = inMax;
this.outMin = outMin;
this.outMax = outMax;
}
scale(value) {
const result = (value - this.inMin) * (this.outMax - this.outMin) / (this.inMax - this.inMin) + this.outMin;
if (result < this.outMin) {
return this.outMin;
} else if (result > this.outMax) {
return this.outMax;
}
return result;
}
}
This example along with a function based version comes from the page https://writingjavascript.com/scaling-values-between-two-ranges
Based on Charles Clayton's response, I included some JSDoc, ES6 tweaks, and incorporated suggestions from the comments in the original response.
/**
* Returns a scaled number within its source bounds to the desired target bounds.
* #param {number} n - Unscaled number
* #param {number} tMin - Minimum (target) bound to scale to
* #param {number} tMax - Maximum (target) bound to scale to
* #param {number} sMin - Minimum (source) bound to scale from
* #param {number} sMax - Maximum (source) bound to scale from
* #returns {number} The scaled number within the target bounds.
*/
const scaleBetween = (n, tMin, tMax, sMin, sMax) => {
return (tMax - tMin) * (n - sMin) / (sMax - sMin) + tMin;
}
if (Array.prototype.scaleBetween === undefined) {
/**
* Returns a scaled array of numbers fit to the desired target bounds.
* #param {number} tMin - Minimum (target) bound to scale to
* #param {number} tMax - Maximum (target) bound to scale to
* #returns {number} The scaled array.
*/
Array.prototype.scaleBetween = function(tMin, tMax) {
if (arguments.length === 1 || tMax === undefined) {
tMax = tMin; tMin = 0;
}
let sMax = Math.max(...this), sMin = Math.min(...this);
if (sMax - sMin == 0) return this.map(num => (tMin + tMax) / 2);
return this.map(num => (tMax - tMin) * (num - sMin) / (sMax - sMin) + tMin);
}
}
// ================================================================
// Usage
// ================================================================
let nums = [10, 13, 25, 28, 43, 50], tMin = 0, tMax = 100,
sMin = Math.min(...nums), sMax = Math.max(...nums);
// Result: [ 0.0, 7.50, 37.50, 45.00, 82.50, 100.00 ]
console.log(nums.map(n => scaleBetween(n, tMin, tMax, sMin, sMax).toFixed(2)).join(', '));
// Result: [ 0, 30.769, 69.231, 76.923, 100 ]
console.log([-4, 0, 5, 6, 9].scaleBetween(0, 100).join(', '));
// Result: [ 50, 50, 50 ]
console.log([1, 1, 1].scaleBetween(0, 100).join(', '));
.as-console-wrapper { top: 0; max-height: 100% !important; }
I've taken Irritate's answer and refactored it so as to minimize the computational steps for subsequent computations by factoring it into the fewest constants. The motivation is to allow a scaler to be trained on one set of data, and then be run on new data (for an ML algo). In effect, it's much like SciKit's preprocessing MinMaxScaler for Python in usage.
Thus, x' = (b-a)(x-min)/(max-min) + a (where b!=a) becomes x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a which can be reduced to two constants in the form x' = x*Part1 + Part2.
Here's a C# implementation with two constructors: one to train, and one to reload a trained instance (e.g., to support persistence).
public class MinMaxColumnSpec
{
/// <summary>
/// To reduce repetitive computations, the min-max formula has been refactored so that the portions that remain constant are just computed once.
/// This transforms the forumula from
/// x' = (b-a)(x-min)/(max-min) + a
/// into x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a
/// which can be further factored into
/// x' = x*Part1 + Part2
/// </summary>
public readonly double Part1, Part2;
/// <summary>
/// Use this ctor to train a new scaler.
/// </summary>
public MinMaxColumnSpec(double[] columnValues, int newMin = 0, int newMax = 1)
{
if (newMax <= newMin)
throw new ArgumentOutOfRangeException("newMax", "newMax must be greater than newMin");
var oldMax = columnValues.Max();
var oldMin = columnValues.Min();
Part1 = (newMax - newMin) / (oldMax - oldMin);
Part2 = newMin + (oldMin * (newMin - newMax) / (oldMax - oldMin));
}
/// <summary>
/// Use this ctor for previously-trained scalers with known constants.
/// </summary>
public MinMaxColumnSpec(double part1, double part2)
{
Part1 = part1;
Part2 = part2;
}
public double Scale(double x) => (x * Part1) + Part2;
}

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