How can I relatively scale something between two points?

浪尽此生 提交于 2019-12-12 00:36:52

问题


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 at Ay
  • D[By] is the diameter/side of circle/square when it is at By
  • D[Cy] is the diameter/side of circle/square when it is at Cy
  • 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!