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;
}
Related
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%)
Let's say I have an axis-aligned box. I also have a location outside that box.
How do I get the point on the boundaries of the box that is along the line from the center of the box to that point? (And thus, know how far the box extends in that direction).
EDIT: preferably, if the point is inside the box, it should just return the point... but that's something I could easily calculate myself.
2D diagram (Though the answer will preferably work in 3D as well):
point3d getIntersection(point3d topfrontleft, point3d backbottomright, point3d externpt)
{
//find the middle
point3d middle(topfrontleft/2+backbottomright/2);
//slide box and source as if the "middle" was at the origin
topfrontleft -= middle;
backbottomright-= middle;
externpt-= middle;
//scale source as if the box is the unit square
externpt/= topfrontleft;
//find the largest magnitude of the source offset
point3d mag = abs(externpt);
auto max = std::max(mag.x,std::max(mag.y,mag.z));
//if it's inside the box, don't scale
if (max < 1)
max = 1;
//scale the source so that it touches the box
externpt/= max;
//then scale and slide that touching point back to original coordinates
externpt= externpt* topfrontleft + middle;
return externpt;
}
http://coliru.stacked-crooked.com/a/9d9504964bc650bb
I'm not experienced with 3d math, so there's probably faster and more effective ways. No idea.
As a bonus, this algorithm doesn't make any reference to the number of dimensions except for finding the largest magnitude, so should work in both 2d, 3d, and any other theoretical number of dimensions.
Here is some code that does what I said in comments modified for rectangular boxes.
#include <stdio.h>
#include <math.h>
// Find intersection p between line A->B and box.
// Point A must be the box center.
// The box is [x_left, y_bottom, x_right, y_top].
void find_box_intersect(double *box, double *a, double *b, double *p)
{
double dx = b[0] - a[0];
double dy = b[1] - a[1];
if (fabs(dx * (box[3] - box[1])) > fabs(dy * (box[2] - box[0]))) {
p[0] = dx > 0 ? box[2] : box[0];
p[1] = a[1] + dy * (p[0] - a[0]) / dx;
} else {
p[1] = dy > 0 ? box[3] : box[1];
p[0] = a[0] + dx * (p[1] - a[1]) / dy;
}
}
int main(void) {
double box[] = { 1, 2,
2, 4 };
double p[2], a[] = { 0.5 * (box[0] + box[2]),
0.5 * (box[1] + box[3]) };
int i, n = 16;
for (i = 0; i < n; i++) {
double theta = 2 * 3.14159 * i / n;
double b[] = { a[0] + cos(theta), a[1] + sin(theta) };
find_box_intersect(box, a, b, p);
printf("%.2f, %.2f\n", p[0], p[1]);
}
return 0;
}
The 3d generalization of this is straightforward. Rather than the if statement that determines only 2 cases, there will be a 3 case if-else chain that determines 3: left-right, top-bottom, front-back.
Output:
2.00, 3.00
2.00, 3.21
2.00, 3.50
1.91, 4.00
1.50, 4.00
1.09, 4.00
1.00, 3.50
1.00, 3.21
1.00, 3.00
1.00, 2.79
1.00, 2.50
1.09, 2.00
1.50, 2.00
1.91, 2.00
2.00, 2.50
2.00, 2.79
Short answer: Intersect the line with the (right) border of the box.
Longer answer:
x is easy: It's at the right border of the box.
for y, solve this: y/line_height = (line_width - box_width/2) / line_width
then add the y of the line lower point
This is assuming the line intersects the right border, as in your picture.
To figure which border the line intersects, compare the ratio of line_height/line_width, and the signs, to the ratio of box_height/box_width.
If the box is square and you're only interested in the radius, and not the intersection point, you can throw away the signs of line_width and line_height, sort them, and just solve one case.
If the box is not square, you can still throw away the signs, but need to compare the ratios to pick one of two remaining cases (in 2D).
Assuming that you know the length and width of the rect, if the angle is pointing left or right, then x is known, so you only need to solve for y (r * sin(angle)); if the angle is pointing up or down, then y is known so you solve for x (r * cos(angle)).
// where the center of the rect = 0, 0 and angles are in degrees 0..360
// determine the 45's first
if (angle in (45, 135, 225, 315)) {
return { x:cos(angle) * (width / 2), y:sin(angle) * (height / 2) };
}
if (angle < 45 || angle > 315) {
// pointing right
return { x:(width / 2), y:sin(angle) * (height / 2) };
}
if (angle > 45 && angle < 135) {
// pointing up
return { x:cos(angle) * (width / 2), y:(height / 2) };
}
if (angle > 135 && angle < 225) {
// pointing left
return { x:(width / -2), y:sin(angle) * (height / 2) };
}
if (angle > 225 && angle < 315) {
// pointing down
return { x:cos( angle ) * (width / 2), y:(height / -2) };
}
There are undoubtedly more elegant mathematical solutions to this problem, but this one works for me--understandable, testable, etc.
I've implemented the spiral GLSL shader described in this question in HLSL, but the results are not the same. I think it's because of the mod function in GLSL that I've translated to fmod in HLSL. I suspect that this problem only happens when we have negative numbers in the input of the fmod function.
I've tried replacing the call to mod by a call to a function that I've made which does what is described in the GLSL documentation and it works:
mod returns the value of x modulo y. This is computed as x - y * floor(x/y).
The working code I use instead of fmod is:
float mod(float x, float y)
{
return x - y * floor(x/y)
}
By contrast to GLSL mod, MSDN says the HLSL fmod function does this:
The floating-point remainder is calculated such that x = i * y + f, where i is an integer, f has the same sign as x, and the absolute value of f is less than the absolute value of y.
I've used an HLSL to GLSL converter, and the fmod function is translated as mod. However, I don't know if I can assume that mod translates to fmod.
Questions
What are the differences between GLSL mod and HLSLfmod?
How can I translate MSDN's cryptic description of fmod to a pseudo-code implementation?
GLSL Shader
uniform float time;
uniform vec2 resolution;
uniform vec2 aspect;
void main( void ) {
vec2 position = -aspect.xy + 2.0 * gl_FragCoord.xy / resolution.xy * aspect.xy;
float angle = 0.0 ;
float radius = length(position) ;
if (position.x != 0.0 && position.y != 0.0){
angle = degrees(atan(position.y,position.x)) ;
}
float amod = mod(angle+30.0*time-120.0*log(radius), 30.0) ;
if (amod<15.0){
gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
} else{
gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 );
}
}
HLSL Shader
struct Psl_VertexShaderInput
{
float3 pos : POSITION;
};
struct Psl_VertexShaderOutput
{
float4 pos : POSITION;
};
struct Psl_PixelShaderOutput
{
float4 Output0 : COLOR0;
};
float3 psl_positionOffset;
float2 psl_dimension;
Psl_VertexShaderOutput Psl_VertexShaderFunction(Psl_VertexShaderInput psl_input)
{
Psl_VertexShaderOutput psl_output = (Psl_VertexShaderOutput)0;
psl_output.pos = float4(psl_input.pos + psl_positionOffset, 1);
return psl_output;
}
float time : TIME;
float2 resolution : DIMENSION;
Psl_PixelShaderOutput Psl_PixelShaderFunction(float2 pos : VPOS)
{
Psl_PixelShaderOutput psl_output = (Psl_PixelShaderOutput)0;
float2 aspect = float2(resolution.x / resolution.y, 1.0);
float2 position = -aspect.xy + 2.0 * pos.xy / resolution.xy * aspect.xy;
float angle = 0.0;
float radius = length(position);
if (position.x != 0.0 && position.y != 0.0)
{
angle = degrees(atan2(position.y, position.x));
}
float amod = fmod((angle + 30.0 * time - 120.0 * log(radius)), 30.0);
if (amod < 15.0)
{
psl_output.Output0 = float4(0.0, 0.0, 0.0, 1.0);
return psl_output;
}
else
{
psl_output.Output0 = float4(1.0, 1.0, 1.0, 1.0);
return psl_output;
}
}
technique Default
{
pass P0
{
VertexShader = compile vs_3_0 Psl_VertexShaderFunction();
PixelShader = compile ps_3_0 Psl_PixelShaderFunction();
}
}
As you've noted, they're different. The GLSL mod will always have the same sign as y rather than x. Otherwise it's the same -- a value f such that x = i*y + f where i is an integer and |f| < |y|. If you're trying to make a repeating pattern of some kind, the GLSL mod is generally what you want.
For comparison, the HLSL fmod is equivalent to x - y * trunc(x/y). They're the same when x/y is positive, different when x/y is negative.
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
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.