I'm struggling with creating a function, that interpolates one range to another. I already have a functioning linear interpolation, but getting more 'curved' interpolations, I am stumped.
F.ex. I want to interpolate the range [0,100] to [0,1000] in a way, so target-values near 0 is more likely than target-values near 1000 (or vice-versa).
One approach I got working was using logarithmic interpolation, which has some annoying drawbacks:
I used a crude hack to deal with negative numbers (f.ex. [-70,50]), by offsetting the range before calculating
The 'slope' is not adjustable (f.ex. if I want the target-value to be just a bit more likely or a lot more likely than a linear interpolation)
function logPol (value, s1, s2, t1, t2) {
var f = (value - s1) / ((value - s1) + (s2 - value));
var add = 0;
if(t1 <= 0 || t2 <= 0) {
add = t1 >= t2? 2 * Math.abs(t2) + 1 : 2 * Math.abs(t1) + 1;
t1 += add;
t2 += add;
}
var interpolated = Math.pow(t2,f) * Math.pow(t1, 1-f) - add;
return interpolated;
};
I've read a lot of articles on quadratic equations, thinking that would solve my problem, but never ended up with a working solution. If I need to explain something further, please let me know.
After reading a ton of different approaches, I finally solved my problem, and ended up using Bezier curves to handle my interpolation. Here's my resulting function:
/**
* Returns a bezier interpolated value, using the given ranges
* #param {number} value Value to be interpolated
* #param {number} s1 Source range start
* #param {number} s2 Source range end
* #param {number} t1 Target range start
* #param {number} t2 Target range end
* #param {number} [slope] Weight of the curve (0.5 = linear, 0.1 = weighted near target start, 0.9 = weighted near target end)
* #returns {number} Interpolated value
*/
var interpolate = function (value, s1, s2, t1, t2, slope) {
//Default to linear interpolation
slope = slope || 0.5;
//If the value is out of the source range, floor to min/max target values
if(value < Math.min(s1, s2)) {
return Math.min(s1, s2) === s1 ? t1 : t2;
}
if(value > Math.max(s1, s2)) {
return Math.max(s1, s2) === s1 ? t1 : t2;
}
//Reverse the value, to make it correspond to the target range (this is a side-effect of the bezier calculation)
value = s2-value;
var C1 = {x: s1, y:t1}; //Start of bezier curve
var C3 = {x: s2, y:t2}; //End of bezier curve
var C2 = { //Control point
x: C3.x,
y: C1.y + Math.abs(slope) * (C3.y - C1.y)
};
//Find out how far the value is on the curve
var percent = value / (C3.x-C1.x);
return C1.y*b1(percent) + C2.y*b2(percent) + C3.y*b3(percent);
function b1(t) { return t*t }
function b2(t) { return 2*t*(1 - t) }
function b3(t) { return (1 - t)*(1 - t) }
};
Related
Forgive me for the long code example, but I couldn't figure out how to properly explain my question with any less code:
let c = document.querySelector("canvas");
let ctx = c.getContext("2d");
class BezierCurve {
constructor(x1, y1, cpX, cpY, x2, y2) {
this.f = 0;
this.x1 = x1;
this.y1 = y1;
this.cpX = cpX;
this.cpY = cpY;
this.x2 = x2;
this.y2 = y2;
this.pointCache = this.calcPoints();
}
calcX(t) { return (1 - t) * (1 - t) * this.x1 + 2 * (1 - t) * t * this.cpX + t * t * this.x2; }
calcY(t) { return (1 - t) * (1 - t) * this.y1 + 2 * (1 - t) * t * this.cpY + t * t * this.y2; }
calcPoints() {
const step = 0.001, segments = [];
for (let i = 0; i <= 1 - step; i += step) {
let dx = this.calcX(i) - this.calcX(i + step);
let dy = this.calcY(i) - this.calcY(i + step);
segments.push(Math.sqrt(dx * dx + dy * dy));
}
const len = segments.reduce((a, c) => a + c, 0);
let result = [], l = 0, co = 0;
for (let i = 0; i < segments.length; i++) {
l += segments[i];
co += step;
result.push({ t: l / len, co });
}
return result;
}
draw() {
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
ctx.quadraticCurveTo(this.cpX, this.cpY, this.x2, this.y2);
ctx.stroke();
}
tick(amount = 0.001) {
this.f = this.f < 1 ? this.f + amount : 0;
}
}
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
}
let a = new BezierCurve(25, 25, 80, 250, 100, 50);
let b = new BezierCurve(225, 25, 280, 250, 300, 50);
function draw(curve, fraction) {
let x = curve.calcX(fraction);
let y = curve.calcY(fraction);
curve.draw();
drawCircle(x, y, 5);
curve.tick();
}
// Inefficient but using this instead of binary search just to save space in code example
function findClosestNumInArray(arr, goal) {
return arr.reduce((prev, cur) => Math.abs(cur.t - goal) < Math.abs(prev.t - goal) ? cur : prev);
}
function drawLoop(elapsed) {
c.width = 600;
c.height = 600;
draw(a, a.f);
let closest = findClosestNumInArray(b.pointCache, b.f).co;
draw(b, closest);
requestAnimationFrame(drawLoop);
}
drawLoop(0);
<canvas></canvas>
Okay, so, to explain what's going on: if you hit Run code snippet you'll see that there are two curves, which I'll refer to as a (left one) and b (right one).
You may notice that the dot moving along a's curve starts off fast, then slows down around the curve, and then speeds up again. This is despite the fractional part being incremented by a constant 0.001 each frame.
The dot for b on the other hand moves at a constant velocity throughout the entire iteration. This is because for b I use the pointCache mapping that I precompute for the curve. This function calcPoints generates a mapping such that the input fractional component t is associated with the "proper" actual percentage along the curve co.
Anyways, this all works, but my issue is that the precomputation calcPoints is expensive, and referencing a lookup table to find the actual fractional part along the line for a percentage is inexact and requires significant memory usage. I was wondering if there was a better way.
What I'm looking for is a way to do something like curve.calcX(0.5) and actually get the 50% mark along the curve. Because currently the existing equation does not do this, and I instead have to do this costly workaround.
We can try to modify your method to be a bit more efficient. It is still not the exact solution you hope for but it might do the trick.
Instead of repeatedly evaluating the Bézier curve at parameter values differing by 0.001 (where you do not reuse the computation from the previous step) we could use the idea of subdivision. Do you know De Casteljau's algorithm? It not only evaluates the Bézier curve at a given parameter t, it also provides you with means to subdivide the curve in two: one Bézier curve that equals the original curve on the interval [0, t] and another one that equals the original curve on [t, 1]. Their control polygons are a much better approximation of the curves than the original control polygon.
So, you would proceed as follows:
Use De Casteljau's algorithm to subdivide the curve at t=0.5.
Use De Casteljau's algorithm to subdivide the first segment at t=0.25.
Use De Casteljau's algorithm to subdivide the second segment at t=0.75.
Proceed recursively in the same manner until prescribed depth. This depends on the precision you would like to achieve.
The control polygons of these segments will be your (piecewise linear) approximation of the original Bézier curve. Either use them to precompute the parameters as you have done so far; or plot this approximation directly instead of using quadraticCurveTo with the original curve. Generating this approximation should be much faster than your procedure.
You can read more about this idea in Sections 3.3, 3.4 and 3.5 of Prautzsch, Boehm and Paluszny: Bézier and B-spline techniques. They also provide an estimate how quickly does this procedure converge to the original curve.
Not totally sure this will work, but are you aware of Horner's Scheme for plotting Bezier points?
/***************************************************************************
//
// This routine plots out a bezier curve, with multiple calls to hornbez()
//
//***************************************************************************
function bezierCalculate(context, NumberOfDots, color, dotSize) {
// This routine uses Horner's Scheme to draw entire Bezier Line...
for (var t = 0.0; t < 1.0001; t = t + 1.0 / NumberOfDots) {
xTemp = hornbez(numberOfControlPoints - 1, "x", t);
yTemp = hornbez(numberOfControlPoints - 1, "y", t);
drawDot(context, xTemp, yTemp, dotSize, color);
}
}
//***************************************************************************
//
// This routine uses Horner's scheme to compute one coordinate
// value of a Bezier curve. Has to be called
// for each coordinate (x,y, and/or z) of a control polygon.
// See Farin, pg 59,60. Note: This technique is also called
// "nested multiplication".
// Input: degree: degree of curve.
// coeff: array with coefficients of curve.
// t: parameter value.
// Output: coordinate value.
//
//***************************************************************************
function hornbez(degree, xORy, t) {
var i;
var n_choose_i; /* shouldn't be too large! */
var fact, t1, aux;
t1 = 1 - t;
fact = 1;
n_choose_i = 1;
var aux = FrameControlPt[0][xORy] * t1;
/* starting the evaluation loop */
for (i = 1; i < degree; i++) {
fact = fact * t;
n_choose_i = n_choose_i * (degree - i + 1) / i; /* always int! */
aux = (aux + fact * n_choose_i * FrameControlPt[i][xORy]) * t1;
}
aux = aux + fact * t * FrameControlPt[degree][xORy];
return aux;
}
Not sure exactly where you are going here, but here's a reference of something I wrote a while ago... And for the contents of just the Bezier iframe, see this... My implied question? Is Bezier the right curve for you?
I'm a beginner and I made a function to calculate the length of a semi-circular infinite snake figure. I took in two arguments; one of the radius the initial circle, and the next to be the precision (which is just the number of semi-circles).
Here's a diagram of the snake I'm talking about.
Here's what I wrote:
function snake(radius, precision) {
var pi = 3.14159265359
var exp = 1 - precision;
var sub = Math.pow(2, exp);
var product = 2 - sub;
var length = pi * radius * product
return length
}
I'm noticing that at one point the precision doesn't matter when I go really high as the value it return is the same. Is there a way to make it more precise?
You may use the constant Math.PI instead of your pi variable.
Feels like Number.toPrecision() is what you are looking for.
Below is slightly cleaned up version of your code snippet.
For the return value I'm using length.toPrecision(50):
function snake(radius, precision) {
const exp = 1 - precision;
const sub = Math.pow(2, exp);
const product = 2 - sub;
const length = Math.PI * radius * product;
return length.toPrecision(50);
}
console.log(snake(5, 55));
Yo can find out more about toPrecision() here.
The max precision value is 100.
This will be my method,
Length can be get by sum of the below series according to the screen shot,
PI X R (1 + 1/2 + 1/4 + ...)
S(n) = PIxR(1-0.5^n)/(1-0.5)
So the JavaScript function is,
function length(r, n) {
const ln = Math.PI * r * (1 - Math.pow(0.5, n))/(1 - 0.5);
return ln.toPrecision(50);
}
Consider the given image of the soccer field
As you can see in the image the various ball movements, some of them are curved(i.e. in case of (1), (2), (3) in the image)) and some may not(i.e a line(4)),
so I need to find the intersection points of ball path with goalline and sideline. Sometimes the input may not be a curve(i.e a line) like in case of (4) given image
I have written a program, I have no clue what is wrong - is this right way to solve this kind of program.
if yes then, how to convert bezier curve into an equation for better solving
considering the given as
beizer curve eqaution -> a(x*x) + b*x + c
and line segment equation -> y = y1 + m(x-x1)
//maxCurvedPoint is the topmost of the curve
var getIntersectionPoint = function (room, ballFromPosition, ballToPosition, maxCurvePoint)
{
var linepoints = [[m1,n1], [m2, n2], [m3, n3], [m4, n4]];
//converting three points(ballFromPosition, maxCurvePoint, ballToPosition) into the quadratic equation (Bezier curve) --(1)
//getting the equation of the line segment using the linepoints --(2)
//equating (1) and (2) and getting a quadratic equation and solving and finding intersection points
return intersectionPoint;
}
// solves //(-b(+-)sqrt(b*b - 4ac)/2ac)
function solve(a, b, c)
{
//check curve intersects line or not
if((Math.pow(b, 2) - (4 * a * c)) >= 0)
{
result1 = (-1 * b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
result2 = (-1 * b - Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
return [result1, result2];
}
return [];
}
Can anyone help me with this? Also is the most curve point can be called vertex of the curve?
I find it easier to work with vector equations since the algebra will be rotation-invariant (hence you don't have to re-write the code to deal with e.g. a "horizontal" parabola).
1. Curve representation + Intersection test
Consider a quadratic Bezier curve with endpoints A, C, control point B and parameter t:
And an infinite line with source O, direction D and parameter s:
Equating P and R give a pair of quadratic simultaneous equations, which can be re-arranged to eliminate s and find the parabolic parameter t:
Solve this quadratic equation for t, and only accept real roots in the range [0, 1]. This ensures that any intersection point is always on the segment itself.
2. Dealing with line segments
You can also restrict the intersection point to a line segment, by computing s from t using the equations above, and limiting its value - which equals the distance along the line from O if D is normalized.
3. Computing the control point B
Note that a general value of the control point B will not give a symmetrical parabola. To compute B for a general symmetric curve:
Defining the variables:
M: midpoint of AB
n: clockwise normal to the direction AC
q: signed bulge distance - absolute value is the distance from M to the midpoint of the curve
k: signed distance from M to B
A surprisingly simple result.
4. Sample C# (-style) code
public static Vector2[] computeIntersection
(
Vector2 A, Vector2 C, double q, // parabola segment
Vector2 O, Vector2 P // line segment
)
{
// quadratic solve
double[] solve(double a, double b, double c)
{
double d = b * b - 4.0 * a * c;
if (d < 0.0) // imaginary roots - no intersection at all
return null;
if (d > 0.0) // two distinct real roots
{
double sd = Math.Sqrt(d);
return new double[2] { (-b + sd) / (2.0 * a),
(-b - sd) / (2.0 * a) };
}
else // only one (line segment is tangent to curve)
{
return new double[1] { -b / (2.0 * a) };
}
}
// cross product (in-case undefined)
double cross(Vector2 i, Vector2 j)
{
return i.x * j.y - i.y * j.x;
}
// validity check for t and s
bool valid(double v)
{
return (v >= 0.0) && (v <= 1.0);
}
// compute control point B
Vector2 E = C - A;
Vector2 M = 0.5 * (A + C);
Vector2 N = (new Vector2(E.y, -E.x)).normalize();
Vector2 B = M + (2.0 * q) * N;
// solving for t
Vector2 D = P - O;
bool useX = Math.Abs(D.X) > Math.Abs(D.Y);
double[] T = solve(cross(A + C - 2.0 * B, D),
cross(B - A, D) * 2.0,
cross(A - O, D));
if (T == null) return null;
Vector2[] I = new Vector2[2];
int c = 0;
for (int i = 0; i < T.Length; i++)
{
// check if t is within curve range
double t = T[i];
if (!valid(t)) continue;
// solve for s and check if is within line range
double u = (1 - t) * (1 - t);
double v = 2 * t * (1 - t);
double w = t * t;
double s = useX ? ((u * A.X + v * B.X + w * C.X - O.X) / D.X)
: ((u * A.Y + v * B.Y + w * C.Y - O.Y) / D.Y);
if (!valid(s)) continue;
// compute the corresponding intersection point
I[c++] = O + s * D;
}
// only return valid solutions
if (c == 0) return null;
Array.Resize(ref I, c);
return I;
}
If you translate and rotate all the endpoints in such a way that the line segment becomes (0, 0)-(d, 0), the problem simplifies.
Let the control points be (Xk, Yk), k= 0, 1, 2. The intersections with the axis X are obtained by solving for t the quadratic equation
Y0 (1-t)² + 2 Y1 t(1-t) + Y2 t² = 0.
The corresponding abscissas are given by
X0 (1-t)² + 2 X1 t(1-t) + X2 t² = 0.
You can check if these belong to the interval [0, d]. Then apply the reverse rotation and translation.
Addendum: intersection of two quadratic Beziers
The vector equation of one of the curves can be written
P = P0 (1 - t)² + 2 P1 t (1 - t) + P2 t²
= P0 + 2 (P1 - P0) t + (P0 - 2 P1 + P2) t².
If you apply the affine change of coordinates such that the reference frame becomes (P0, P1 - P0, P0 - 2 P1 + P2), the equation simplifies to
X = 2t
Y = t²
which is the implicit equation
X² = 4Y.
Now by applying the same transform to the control points of the second curve and plugging the parametric equations in the above, you get a quartic equation in t (square of a quadratic polynomial on one side, quadratic polynomial on the other).
There are closed-form formulas for the roots of a quartic equation, and there can be four of them. After selecting the real roots in [0, 1], you evaluate the t parameter of the first curve and also check membership in [0, 1].
Don't forget to restore the original coordinates.
I'm modeling the resonance effect with html5 canvas to animate spring when its reach the resonance.
Also got jquery ui slider (max ranged) that changes frequency (w) of the oscillations dynamically during the animation. The problem is when its changed, for some reason sine wave brokes at some points and the animation is not smooth. This is only happens when changing frequency, with amplitude its much better.
my main function to render each frame is this:
function doSways() {
var spring = springs[0],
a = 0,
A = params.standParams.A,
w = params.standParams.w;
animIntervalId = setInterval(function() {
ctx.clearRect(0, 0, cnvW, cnvH);
A = params.standParams.A;
w = params.standParams.w;
/*if (w < params.standParams.w) { // with this expression and commented <w = params.standParams.w> it works a bit smoother but still some issues can be noticed, for example sharp increases just after the new change of the frequency (w) on the slider
w += 0.01;
}*/
stand.y = A*Math.sin(a*degToRad*w) + offsetY;
stand.draw();
spring.draw(stand.y, A, w);
if (a++ >= 360) { // avoid overflow
a = 0;
}
},
25);
}
here's how I change frequency(w) on the slider and assign it to params.standParams.w
$( "#standfreq_slider" ).slider({
range: "max",
min: 1,
max: 25,
step: 1,
value: 5,
slide: function( event, ui ) {
params.standParams.w = parseInt(ui.value);
}
});
);
That if expression in doSways function kinda work but it casues another problem, I need to know the direction of sliding to determine wether I need to += or -= 0.01..
How to make everything work ideal ?
problem illustration live
jsfiddle
based on the basic formula for sinusoids:
V = V0 + A * sin(2 * pi * f * t + phi)
Where:
V: current value
V0: center value
A: amplitude
f: frequency (Hz)
t: time (seconds)
phi: phase
We want the current value (V) to be the same before and after a frequency change. In order to do that we will need a frequency (f) and phase (phi) before and after the change.
First we can set the first equation equal the the second one:
V0 + A * sin(2 * pi * f1 * t + phi1) = V0 + A * sin(2 * pi * f2 * t + phi2)
do some cancelling:
2 * pi * f1 * t + phi1 = 2 * pi * f2 * t + phi2
solve for the new phase for the final formula:
phi2 = 2 * pi * t * (f1 - f2) + phi1
More mathy version:
edited:
check out modified fiddle: https://jsfiddle.net/potterjm/qy1s8395/1/
Well the pattern i explain below is one i use quite often.
( It might very well have a name - please S.O. people let me know if so-. )
Here's the idea : whenever you need to want a property evolving in a smooth manner, you must distinguish the target value, and the current value. If, for some reason, the target value changes, it will only affect the current value in the way you decided.
To get from the current value to the target value, you have many ways :
• most simple : just get it closer from a given ratio on every tick :
current += ratio * (target-current);
where ratio is in [0, 1], 0.1 might be ok. You see that, with ratio == 1, current == target on first tick.
Beware that this solution is frame-rate dependent, and that you might want to threshold the value to get the very same value at some point, expl :
var threshold = 0.01 ;
if (Math.abs(target-current)<threshold) current = target;
• You can also reach target at a given speed :
var sign = (target - current) > 0 ? 1 : -1;
current += sign * speedToReachTarget * dt;
Now we are not frame-rate dependent (you must handle frame time properly), but you will have 'bouncing' if you don't apply a min/max and a threshold also :
if (Math.abs(target-current)<0.01) {
current = target;
return;
}
var sign = (target - current) > 0 ? 1 : -1;
current += sign * speedToReachTarget * dt;
current = (( sign > 0) ? Math.min : Math.max )( target, current);
• and you might use many other type of interpolation/easing.
I am using the following code to generate a random number:
function getRandomInt (min, max) {
return Math.floor((Math.random() * (max - min + 1)) + min;
}
What I want to do is add a weighting that favours the numbers at the lower end of the range.
I thought about maybe trying to multiply the numbers by 1/cosine.
Would this work and does anyone know how I might go about it?
Many thanks!
First Solution
You need a function which contains the points (0, 0) and (1, 1). For instance: x^n when n > 0
Math.pow(1, n) === 1
And
Math.pow(0, n) === 0
Therefore, you would just change n depending on how you want the weighting to work.
When n = 1 : y === x
When n > 1 : y <= x
When 0 < n < 1 : y >= x
So, if you want lower values to be favored over higher values, simply use n > 1.
var weighted = Math.pow(Math.random(), 2);
Then you can scale the result as usual.
var scaled = Math.floor(weighted * (max - min + 1)) + min;
Other Functions
Likewise, you could use any continuous function which contains the points (0, 0), (1, 1), and has range and domain of [0, 1].
Sine
y = sin(xπ/2)
Cosine
y = 1 - cos(xπ/2)
EDIT: there was a type in the final formula, log(2+log(x)) is incorrect it should have been log(1+log(x))+1, its fixed now.
If you are using logarithmic weighting, using something like
var x = Math.random();
var weighted = x * Math.log(1+x);
would make 0.5 weigh in at around 0.2, but 1 would only weigh in at around 0.69.
Using this
var x = Math.random();
var weighted = x * Math.log(2 + Math.log(x));
would allow 1 to weigh in at 1. So combine them, and this
var x = Math.random();
var weighted = (x <= 0.5) ? x * Math.log(1 + x) : x * Math.log(1 + Math.log(x))+1;
should do the trick