问题
This is more of a math question, and I couldn't seem to find any answers online.
So here is what I am trying to accomplish:
Lets say I have a circle, starting at Ay
. Now as this circle moves towards By
, I want it to scale proportionally to a certain size.
So for example, if the circle's diameter was 5 at Ay
, how could I scale it to become 52.2 by the time it reaches By
.
And bonus question: could I achieve this same thing with a square?
回答1:
Tweening in terms of position.
Keys and keyFrames
In animation we define known positions and states as key frames, normally we index the key frames in terms of time.
// an array of keys. The ? represents the applicable number value
var keys = [{
time : 0, // the state of an object at time 0
pos : {x : ? , y : ? }, // position
scale : ?,
rotation : ?,
colour : [?,?,?], // rgb colour, just for the hell of it
// and whatever else you may want to animate
},{
time : 100, // the state of the object at time 100
pos : {x : ? , y : ? },
scale : ?,
rotation : ?,
colour : [?,?,?],
// and whatever else you may want to animate
}
]
Normalised time
To get the state of an object at any time t between the key frames we find the normalised time ( a value from 0 to 1) between the times and multiply that to the difference between other states then add that to the beginning state.
So say the time is 50 first we get the normalised time
var currentTime = 50;
var timeDif = currentTime - keys[0].time; // difference from start time to current
// to get the normalised time divid by the differance
var normTime = timeDif / (keys[1].time - keys[0].time); // divide by the differance in time between keys
Now you have the normalised time you can easily calculate any of the states
var scaleDif = keys[1].scale - keys[0].scale; // get diff in scale
var scaleChange = scaleDif * normTime; // multiply by the normalised time
var currentScale = keys[0].scale + scaleChange; // add to the starting scale
That is all a little long winded but that is to ease you into what is happening. the complete keying function would look like.
function tweenKeys(time,key1,key2){
var nt = (time - key1.time) / (key2.time - key1.time); // get normalised time
// because you can not divide by zero we need a little check. Javascript return infinity if we div by zero but we want the value 0
nt = nt < Infinity ? nt : 0; // zero if there was a divide by zero
var ck = {}; // ck for current key. the key represents the state at time
ck.scale = key1.scale + (key2.scale - key1.scale) * nt;
ck.rotation = key1.rotation + (key2.rotation - key1.rotation ) * nt;
ck.pos.x = key1.pos.x + (key2.pos.x- key1.pos.x) * nt;
ck.pos.y = key1.pos.y + (key2.pos.y- key1.pos.y) * nt;
ck.colour[0] = key1.colour[0] + (key2.colour[0] - key1.colour[0]) * nt;
ck.colour[1] = key1.colour[1] + (key2.colour[1] - key1.colour[1]) * nt;
ck.colour[2] = key1.colour[2] + (key2.colour[2] - key1.colour[2]) * nt;
return ck; // return the newly create state
}
Thats the basics of keyframing and you can find more on it in this answer How would I animate... ?
In space rather than time
All good but for your problem this has not helped, you are not using time you are using position to determine the current state of the object. Well it does not matter what we use to find our current state, any of the values in the key frame can be used to determine that state of all the others. All we need to do is find the normalised difference and then apply that like we did normalised time to all the other values.
Normalised position
So lets look at position
Consider two points p1 and p2, defined as
var p1 = {x : ?, y : ?}; // ? represent some number value
var p2 = {x : ?, y : ?}; // ? represent some number value
And representing your positions A,B
If we have a 3rd point C
var c = {x : ?, y : ?}; // ? represent some number value
somewhere on the 2D plane. We want a formula that will return a 0 when C is at point p1 and 1 when the point c is at point p2. This will be our normalised position used to get the current state.
As position is 2d we need to involve both the x and y in the calculations. We get the distance from p1 to point c and the divide that by the distance between point p1 and p2. that will give us the value we want. To find the distance we use the pythag solution. root of the sum of the squares
var dist = Math.sqrt( Math.pow( p2.x - p1.x, 2) + Math.pow( p2.y - p1.y, 2)); // for the twisted world of IE users and
var dist = Math.hypot(p2.x - p1.x, p2.y - p1.y); // for all good browsers
So the normalised distance is
var normDist = Math.hypot(c.x - p1.x, c.y - p1.y) / Math.hypot(p2.x - p1.x, p2.y - p1.y);
// because you can not divide by zero we need a little check. Javascript returns infinity if we div by zero but we want the value 0
normDist = normDist < Infinity ? normDist : 0; // zero if there was a divide by zero
Then apply that (normDist) to all the key states.
var currentScale = (keys[1].scale - keys[0].scale) * normDist + keys[0].scale;
Problems with positioning
Ok you say thanks, sorry but that is not the solution, it would be if you knew that the point c is always on the line between p1, p2 but that is not always the case, and under a strict examination it is hardly ever because computers store digital information so there will be a little error in any calculation that requires very fine detail. Also the above method will return 1 for normalized distance for any point that is distance to p2 away from p1, that describes a circle around the point p1. We need to constrain this value a bit more. Also if c is befor the point p1 or after the point p2 it would be handy to know. Thus we can use the following to do so.
// get the unit distance on the line p1,p2 of point c representing
// the distance along the line that is closest to c
function unitDistOfPoint(p1,p2,c){
var v1 = {}; // working vectors
var v2 = {};
v1.x = p2.x - p1.x; // vector between p1,p2
v1.y = p2.y - p1.y;
v2.x = c.x - p1.x; // vector to c from p1
v2.y = c.y - p1.y;
// a little math magic. Divide the dot product of the vectors v2, v1
// by the square of line length
return (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x);
}
Now we can do the tweening and get your scale
// return the state for a object at point c in terms of key1, to key2
function tweenKeysViaPos(c,key1,key2){
// get the normalised distance of the point c between keys 1 and 2
var nd = unitDistOfPoint(c, key1.pos, key2.pos); // nd for normalised distance
// you may want to constrain the position to only between the points
// do that by clamping the value nd between 0 and 1 inclusive
nd = Math.max(0, Math.min(1, nd)); // clamp the normalise distance
var ck = {}; // ck for current key. the key represents the state at time
ck.scale = key1.scale + (key2.scale - key1.scale) * nt;
ck.rotation = key1.rotation + (key2.rotation - key1.rotation ) * nt;
ck.pos.x = key1.pos.x + (key2.pos.x- key1.pos.x) * nt;
ck.pos.y = key1.pos.y + (key2.pos.y- key1.pos.y) * nt;
ck.colour[0] = key1.colour[0] + (key2.colour[0] - key1.colour[0]) * nt;
ck.colour[1] = key1.colour[1] + (key2.colour[1] - key1.colour[1]) * nt;
ck.colour[2] = key1.colour[2] + (key2.colour[2] - key1.colour[2]) * nt;
return ck; // return the newly create state
}
That is the answer. As a side benefit if the point c does stray away from the line between the keys then the above function also return the position it should be.
For more if needed
You may want to extend this to adapt to many key frames. Normally for more than two key frames and using time it is easy to find the keys that we want by finding where time is greater than the first key and less than the next key. But this is not as simple if you are using the position to work out at which key you are at. So to help a more complex solution you will find this function handy
// returns the distance point c is from the line p1,p2. If on the line
// the the return value is 0. If befor point p1 or after p2 then the distance
// is the distance to p1, or p2 respectively
function distFromLine(p1,p2,c){
var v1 = {}; // working vectors
var v2 = {};
v1.x = p2.x - p1.x; // vector between p1,p2
v1.y = p2.y - p1.y;
v2.x = c.x - p1.x; // vector to c from p1
v2.y = c.y - p1.y;
// a little math magic. Divide the dot product of the vectors v2, v1
// by the square of line length
var u = (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x);
var v3 = {};
if(u < 0){ // befor the start
return Math.hypot(v2.x,v2.y); // distance to p1
}
if(u > 1){ // after end
return Math.hypot(c.x - p2.x,c.y p2.y); // distance to p2
}
// get the point on the line that is closest
v3.x = p1.x + v1.x * u;
v3.y = p1.y + v1.y * u;
// return the distance from that point to c
return Math.hypot(c.x - v3.x,c.y - v3.y); // distance from line of c
}
You can then find the two keys you need by finding the keys that return the smallest distance from the line between them. You and then define a complicated line by defining many key frames and where ever you put an object you can calculate where it should be and in what state.
Hope this helps and did not go over the top. If anything is unclear to anyone that reads please do say so in the comments and I will clarify.
回答2:
For linear scaling if
D[Ay]
is the diameter/side of circle/square when it is atAy
D[By]
is the diameter/side of circle/square when it is atBy
D[Cy]
is the diameter/side of circle/square when it is atCy
Ay <= Cy <= By
then
D[Cy] = D[Ay] + (Cy - Ay) * (D[By] - D[Ay]) / (By - Ay)
回答3:
If vector v=(By-Ay)
, the line between Ay
and By
can be defined as l(t)=Ay+vt
. Therefore any point on l(t)
with parameter t
has scaling factor of s=47.5t+5
. For instance at t=0 that gives point Ay
on the line has scaling factor s=5
. If you put t=1
, you get By and scale s= 52.5
. For your bonus question, scaling factor is the same but you cannot simply multiply the coordinates of the square in the scaling factor. You need to translate the square using l(t)
to the origin and scale the coordinates and translate it back to l(t)
.
来源:https://stackoverflow.com/questions/37911194/how-can-i-relatively-scale-something-between-two-points