Convert from relative luminance to HSL - hsl

Given a certain color in HSL (let's say hsl(74,64%,59%)), I want to calculate what darker shade (with the same h and s values) gives me enough contrast to satisfy W3C color contrast requirements.
There are formulas to convert HSL to RGB (for example https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB) and to calculate the relative luminance from that RGB (for example https://www.w3.org/TR/WCAG20/#relativeluminancedef). Based on the color contrast formula (https://www.w3.org/TR/WCAG20/#contrast-ratiodef) I can calculate what the relative luminance for my other color should be.
However, then I'm stuck. I find no way to calculate back from a given relative luminance, to an HSL color with given h and s.
Using tools like https://contrast-ratio.com/ I can just decrease the lightness until it satisfies the requirements, but I would like a formula (preferably in JavaScript) to do this calculation for a large selection of colors.
(I am currently using a binary search method to find the closest value, by testing many conversions from HSL to RGB to relative lightness, but that is quite intensive plus I wonder if the conversion to RGB in between introduces inaccuracies.)

Hope this is what you need
Using the formulas in this SO answer, and below:
// Relative luminance calculations
function adjustGamma(p) {
if (p <= 0.03928) {
return p / 12.92;
} else {
return Math.pow( ( p + 0.055 ) / 1.055, 2.4 );
}
}
function relativeLuminance(rgb) {
const r = adjustGamma( rgb[0] / 255 );
const g = adjustGamma( rgb[1] / 255 );
const b = adjustGamma( rgb[2] / 255 );
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
// Contrast calculations
function contrastRatio(a,b) {
const ratio = (a + 0.05) / (b + 0.05);
return ratio >= 1 ? ratio : 1 / ratio;
}
// Loop for correct lightness
function rgbFromHslContrast(h, s, l1, ratio) {
var inc = -0.01;
var l2 = ( ( l1 + 0.05 ) / ratio - 0.05 );
if (l2 < 0) {
l2 = ( ratio * ( l1 + 0.05 ) - 0.05 );
inc = -inc;
}
while (contrastRatio(l1, relativeLuminance(hslToRgb(h, s, l2))) < ratio) {
l2 += inc;
}
return hslToRgb(h, s, l2);
}
The function you want to call is:
const originalHslAsRgb = hslToRgb(0.2, 0.2, 0.2);
const l1 = relativeLuminance(originalHslAsRgb);
const contrastRgb = rgbFromHslContrast(0.2, 0.2, l1, 3.5) // 3.5 is minimum contrast factor we target for..
// [139, 149, 100]
// equivalent to hsl(72, 20%, 53%)

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
}

implementing easing function

I'm trying to port and implement an easing function I found
EDIT
: I pasted in the wrong easing function, Sorry! Here is the correct one:
Math.easeOutQuart = function (t, b, c, d) {
t /= d;
t--;
return -c * (t*t*t*t - 1) + b;
};
The language i'm using is not Flash or Actionscript. Here is my code:
ease:{outquart:{function(t as float,b as float,c as float,d as float) as float
t=t/d
t=t-1
return -c * (t*t*t*t - 1) + b
end function}}
I'm calling the function in a loop with:
EDIT2 - the calling function.
m.move is set to 1 or -1 for direction to move, or -5 +5 to move by 5 lengths.
setspritemoves is called as often as possible, currently it is as fast as the system can call, but I could trigger the call on a millisecond timer.
setspritemoves:function()
if m.move=1 then
m.duration=1
if m.ishd then
for i=0 to m.spriteposx.count()-1
m.moveto[i]=m.spriteposx[i]+m.move*324
next i
else
for i=0 to m.spriteposx.count()-1
m.moveto[i]=m.spriteposx[i]+m.move*224
next i
end if
else if m.move=5 then
m.duration=5
if m.ishd then
for i=0 to m.spriteposx.count()-1
m.moveto[i]=m.spriteposx[i]+m.move*324
next i
else
for i=0 to m.spriteposx.count()-1
m.moveto[i]=m.spriteposx[i]+m.move*224
next i
end if
else if m.move=-1 then
m.duration=1
if m.ishd then
for i=0 to m.spriteposx.count()-1
m.moveto[i]=m.spriteposx[i]-m.move*324
next i
else
for i=0 to m.spriteposx.count()-1
m.moveto[i]=m.spriteposx[i]-m.move*224
next i
end if
else if m.move=-5 then
m.duration=5
if m.ishd then
for i=0 to m.spriteposx.count()-1
m.moveto[i]=m.spriteposx[i]-m.move*324
next i
else
for i=0 to m.spriteposx.count()-1
m.moveto[i]=m.spriteposx[i]-m.move*224
next i
end if
end if
end function
m.moveto[i] is the destination x coordinate, m.time is an integer I increment, m.duration is what I assume to be the amount of time I want the change to take to complete, m.spriteposx is the current position of the object I'm moving. [i] is the current sprite.
What should the increment value be for time what should the duration be, if I want to move 345 pixels in 1/2 second?
In all my experiments, I either overshoot by a huge factor, or only move a few pixels.
currently m.time is incremented by 1 every iteration, and m.duration is 100. I"ve tried all kinds of values and none seems to work consistently.
Why haven't you copied the logic across 1-1? The tween is a simple algorithm, it simply maps co-ordinates from b to b+c in a quartic fashion, i.e. b + c*t^4 where t gets values in the interval [0,1]. You can see by substitution that when t=0 the value is the initial value, b, and as t->1 the position is the required b+c.
That's the reason for the line t \= d, so d is an arbitrary duration and t, the time passed since the beginning of the animation gets a value in the aforementioned range. But you've done t=t-1 and taken negatives, etc. Why?
For example, moving 345px in 0.5s, you would have an initial position, b and c=345 assuming px is the units of measure. d=0.5 and you split the animation into intervals of a length of your choosing (depending on the power of the machine that will run the animation. Mobile devices aren't as powerful as desktops, so you choose a reasonable framerate under the circumstances). Let's say we choose 24 fps, so we split the interval into 0.5*24 = 12 frames, and call the function once every 1/24th of a second, each time with t taking values of 1/24, 2/24, etc. If it's more comfortable to work not in seconds but in frames, then d=12 and t takes values 1,2,...,12. The calculations are the same either way.
Here's a nice example (click the box to run the demo), feel free to fiddle with the values:
http://jsfiddle.net/nKhxw/
Bezier functions
Borrowed from http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
/**
* KeySpline - use bezier curve for transition easing function
* is inspired from Firefox's nsSMILKeySpline.cpp
* Usage:
* var spline = new KeySpline(0.25, 0.1, 0.25, 1.0)
* spline.get(x) => returns the easing value | x must be in [0, 1] range
*/
function KeySpline (mX1, mY1, mX2, mY2) {
this.get = function(aX) {
if (mX1 == mY1 && mX2 == mY2) return aX; // linear
return CalcBezier(GetTForX(aX), mY1, mY2);
}
function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
function C(aA1) { return 3.0 * aA1; }
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
function CalcBezier(aT, aA1, aA2) {
return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
}
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
function GetSlope(aT, aA1, aA2) {
return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}
function GetTForX(aX) {
// Newton raphson iteration
var aGuessT = aX;
for (var i = 0; i < 4; ++i) {
var currentSlope = GetSlope(aGuessT, mX1, mX2);
if (currentSlope == 0.0) return aGuessT;
var currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
aGuessT -= currentX / currentSlope;
}
return aGuessT;
}
}
Aliases for common curves:
{
"ease": [0.25, 0.1, 0.25, 1.0],
"linear": [0.00, 0.0, 1.00, 1.0],
"ease-in": [0.42, 0.0, 1.00, 1.0],
"ease-out": [0.00, 0.0, 0.58, 1.0],
"ease-in-out": [0.42, 0.0, 0.58, 1.0]
}
Should be easy to make your own curves...
Thank you, Jonny!
Here is how to implement Bezier easing functions: C or Objective-C for iOS
// APPLE ORIGINAL TIMINGS:
// linear (0.00, 0.00), (0.00, 0.00), (1.00, 1.00), (1.00, 1.00)
// easeIn (0.00, 0.00), (0.42, 0.00), (1.00, 1.00), (1.00, 1.00)
// easeOut (0.00, 0.00), (0.00, 0.00), (0.58, 1.00), (1.00, 1.00)
// easeInEaseOut (0.00, 0.00), (0.42, 0.00), (0.58, 1.00), (1.00, 1.00)
// default (0.00, 0.00), (0.25, 0.10), (0.25, 1.00), (1.00, 1.00)
+(double)defaultEase_Linear:(double)t
{
return t;
}
// Замедление в начале
+(double)defaultEase_In:(double)t
{
return [AnimationMath easeBezier_t:t
point0_x:0
point0_y:0
point1_x:0.42
point1_y:0
point2_x:1
point2_y:1
point3_x:1
point3_y:1];
}
// Замедление в конце
+(double)defaultEase_Out:(double)t
{
return [AnimationMath easeBezier_t:t
point0_x:0
point0_y:0
point1_x:0
point1_y:0
point2_x:0.58
point2_y:1
point3_x:1
point3_y:1];
}
+(double)defaultEase_InOut:(double)t
{
return [AnimationMath easeBezier_t:t
point0_x:0
point0_y:0
point1_x:0.42
point1_y:0
point2_x:0.58
point2_y:1
point3_x:1
point3_y:1];
}
+(double)defaultEase_default:(double)t
{
return [AnimationMath easeBezier_t:t
point0_x:0
point0_y:0
point1_x:0.25
point1_y:0.1
point2_x:0.25
point2_y:1.0
point3_x:1
point3_y:1];
}
// For *better understanding* there is p1 and p2, because it is a Bezier curve from 0,0 to 1,0. So, you can remove p1 and p2 from this method, it is just for better understanding what's going on here
double ease_bezier_A(double aA1, double aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
double ease_bezier_B(double aA1, double aA2) { return 3.0 * aA2 - 6.0 * aA1; }
double ease_bezier_C(double aA1) { return 3.0 * aA1; }
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
double ease_bezier_calc(double aT, double aA1, double aA2) {
return ((ease_bezier_A(aA1, aA2)*aT + ease_bezier_B(aA1, aA2))*aT + ease_bezier_C(aA1))*aT;
}
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
double ease_bezier_get_slope(double aT, double aA1, double aA2) {
return 3.0 * ease_bezier_A(aA1, aA2)*aT*aT + 2.0 * ease_bezier_B(aA1, aA2) * aT + ease_bezier_C(aA1);
}
double ease_bezier_get_t_for_x(double aX, double mX1, double mX2) {
// Newton raphson iteration
double aGuessT = aX;
for (int i = 0; i < 4; ++i) {
double currentSlope = ease_bezier_get_slope(aGuessT, mX1, mX2);
if (currentSlope == 0.0) return aGuessT;
double currentX = ease_bezier_calc(aGuessT, mX1, mX2) - aX;
aGuessT -= currentX / currentSlope;
}
return aGuessT;
}
// Objective-C
// For ***better understanding*** there is p1 and p2, because it is a Bezier curve from 0,0 to 1,0. So, you can remove p1 and p2 from this method, it is just for better understanding what's going on here
// p1_x always = 0
// p1_y always = 0
// p2_x always = 1.0
// p2_y always = 1.0
+(double)easeBezier_t:(double)t
point0_x:(double)point0_x point0_y:(double)point0_y
point1_x:(double)point1_x point1_y:(double)point1_y
point2_x:(double)point2_x point2_y:(double)point2_y
point3_x:(double)point3_x point3_y:(double)point3_y
{
if (point0_x != 0 || point0_y != 0 || point3_x != 1 || point3_y != 1) {
[NSException raise:#"Error! Your bezier is wrong!!!" format:#""];
}
double v = ease_bezier_calc(ease_bezier_get_t_for_x(t, point1_x, point2_x), point1_y, point2_y);
return v;
}

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;
}

Interpolating values between interval, interpolation as per Bezier curve

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.

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