Spline interpolation of animated scale - css

I am trying to understand how Adobe After Effects performs some of interpolations when animating different transformation properties. Properties like position are straightforward. It uses cubic bezier with start,end positions and 2 control tangents. But now I try to implement interpolation of scale property the same way Adobe does. I have two key frames, each contains same values for scale in 2 dimensions. Key1 (100,100) , Key2 (100,100) (in %). Basically,it could be just linear interpolation, but After Effects allows plotting a curve to allow easing even between two similar values. Here is how the curve looks for the points above:
Now,the data from this spline is as follows:
vec2 P0 = vec2(100.0f,100.0f); //start points
vec2 P1 = vec2(100.0f,100.0f); //end point
vec2 T1 = vec2(0.06f,40.12f); //first tangent
vec3 T2 = vec2(0.95f,40.84f); //second tangent
It is clear to me that X value of the tangent contains time in range [0-1],while Y value contains the value of the scale,which is percentage from the default value,which is 100%. I tried to match different types of spline in order to get same interpolated values across 50 frames as in After Effects. I tried:
Catmull- Rom
template <typename T>
T catmullrom(float t, T p0, T p1, T p2, T p3)
{
return (
(p1 * 2.0f) +
(-p0 + p2) * t +
(p0 * 2.0f - p1 * 5.0f + p2 * 4.0f - p3) * t * t +
(-p0 + p1 * 3.0f - p2 * 3.0f + p3) * t * t * t
) * 0.5f;
}
Hermite
template <typename T>
T hermite(float s, T P1, T P2, T T1, T T2)
{
float h1 = 2 * glm::pow(s, 3) - 3 * glm::pow(s, 2) + 1;
float h2 = -2 * glm::pow(s, 3) + 3 * glm::pow(s, 2);
float h3 = glm::pow(s, 3) - 2 * glm::pow(s, 2) + s;
float h4 = glm::pow(s, 3) - glm::pow(s, 2);
T p = P1 * h1 +
P2 * h2 +
T1 * h3 +
T2 * h4;
return p;
}
And even regular Cubic bezier.
template <typename T>
T cubic(float t, T p0, T p1, T p2, T p3)
{
T p = glm::pow3(1.0f - t) * p0 + 3 * glm::pow2(1.0f - t) * t * (p0 + p2) + 3 * (1.0f - t)*glm::pow2(t)*(p1 + p3) + glm::pow3(t) * p1;
return p;
}
int steps = 50;
for (int t = 0; t <= steps; t++)
{
float s = (float)t / (float)steps; // scale s to go from 0 to 1
auto p = cubic(s, P1, P2, T2, T1);
printf("t:%f | %f , %f\n",s,p.x,p.y);
}
I started with Catmull-Rom and Hermite because those define control points on curve,but the results were completely wrong. Then I thought that these two are actually least fit because the tangents in this case have nothing to do with control points on curve. Cubic bezier actually gives correct interpolation in terms of value magnitude change over time. It gave me values starting from 100,with the peak at 130 (as can be seen on the graph),then descending to 100 towards t = 1. But the interpolation steps are completely wrong. Also,only Y value gets interpolated close to the values I expect. I am not a math expert.I was reading about different spline interpolations, but most of the material is completely theoretical. I would like to understand what I am doing wrong here.

Related

Cubic / Quintic linear interpolation

The following is a linear interpolation function:
float lerp (float a, float b, float weight) {
return a + weight * (b - a);
}
The following is a cubic interpolation function:
float cubic (float p1, float p2, float p3, float p4, float weight) {
float m = weight * weight;
float a = p4 - p3 - p1 + p2;
float b = p1 - p2 - a;
float c = p3 - p1;
float d = p2;
return a * weight * m + b * m + c * weight + d;
}
What is the name of the following method?:
float lerp (float a, float b, float weight) {
float v = weight * weigth * (3.0f - 2.0f * weight);
return a + v * (b - a);
}
I've seen some people reference to the above method as "cubic" but, to me, a cubic interpolation needs 4 points.
Also, I've seen the following as well:
float lerp (float a, float b, float weight) {
float v = weight * weight * weight * (weight * (weight * 6.0f - 15.0f) + 10.0f);
return a + v * (b - a);
}
The above code was referenced to as "quintic", but I'm not really sure how can those functions be "cubic" and "quintic" without the necessary additional "points".
What is the name of these operations performed on the "weights"?
float v = weight * weigth * (3.0f - 2.0f * weight);
float v = weight * weight * weight * (weight * (weight * 6.0f - 15.0f) + 10.0f);
"Cubic" is another word for "third degree polynomial".
"Quintic" is another word for "fifth degree polynomial".
The number of parameters does not matter. P(x) = x*x*x is a "cubic" polynomial even though there are no parameters.
What is the name of these operations performed on the "weights"?
...
These functions are called "smoothstep". Smoothstep functions are a family of odd-degree polynomials. The first one is a 3rd degree (or "cubic") smoothstep, the second is a 5th degree (or "quintic") smoothstep.

Formula to create simple MIDI velocity curves

I'm trying to figure out a simple math formula that will allow me to apply various velocity curves to an incoming MIDI value. In the picture below, the starting x,y is (0,0) and ending x,y is (127,127). I'm trying to get a formula with a single variable that will allow me to produce simple expanded or contracted curves always bumping in the middle (by a degree of the variable). My input will be a value between 0 and 127, and my output will always be between 0 and 127. This seems like it should be easy, but my college calculus is escaping me at the moment.
Let's start by marking the four corners as:
S = (0,0)
E = (127,127)
U = (0,127)
V = (127,0)
You are looking for a circle equation which passes through E, S and a third point Z on the line between U and V. Let's mark it Z:
Z(t) = t*U + (1-t)*V
I will use the fact the the perpendicular bisectors of two chords meet at the center.
The bisectors B1 and B2 are:
B1 = (Xz/2 , Yz/2)
B2 = ((127+Xz)/2 , (127+Yz)/2)
The slopes of the perpendiculars are the negative inverse of the slope of the chords so:
Slope1 = - (127-Xz)/(127-Yz)
Slope2 = - Xz/Yz
Using the straight line equation for a point and a slope
L1 = y = Slope1*(x - B1x) + B1y
L2 = y = Slope2*(x - B2x) + B2y
The center of the circle C is their intersection:
Cx = [B2y-B1y + Slope1*B1x - Slope2*B2x]/[Slope1 - Slope2]
Cy = [Slope2*(B2y-B1y + Slope1*B1x - Slope2*B2x)]/[Slope1-Slope2] - Slope2*B2x +B2y
So we have the left hand side of the circle equation:
(x-Cx)^2 + (y-Cy)^2 = R^2
What's missing is the radius. But it is just the distance between C and any of our initial points. Computing from S is the easiest because it is (0,0):
R = square-root( [Cx-Sx]^2 + [Cy-Sy]^2 ) = square-root( Cx^2 + Cy^2)
So finally if you substitute all the definitions (probably easier to do in a program than to type it here) then you will obtain a function of a single variable t:
(x-Cx)^2 + (y-Cy)^2 = Cx^2 + Cy^2
note: you will get a straight line for t = 0.5 , but you can easily substitute t' = t-0.5 and only play with t'
I wanted a simple quadratic bezier curve from p0 (0,0) to p2 (127,127) based on a movable control point p1 that would range between (0,127) and (127,0). I wanted the position of the control point to be determined by a deviation variable. This is how I solved it based on my deviation variable being a number between -100 and 100 (0 being a linear line, the negative side representing pic 2 and positive side representing pic 3).
/// <summary>
/// Converts a MIDI value based on a velocity curve
/// </summary>
/// <param name="value">The value to convert</param>
/// <param name="deviation">The amount of deviation from a linear line from -100 to 100</param>
private int ConvertMidiValue(int value, double deviation)
{
if (deviation < -100 || deviation > 100)
throw new ArgumentException("Value must be between -100 and 100", "deviation");
var minMidiValue = 0d;
var maxMidiValue = 127d;
var midMidiValue = 63.5d;
// This is our control point for the quadratic bezier curve
// We want this to be between 0 (min) and 63.5 (max)
var controlPointX = midMidiValue + ((deviation / 100) * midMidiValue);
// Get the percent position of the incoming value in relation to the max
var t = (double)value / maxMidiValue;
// The quadratic bezier curve formula
// B(t) = ((1 - t) * (1 - t) * p0) + (2 * (1 - t) * t * p1) + (t * t * p2)
// t = the position on the curve between (0 and 1)
// p0 = minMidiValue (0)
// p1 = controlPointX (the bezier control point)
// p2 = maxMidiValue (127)
// Formula can now be simplified as:
// B(t) = ((1 - t) * (1 - t) * minMidiValue) + (2 * (1 - t) * t * controlPointX) + (t * t * maxMidiValue)
// What is the deviation from our value?
var delta = (int)Math.Round((2 * (1 - t) * t * controlPointX) + (t * t * maxMidiValue));
return (value - delta) + value;
}
This results in value curves ranging between the three shown below (blue is -100, gray is 0 and red is 100):

How to find the interception coordinates of a moving target in 3D space?

Assuming I have a spaceship (source); And an asteroid (target) is somewhere near it.
I know, in 3D space (XYZ vectors):
My ship's position (sourcePos) and velocity (sourceVel).
The asteroid's position (targetPos) and velocity (targetVel).
(eg. sourcePos = [30, 20, 10]; sourceVel = [30, 20, 10]; targetPos = [600, 400, 200]; targetVel = [300, 200, 100]`)
I also know that:
The ship's velocity is constant.
The asteroid's velocity is constant.
My ship's projectile speed (projSpd) is constant.
My ship's projectile trajectory, after being shot, is linear (/straight).
(eg. projSpd = 2000.00)
How can I calculate the interception coordinates I need to shoot at in order to hit the asteroid?
Notes:
This question is based on this Yahoo - Answers page.
I also searched for similar problems on Google and here on SO, but most of the answers are for 2D-space, and, of the few for 3D, neither the explanation nor the pseudo-codes explain what is doing what and/or why, so I couldn't really understand enough to apply them on my code successfully. Here are some of the pages I visited:
Danik Games Devlog, Blitz3D Forums thread, UnityAnswers, StackOverflow #1, StackOverflow #2
I really can't figure out the maths / execution-flow on the linked pages as they are, unless someone dissects it (further) into what is doing what, and why;
Provides a properly-commented pseudo-code for me to follow;
Or at least points me to links that actually explain how the equations work instead of just throwing even more random numbers and unfollowable equations in my already-confused psyche.
I find the easiest approach to these kind of problems to make sense of them first, and have a basic high school level of maths will help too.
Solving this problem is essentially solving 2 equations with 2 variables which are unknown to you:
The vector you want to find for your projectile (V)
The time of impact (t)
The variables you know are:
The target's position (P0)
The target's vector (V0)
The target's speed (s0)
The projectile's origin (P1)
The projectile's speed (s1)
Okay, so the 1st equation is basic. The impact point is the same for both the target and the projectile. It is equal to the starting point of both objects + a certain length along the line of both their vectors. This length is denoted by their respective speeds, and the time of impact. Here's the equation:
P0 + (t * s0 * V0) = P1 + (t * s0 * V)
Notice that there are two missing variables here - V & t, and so we won't be able to solve this equation right now. On to the 2nd equation.
The 2nd equation is also quite intuitive. The point of impact's distance from the origin of the projectile is equal to the speed of the projectile multiplied by the time passed:
We'll take a mathematical expression of the point of impact from the 1st equation:
P0 + (t * s0 * V0) <-- point of impact
The point of origin is P1
The distance between these two must be equal to the speed of the projectile multiplied by the time passed (distance = speed * time).
The formula for distance is: (x0 - x1)^2 + (y0 - y1)^2 = distance^2, and so the equation will look like this:
((P0.x + s0 * t * V0.x) - P1.x)^2 + ((P0.y + s0 * t * V0.y) - P1.y)^2 = (s1 * t)^2
(You can easily expand this for 3 dimensions)
Notice that here, you have an equation with only ONE unknown variable: t!. We can discover here what t is, then place it in the previous equation and find the vector V.
Let me solve you some pain by opening up this formula for you (if you really want to, you can do this yourself).
a = (V0.x * V0.x) + (V0.y * V0.y) - (s1 * s1)
b = 2 * ((P0.x * V0.x) + (P0.y * V0.y) - (P1.x * V0.x) - (P1.y * V0.y))
c = (P0.x * P0.x) + (P0.y * P0.y) + (P1.x * P1.x) + (P1.y * P1.y) - (2 * P1.x * P0.x) - (2 * P1.y * P0.y)
t1 = (-b + sqrt((b * b) - (4 * a * c))) / (2 * a)
t2 = (-b - sqrt((b * b) - (4 * a * c))) / (2 * a)
Now, notice - we will get 2 values for t here.
One or both may be negative or an invalid number. Obviously, since t denotes time, and time can't be invalid or negative, you'll need to discard these values of t.
It could very well be that both t's are bad (in which case, the projectile cannot hit the target since it's faster and out of range). It could also be that both t's are valid and positive, in which case you'll want to choose the smaller of the two (since it's preferable to hit the target sooner rather than later).
t = smallestWhichIsntNegativeOrNan(t1, t2)
Now that we've found the time of impact, let's find out what the direction the projectile should fly is. Back to our 1st equation:
P0 + (t * s0 * V0) = P1 + (t * s0 * V)
Now, t is no longer a missing variable, so we can solve this quite easily. Just tidy up the equation to isolate V:
V = (P0 - P1 + (t * s0 * V0)) / (t * s1)
V.x = (P0.x - P1.x + (t * s0 * V0.x)) / (t * s1)
V.y = (P0.y - P1.y + (t * s0 * V0.y)) / (t * s1)
And that's it, you're done!
Assign the vector V to the projectile and it will go to where the target will be rather than where it is now.
I really like this problem since it takes math equations we learnt in high school where everyone said "why are learning this?? we'll never use it in our lives!!", and gives them a pretty awesome and practical application.
I hope this helps you, or anyone else who's trying to solve this.
If you want a projectile to hit asteroid, it should be shoot at the point interceptionPos that satisfy the equation:
|interceptionPos - sourcePos| / |interceptionPos - targetPos| = projSpd / targetVel
where |x| is a length of vector x.
In other words, it would take equal amount of time for the target and the projectile to reach this point.
This problem would be solved by means of geometry and trigonometry, so let's draw it.
A will be asteroid position, S - ship, I - interception point.
Here we have:
AI = targetVel * t
SI = projSpd * t
AS = |targetPos - sourcePos|
vector AS and AI direction is defined, so you can easily calculate cosine of the SAI angle by means of simple vector math (take definitions from here and here). Then you should use the Law of cosines with the SAI angle. It will yield a quadratic equation with variable t that is easy to solve (no solutions = your projectile is slower than asteroid). Just pick the positive solution t, your point-to-shoot will be
targetPos + t * targetVel
I hope you can write a code to solve it by yourself. If you cannot get something please ask in comments.
I got a solution. Notice that the ship position, and the asteroid line (position and velocity) define a 3D plane where the intercept point lies. In my notation below | [x,y,z] | denotes the magnitude of the vector or Sqrt(x^2+y^2+z^2).
Notice that if the asteroid travels with targetSpd = |[300,200,100]| = 374.17 then to reach the intercept point (still unknown, called hitPos) will require time equal to t = |hitPos-targetPos|/targetSpd. This is the same time the projectile needs to reach the intercept point, or t = |hitPos - sourcePos|/projSpd. The two equations are used to solve for the time to intercept
t = |targetPos-sourcePos|/(projSpd - targetSpd)
= |[600,400,200]-[30,20,10]|/(2000 - |[300,200,100]|)
= 710.81 / ( 2000-374.17 ) = 0.4372
Now the location of the intetception point is found by
hitPos = targetPos + targetVel * t
= [600,400,200] + [300,200,100] * 0.4372
= [731.18, 487.45, 243.73 ]
Now that I know the hit position, I can calculate the direction of the projectile as
projDir = (hitPos-sourcePos)/|hitPos-sourcePos|
= [701.17, 467.45, 233.73]/874.52 = [0.8018, 0.5345, 0.2673]
Together the projDir and projSpd define the projectile velocity vector.
Credit to Gil Moshayof's answer, as it really was what I worked off of to build this. But they did two dimensions, and I did three, so I'll share my Unity code in case it helps anyone along. A little long winded and redundant. It helps me to read it and know what's going on.
Vector3 CalculateIntercept(Vector3 targetLocation, Vector3 targetVelocity, Vector3 interceptorLocation, float interceptorSpeed)
{
Vector3 A = targetLocation;
float Ax = targetLocation.x;
float Ay = targetLocation.y;
float Az = targetLocation.z;
float As = targetVelocity.magnitude;
Vector3 Av = Vector3.Normalize(targetVelocity);
float Avx = Av.x;
float Avy = Av.y;
float Avz = Av.z;
Vector3 B = interceptorLocation;
float Bx = interceptorLocation.x;
float By = interceptorLocation.y;
float Bz = interceptorLocation.z;
float Bs = interceptorSpeed;
float t = 0;
float a = (
Mathf.Pow(As, 2) * Mathf.Pow(Avx, 2) +
Mathf.Pow(As, 2) * Mathf.Pow(Avy, 2) +
Mathf.Pow(As, 2) * Mathf.Pow(Avz, 2) -
Mathf.Pow(Bs, 2)
);
if (a == 0)
{
Debug.Log("Quadratic formula not applicable");
return targetLocation;
}
float b = (
As * Avx * Ax +
As * Avy * Ay +
As * Avz * Az +
As * Avx * Bx +
As * Avy * By +
As * Avz * Bz
);
float c = (
Mathf.Pow(Ax, 2) +
Mathf.Pow(Ay, 2) +
Mathf.Pow(Az, 2) -
Ax * Bx -
Ay * By -
Az * Bz +
Mathf.Pow(Bx, 2) +
Mathf.Pow(By, 2) +
Mathf.Pow(Bz, 2)
);
float t1 = (-b + Mathf.Pow((Mathf.Pow(b, 2) - (4 * a * c)), (1 / 2))) / (2 * a);
float t2 = (-b - Mathf.Pow((Mathf.Pow(b, 2) - (4 * a * c)), (1 / 2))) / (2 * a);
Debug.Log("t1 = " + t1 + "; t2 = " + t2);
if (t1 <= 0 || t1 == Mathf.Infinity || float.IsNaN(t1))
if (t2 <= 0 || t2 == Mathf.Infinity || float.IsNaN(t2))
return targetLocation;
else
t = t2;
else if (t2 <= 0 || t2 == Mathf.Infinity || float.IsNaN(t2) || t2 > t1)
t = t1;
else
t = t2;
Debug.Log("t = " + t);
Debug.Log("Bs = " + Bs);
float Bvx = (Ax - Bx + (t * As + Avx)) / (t * Mathf.Pow(Bs, 2));
float Bvy = (Ay - By + (t * As + Avy)) / (t * Mathf.Pow(Bs, 2));
float Bvz = (Az - Bz + (t * As + Avz)) / (t * Mathf.Pow(Bs, 2));
Vector3 Bv = new Vector3(Bvx, Bvy, Bvz);
Debug.Log("||Bv|| = (Should be 1) " + Bv.magnitude);
return Bv * Bs;
}
I followed the problem formulation as described by Gil Moshayof's answer, but found that there was an error in the simplification of the quadratic formula. When I did the derivation by hand I got a different solution.
The following is what worked for me when finding the intersect in 2D:
std::pair<double, double> find_2D_intersect(Vector3 sourcePos, double projSpd, Vector3 targetPos, double targetSpd, double targetHeading)
{
double P0x = targetPos.x;
double P0y = targetPos.y;
double s0 = targetSpd;
double V0x = std::cos(targetHeading);
double V0y = std::sin(targetHeading);
double P1x = sourcePos.x;
double P1y = sourcePos.y;
double s1 = projSpd;
// quadratic formula
double a = (s0 * s0)*((V0x * V0x) + (V0y * V0y)) - (s1 * s1);
double b = 2 * s0 * ((P0x * V0x) + (P0y * V0y) - (P1x * V0x) - (P1y * V0y));
double c = (P0x * P0x) + (P0y * P0y) + (P1x * P1x) + (P1y * P1y) - (2 * P1x * P0x) - (2 * P1y * P0y);
double t1 = (-b + std::sqrt((b * b) - (4 * a * c))) / (2 * a);
double t2 = (-b - std::sqrt((b * b) - (4 * a * c))) / (2 * a);
double t = choose_best_time(t1, t2);
double intersect_x = P0x + t * s0 * V0x;
double intersect_y = P0y + t * s0 * V0y;
return std::make_pair(intersect_x, intersect_y);
}

Line/Plane intersection based on points

I have two points in space, L1 and L2 that defines two points on a line.
I have three points in space, P1, P2 and P3 that 3 points on a plane.
So given these inputs, at what point does the line intersect the plane?
Fx. the plane equation A*x+B*y+C*z+D=0 is:
A = p1.Y * (p2.Z - p3.Z) + p2.Y * (p3.Z - p1.Z) + p3.Y * (p1.Z - p2.Z)
B = p1.Z * (p2.X - p3.X) + p2.Z * (p3.X - p1.X) + p3.Z * (p1.X - p2.X)
C = p1.X * (p2.Y - p3.Y) + p2.X * (p3.Y - p1.Y) + p3.X * (p1.Y - p2.Y)
D = -(p1.X * (p2.Y * p3.Z - p3.Y * p2.Z) + p2.X * (p3.Y * p1.Z - p1.Y * p3.Z) + p3.X * (p1.Y * p2.Z - p2.Y * p1.Z))
But what about the rest?
The simplest (and very generalizable) way to solve this is to say that
L1 + x*(L2 - L1) = (P1 + y*(P2 - P1)) + (P1 + z*(P3 - P1))
which gives you 3 equations in 3 variables. Solve for x, y and z, and then substitute back into either of the original equations to get your answer. This can be generalized to do complex things like find the point that is the intersection of two planes in 4 dimensions.
For an alternate approach, the cross product N of (P2-P1) and (P3-P1) is a vector that is at right angles to the plane. This means that the plane can be defined as the set of points P such that the dot product of P and N is the dot product of P1 and N. Solving for x such that (L1 + x*(L2 - L1)) dot N is this constant gives you one equation in one variable that is easy to solve. If you're going to be intersecting a lot of lines with this plane, this approach is definitely worthwhile.
Written out explicitly this gives:
N = cross(P2-P1, P3 - P1)
Answer = L1 + (dot(N, P1 - L1) / dot(N, L2 - L1)) * (L2 - L1)
where
cross([x, y, z], [u, v, w]) = x*u + y*w + z*u - x*w - y*u - z*v
dot([x, y, z], [u, v, w]) = x*u + y*v + z*w
Note that that cross product trick only works in 3 dimensions, and only for your specific problem of a plane and a line.
This is how I ended up doing it in come code. Luckily one code library (XNA) had half of what I needed, and the rest was easy.
var lv = L2-L1;
var ray = new Microsoft.Xna.Framework.Ray(L1,lv);
var plane = new Microsoft.Xna.Framework.Plane(P1, P2, P3);
var t = ray.Intersects(plane); //Distance along line from L1
///Result:
var x = L1.X + t * lv.X;
var y = L1.Y + t * lv.Y;
var z = L1.Z + t * lv.Z;
Of course I would prefer having just the simple equations that takes place under the covers of XNA.

Non-axis aligned scaling

Finding a good way to do this has stumped me for a while now: assume I have a selection box with a set of points in it. By dragging the corners you can scale the (distance between) points in the box. Now for an axis aligned box this is easy. Take a corner as an anchor point (subtract this corner from each point, scale it, then add it to the point again) and multiply each points x and y by the factor with which the box has gotten bigger.
But now take a box that is not aligned with the x and y axis. How do you scale the points inside this box when you drag its corners?
Any box is contained inside a circle.
You find the circle which binds the box, find its center and do exactly the same as you do with an axis aligned box.
You pick one corner of the rectangle as the origin. The two edges connected to it will be the basis (u and v, which should be perpendicular to each other). You would need to normalize them first.
Subtract the origin from the coordinates and calculate the dot-product with the scaling vector (u), and with the other vector (v). This would give you how much u and v contributes to the coordinate.
Then you scale the component you want. To get the final coordinate, you just multiply the the (now scaled) components with their respective vector, and add them together.
For example:
Points: p1 = (3,5) and p2 = (6,4)
Selection corners: (0,2),(8,0),(9,4),(1,6)
selected origin = (8,0)
u = ((0,2)-(8,0))/|(0,2)-(8,0)| = <-0.970, 0.242>
v = <-0.242, -0.970>
(v is u, but with flipped coordinates, and one of them negated)
p1´ = p1 - origin = (-5, 5)
p2´ = p2 - origin = (-2, 4)
p1_u = p1´ . u = -0.970 * (-5) + 0.242 * 5 = 6.063
p1_v = p1´ . v = -0.242 * (-5) - 0.970 * 5 = -3.638
Scale p1_u by 0.5: 3.038
p1_u * u + p1_v * v + origin = <5.941, 4.265>
Same for p2: <7.412, 3.647>
As you maybe can see, they have moved towards the line (8,0)-(9,4), since we scaled by 0.5, with (0,8) as the origin.
Edit: This turned out to be a little harder to explain than I anticipated.
In python code, it could look something like this:
def scale(points, origin, u, scale):
# normalize
len_u = (u[0]**2 + u[1]**2) ** 0.5
u = (u[0]/len_u, u[1]/len_u)
# create v
v = (-u[1],u[0])
ret = []
for x,y in points:
# subtract origin
x, y = x - origin[0], y - origin[1]
# calculate dot product
pu = x * u[0] + y * u[1]
pv = x * v[0] + y * v[1]
# scale
pu = pu * scale
# transform back to normal space
x = pu * u[0] + pv * v[0] + origin[0]
y = pu * u[1] + pv * v[1] + origin[1]
ret.append((x,y))
return ret
>>> scale([(3,5),(6,4)],(8,0),(-8,2),0.5)
[(5.9411764705882355, 4.2647058823529411), (7.4117647058823533, 3.6470588235294117)]
Let's say that the box is defined as a set of four points (P1, P2, P3 and P4).
For the sake of simplicity, we'll say you are dragging P1, and that P3 is the opposite corner (the one you are using as an anchor).
Let's label the mouse position as M, and the new points you wish to calculate as N1, N2 and N4. P3 will, of course, remain the same.
Your scaling factor can be simply computed using vector subtraction and the vector dot product:
scale = ((M - P3) dot (P1 - P3)) / ((P1 - P3) dot (P1 - P3))
And the three new points can be found using scalar multiplication and vector addition:
N1 = scale*P1 + (1 - scale)*P3
N2 = scale*P2 + (1 - scale)*P3
N4 = scale*P4 + (1 - scale)*P3
edit: I see that MizardX has answered the question already, so my answer is here to help with that difficult explanation. I hope it helps!
edit: here is the algorithm for non-proportional scaling. In this case, N1 is equal to M (the point being dragged follows the mouse), so the only points of interest are N2 and N4:
N2 = ((M - P3) dot (P2 - P3)) / ((P2 - P3) dot (P2 - P3)) * (P2 - P3) + P3
N4 = ((M - P3) dot (P4 - P3)) / ((P4 - P3) dot (P4 - P3)) * (P4 - P3) + P3
where * represents scalar multiplication
edit: Here is some C++ code which answers the question. I'm sure this question is long-dead by now, but it was an interesting problem, and I had some fun writing the code.
#include <vector>
class Point
{
public:
float x;
float y;
Point() { x = y = 0; }
Point(float nx, float ny) { x = nx; y = ny; }
};
Point& operator-(Point& A, Point& B) { return Point(A.x-B.x, A.y-B.y); }
Point& operator+(Point& A, Point& B) { return Point(A.x+B.x, A.y+B.y); }
Point& operator*(float sc, Point& P) { return Point(sc*P.x, sc*P.y); }
float dot_product(Point A, Point B) { return A.x*B.x + A.y*B.y; }
struct Rect { Point point[4]; };
void scale_points(Rect box, int anchor, Point mouse, vector<Point> points)
{
Point& P3 = box.point[anchor];
Point& P2 = box.point[(anchor + 1)%4];
Point& P1 = box.point[(anchor + 2)%4];
Point& P4 = box.point[(anchor + 3)%4];
Point A = P4 - P3;
Point aFactor = dot_product(mouse - P3, A) / dot_product(A, A) * A;
Point B = P2 - P3;
Point bFactor = dot_product(mouse - P3, B) / dot_product(B, B) * B;
for (int i = 0; i < points.size(); i++)
{
Point P = points[i] - P3;
points[i] = P3 + dot_product(P, aFactor) + dot_product(P, bFactor);
}
}

Resources