0
votes

I have a sprite animation where I have set a stopping distance to it and want to calculate how much i have to slow the object down in that stopping distance to reach its new target speed. But at the moment I am not getting the correct result.

My code looks like this:

function updatePosition(obj,brake){
    var delta  = new Date().getTime() - obj.timer; //time since last frame

    if(brake){          
        obj.velocity -= (Math.pow(obj.velocity,2) - Math.pow(obj.targetSpeed,2)) / (2 * obj.stopDist); 
        if(obj.velocity < obj.targetSpeed){
            obj.velocity = obj.targetSpeed;
        }
    }
}

My problem is the sprite goes far past the stopping distance with a velocity well above the target speed.

I created a fiddle with a red dot travelling to a destination here: http://jsfiddle.net/4tLmz3ch/1/

When it travels the distance set by obj.stopDist it should be going the target speed which should be well before it reaches its destination. But i am obviously getting something incorrect with the math here.

Hope you can help explain my misunderstanding.

1
Velocity isn't a scalar value. You're mixing up scalar and vector calculations and I think that's one of the big problems here.JLRishe
@JLRishe not 100% sure i understand what you mean? Should i not be changing the velocity value?Sir
@Dave Velocity is a vector. It has a magnitude and a direction. In two dimensions, that alternatively means that it has an X component and a Y component. It's not just a single number.JLRishe
Sorry for offtopic, but it will be interesting to you if you are interested in physics programming in JS: box2d-js.sourceforge.net/index2.htmlAdil Aliyev
@Dave You've managed to get the dot to move in the right direction. That doesn't mean that the calculations are necessarily correct. If you want to do 2d physics, you should be using vectors.JLRishe

1 Answers

4
votes

This problem is a lot simpler if you determine the desired acceleration ahead of time, and use that during each refresh. Then the entire code for each frame (excluding the drawing logic and assuming one dimension) just becomes:

function frame() {
    var t = new Date().getTime();
    var tDelta = t - obj.lastTime;

    obj.lastTime = t;

    obj.pos += obj.velocity * tDelta;

    if (obj.velocity > obj.destVelocity) {
        obj.velocity += obj.acceleration * tDelta;
    }

    draw();

    setTimeout(frame, 1);
}

Given a starting and ending position and velocity, the formula for the acceleration required (assuming constant acceleration) is:

(2 * v0 * (vf - v0) + (vf - v0)^2) / (2 * (xf - x0))

So initializing the object like this:

var obj = {
    start: 10,
    height: 200,
    stopDist: 300, 
    dest: 500,
    lastTime: new Date().getTime(),
    velocity: 0.05,
    destVelocity: 0.01,
    pos: undefined,
    acceleration: undefined
};

here is how we can kick this all off:

function start(){
    var v0 = obj.velocity,
        vf = obj.destVelocity,
        x0 = obj.start,
        xf = x0 + x.stopDist,
        vDelta = vf - v0;

    obj.pos = x0;
    obj.acceleration = (2 * v0 * vDelta + vDelta * vDelta) / (2 * (xf - x0));

    frame();
}

As I've done above, it's helpful to solve the 1d case first. Here is that, all put together.

var canvas = document.getElementById('canvas');
var test = document.getElementById('test');
var ctx = canvas.getContext('2d');

function drawDot(color, x, y) {
    ctx.fillStyle = color;
    ctx.fillRect(x - 2, y - 2, 4, 4);
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    drawDot("red", obj.pos, obj.height);
    drawDot("white", obj.start, obj.height);
    drawDot("green", obj.dest, obj.height);
    drawDot("yellow", obj.start + obj.stopDist, obj.height);

    ctx.fillText("x = " + obj.pos.toFixed(5), 20, 400);
    ctx.fillText("v = " + obj.velocity.toFixed(5), 20, 420);
    ctx.fillText("distance traveled: " + (obj.pos - obj.start).toFixed(2), 20, 440);
}

var obj = {
    start: 10,
    height: 200,
    stopDist: 300,
    dest: 500,
    lastTime: new Date().getTime(),
    velocity: 0.05,
    destVelocity: 0.01,
    pos: undefined,
    acceleration: undefined
};

function frame() {
    var t = new Date().getTime(),
        tDelta = t - obj.lastTime;
    obj.lastTime = t;

    obj.pos += obj.velocity * tDelta;
    if (obj.velocity > obj.destVelocity) {
        obj.velocity += obj.acceleration * tDelta;
    }

    draw();

    setTimeout(frame, 1);
}

function start() {
    var v0 = obj.velocity,
        vf = obj.destVelocity,
        x0 = obj.start,
        xf = x0 + obj.stopDist,
        vDelta = vf - v0;

    obj.pos = x0;
    obj.acceleration = (2 * v0 * vDelta + vDelta * vDelta) / (2 * (xf - x0));

    frame();
}

start();
#canvas{
    background-color:black;
}
<canvas id="canvas" width="700" height="700"></canvas>

http://jsfiddle.net/x7842xcb/3/


And here is the 2d version (brace yourself):

var canvas = document.getElementById('canvas');
var test = document.getElementById('test');
var ctx = canvas.getContext('2d');

function drawDot(color, x, y) {
    ctx.fillStyle = color;
    ctx.fillRect(x - 2, y - 2, 4, 4);
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    drawDot("red", obj.pos.x, obj.pos.y);
    drawDot("white", obj.start.x, obj.start.y);
    drawDot("green", obj.dest.x, obj.dest.y);
    drawDot("yellow", obj.stopLocation.x, obj.stopLocation.y);
    
    var dx = obj.pos.x - obj.start.x,
        dy = obj.pos.y - obj.start.y,
        dist = Math.sqrt(dx * dx + dy *dy),
        v = obj.velocity,
        speed = Math.sqrt(v.x * v.x + v.y * v.y);
    
    ctx.fillText("distance traveled: " + dist.toFixed(5), 20, 400);
    ctx.fillText("speed:             " + speed.toFixed(5), 20, 420);
}

var obj = {
    start: { x: 400, y: 230 },
    stopDist: 350,
    dest: { x: 50, y: 330 },
    lastTime: new Date().getTime(),
    startSpeed: 0.05,
    destSpeed: 0.1,

    pos: null,
    velocity: null,
    destVelocity: null,
    acceleration: null
};

function sign(value) {
    return value > 0 ? 1 : (value < 0 ? -1 : 0);
}

function reached(start, current, dest) {
    return current === dest || 
        sign(current - dest) === sign(dest - start);
}

function frame() {
    var t = new Date().getTime(),
        tDelta = t - obj.lastTime,
        v = obj.velocity,
        destv = obj.destVelocity,
        startv = obj.startVelocity;
    obj.lastTime = t;

    obj.pos.x += v.x * tDelta;
    obj.pos.y += v.y * tDelta;
    
    if (!reached(startv.x, v.x, destv.x) ||
        !reached(startv.y, v.y, destv.y)) {
        v.x += obj.acceleration.x * tDelta;
        v.y += obj.acceleration.y * tDelta;
    }

    draw();

    setTimeout(frame, 1);
}

function calcAcceleration(p0, pf, v0, vf) {
    var vDelta = vf - v0;
    
    return pf ===  p0 
        ? 0
        : (2 * v0 * vDelta + vDelta * vDelta) / (2 * (pf - p0));
}

function start() {
    // positions and deltas
    var start = obj.start,
        dest = obj.dest,
        dx = dest.x - start.x,
        dy = dest.y - start.y,
        totalDistance = Math.sqrt(dx * dx + dy * dy);
    
    // x and y component ratio
    var cx = dx / totalDistance,
        cy = dy / totalDistance;
    
    var stopLocation = { x: cx * obj.stopDist + start.x, 
                         y: cy * obj.stopDist + start.y };
    
    // velocities
    var startSpeed = obj.startSpeed,
        destSpeed = obj.destSpeed,
        startVelocity = { x: cx * startSpeed, y: cy * startSpeed },
        endVelocity = { x: cx * destSpeed, y: cy * destSpeed };
    console.log(startVelocity);
    console.log(endVelocity);
    
    // acceleration
    var acceleration = { 
        x: calcAcceleration(start.x, stopLocation.x, startVelocity.x, endVelocity.x),
        y: calcAcceleration(start.y, stopLocation.y, startVelocity.y, endVelocity.y) 
    };

    obj.pos = Object.create(start);
    obj.startVelocity = startVelocity;
    obj.velocity = Object.create(startVelocity);
    obj.stopLocation = stopLocation;
    obj.destVelocity = endVelocity;
    obj.acceleration = acceleration;
    
    frame();
}

start();
#canvas{
    background-color:black;
}
<canvas id="canvas" width="700" height="700"></canvas>

http://jsfiddle.net/1r3q4oob/3/

Edit Regarding the fix I made after the fact:

The problem with my original implementation was that it would only update the velocity if both the X and Y components of the current velocity were greater than the target velocity. This would prevent the correct behavior if:

  • The X or Y component of both the start and end velocity were 0 (i.e. if it were travelling perfectly horizontally or vertically)
  • The X or Y component of the start and end velocity were negative (i.e. if it were travelling up and to the left)
  • The velocity needed to increase rather than decrease (i.e. the dot were speeding up to a target velocity)

I resolved this with the addition of the reached() function, which basically returns true if (a) the destination velocity is between the current velocity and the start velocity (i.e. the current velocity has gone past the destination velocity), or (b) the current velocity is equal to the destination velocity.