I am trying to create what I thought would be a simple Canvas to move a point from A to B using requestAnimationFrame.
http://jsfiddle.net/p1L81yk4/1/
Unfortunately it is not showing anything, and the animation loop seems to be headed towards infinity.
Can anyone explain what I am doing wrong? And is there a flag I can create to show when the animation should stop?
var startX = 10,
startY = 10;
var endX = 200,
endY = 350;
var speed = 1;
var dx = endX - startX;
var dy = endY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
var moves = distance / speed;
var xunits = (dx / moves);
var yunits = (dy / moves);
var posit = {
x: startX,
y: startY
};
var fps = 60;
var delay = 1000 / 30;
function draw() {
ctx.clearRect(0, 0, canv.width, canv.height);
ctx.save();
ctx.translate(startX, startY);
ctx.beginPath();
ctx.arc(0, 0, 5, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function move() {
startX += dx;
startY += dy;
console.log(posit);
if (moves > 0) {
moves++;
posit.x += xunits;
posit.y += yunits;
}
}
var start = 0;
function animate() {
running = true;
var current = new Date().getTime(),
delta = current - start;
if (delta >= delay) {
move();
draw();
start = new Date().getTime();
}
anim = requestAnimationFrame(animate);
}
animate();
You should not use save and restore, these functions is for saving and restoring the images.
You should translate posit.x and posit.y instead of startX and startY.
You are changing startX and startY which is not reasonable.
You need a if to determinate if or if not to continue aniamtion.
Which ends up with a working code:
var canv = document.getElementById('canv'),
ctx = canv.getContext('2d');
var startX = 10,
startY = 10;
var endX = 200,
endY = 350;
var speed = 2;
var dx = endX - startX;
var dy = endY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
var moves = distance / speed;
var xunits = (dx / moves);
var yunits = (dy / moves);
var posit = {
x: startX,
y: startY
};
var fps = 60;
var delay = 1000 / 60;
function draw() {
ctx.clearRect(0, 0, canv.width, canv.height);
//ctx.save();
ctx.translate(posit.x, posit.y);
ctx.beginPath();
ctx.arc(0, 0, 5, 0, Math.PI * 2);
ctx.fill();
ctx.translate(-posit.x, -posit.y);
//ctx.restore();
}
function move() {
//startX += dx;
//startY += dy;
//console.log(posit);
if (moves > 0) {
moves++;
posit.x += xunits;
posit.y += yunits;
}
}
var start = 0;
function animate() {
running = true;
var current = new Date().getTime(),
delta = current - start;
if (delta >= delay) {
move();
draw();
start = new Date().getTime();
}
if(posit.y < endY)
anim = requestAnimationFrame(animate);
}
animate();
<canvas id="canv" width="500" height="500"></canvas>
Using basic trigonometry you can make the entire code look and read easier, with less up front variables. I still need to set quite a lot of variables here, but you would want to mostly calculate them on the fly (which means you would have to move things like angle, from and to value and distance into the myDraw() function).
var myCanvas = document.getElementById('myCanvas');
var myContext = myCanvas.getContext('2d');
myContext.fillStyle = '#000';
// Values expressed as x, y, positions
var fromValue = [300,20];
var toValue = [100,100];
// time expressed in Milliseconds
var time = 5000;
var start = Date.now();
// Get the angle with the arctangent function
// tan(angle) = opposite / adjacent => atan(opposite / adjacent) = angle
// atan2 is used because you can pass it the lengths and it takes care of
// some negative edge cases.
var angle = Math.atan2(toValue[0] - fromValue[0], toValue[1] - fromValue[1]);
// Basic distance because.. Pythagoras. (a^2 = sqrt(b^2 + c^2))
var distance = Math.sqrt(Math.pow(toValue[0] - fromValue[0], 2) + Math.pow(toValue[1] - fromValue[1], 2));
function myDraw(now){
// The max distance can be divided by the total time, multiplied by the time that has passed
var t = (distance / time * (now - start));
var x = fromValue[0] + Math.sin(angle) * t;
var y = fromValue[1] + Math.cos(angle) * t;
// Clear the canvas by resetting its width
myCanvas.width = myCanvas.width;
// Draw the arc at position x and y
myContext.arc(x, y, 3, 0, Math.PI * 2);
myContext.fill();
// Return false if the animation is done.
if(now < start + time) return true;
else return false;
}
function myAnimate(){
// Keep executing as long as myDraw() returns true
if(myDraw(Date.now())) window.requestAnimationFrame(myAnimate);
}
myAnimate();
<canvas width="500" height="500" id="myCanvas" />
Related
In the Below code link HTML5 canvas spin wheel game. I want to stop this canvas at a user-defined position as if the user wants to stop always at 200 texts or 100 texts like that.
Currently, it is stopping at random points I want to control where to stop as in if I want to stop circle at 100 or 200 or 0 whenever I want.
How can we achieve that??? Can anyone Help!!!!!
Attached Codepen link also.
Html file
<div>
<canvas class="spin-wheel" id="canvas" width="300" height="300"></canvas>
</div>
JS file
var color = ['#ca7','#7ac','#77c','#aac','#a7c','#ac7', "#caa"];
var label = ['10', '200','50','100','5','500',"0"];
var slices = color.length;
var sliceDeg = 360/slices;
var deg = 270;
var speed = 5;
var slowDownRand = 0;
var ctx = canvas.getContext('2d');
var width = canvas.width; // size
var center = width/2; // center
var isStopped = false;
var lock = false;
function rand(min, max) {
return Math.random() * (max - min) + min;
}
function deg2rad(deg){ return deg * Math.PI/180; }
function drawSlice(deg, color){
ctx.beginPath();
ctx.fillStyle = color;
ctx.moveTo(center, center);
ctx.arc(center, center, width/2, deg2rad(deg), deg2rad(deg+sliceDeg));
console.log(center, center, width/2, deg2rad(deg), deg2rad(deg+sliceDeg))
ctx.lineTo(center, center);
ctx.fill();
}
function drawText(deg, text) {
ctx.save();
ctx.translate(center, center);
ctx.rotate(deg2rad(deg));
ctx.textAlign = "right";
ctx.fillStyle = "#fff";
ctx.font = 'bold 30px sans-serif';
ctx.fillText(text, 130, 10);
ctx.restore();
}
function drawImg() {
ctx.clearRect(0, 0, width, width);
for(var i=0; i<slices; i++){
drawSlice(deg, color[i]);
drawText(deg+sliceDeg/2, label[i]);
deg += sliceDeg;
}
}
// ctx.rotate(360);
function anim() {
isStopped = true;
deg += speed;
deg %= 360;
// Increment speed
if(!isStopped && speed<3){
speed = speed+1 * 0.1;
}
// Decrement Speed
if(isStopped){
if(!lock){
lock = true;
slowDownRand = rand(0.994, 0.998);
}
speed = speed>0.2 ? speed*=slowDownRand : 0;
}
// Stopped!
if(lock && !speed){
var ai = Math.floor(((360 - deg - 90) % 360) / sliceDeg); // deg 2 Array Index
console.log(slices)
ai = (slices+ai)%slices; // Fix negative index
return alert("You got:\n"+ label[ai] ); // Get Array Item from end Degree
// ctx.arc(150,150,150,8.302780584487312,9.200378485512967);
// ctx.fill();
}
drawImg();
window.requestAnimationFrame(anim);
}
function start() {
anim()
}
drawImg();
Spin wheel codepen
Ease curves
If you where to plot the wheel position over time as it slows to a stop you would see a curve, a curve that looks like half a parabola.
You can get the very same curve if you plot the value of x squared in the range 0 to 1 as in the next snippet, the red line shows the plot of f(x) => x * x where 0 <= x <= 1
Unfortunately the plot is the wrong way round and needs to be mirrored in x and y. That is simple by changing the function to f(x) => 1 - (1 - x) ** 2 (Click the canvas to get the yellow line)
const size = 200;
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size / 2}).getContext("2d");
document.body.appendChild(ctx.canvas);
ctx.canvas.style.border = "2px solid black";
plot(getData());
plot(unitCurve(x => x * x), "#F00");
ctx.canvas.addEventListener("click",()=>plot(unitCurve(x => 1 - (1 - x) ** 2), "#FF0"), {once: true});
function getData(chart = []) {
var pos = 0, speed = 9, deceleration = 0.1;
while(speed > 0) {
chart.push(pos);
pos += speed;
speed -= deceleration;
}
return chart;
}
function unitCurve(f,chart = []) {
const step = 1 / 100;
var x = 0;
while(x <= 1) {
chart.push(f(x));
x += step
}
return chart;
}
function plot(chart, col = "#000") {
const xScale = size / chart.length, yScale = size / 2 / Math.max(...chart);
ctx.setTransform(xScale, 0, 0, yScale, 0, 0);
ctx.strokeStyle = col;
ctx.beginPath();
chart.forEach((y,x) => ctx.lineTo(x,y));
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
In animation this curve is an ease in.
We can create function that uses the ease function, takes the time and returns the position of the wheel. We can provide some additional values that controls how long the wheel will take to stop, the starting position and the all important stop position.
function wheelPos(currentTime, startTime, endTime, startPos, endPos) {
// first scale the current time to a value from 0 to 1
const x = (currentTime - startTime) / (endTime - startTime);
// rather than the square, we will use the square root (this flips the curve)
const xx = x ** (1 / 2);
// convert the value to a wheel position
return xx * (endPos - startPos) + startPos;
}
Demo
The demo puts it in action. Rather than using the square root the function in the demo defines the root as the constant slowDownRate = 2.6. The smaller this value the greater start speed and the slower the end speed. A value of 1 means it will move at a constant speed and then stop. The value must be > 0 and < 1
requestAnimationFrame(mainLoop);
Math.TAU = Math.PI * 2;
const size = 160;
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size}).getContext("2d");
document.body.appendChild(ctx.canvas);
const stopAt = document.createElement("div")
document.body.appendChild(stopAt);
ctx.canvas.style.border = "2px solid black";
var gTime; // global time
const colors = ["#F00","#F80","#FF0","#0C0","#08F","#00F","#F0F"];
const wheelSteps = 12;
const minSpins = 3 * Math.TAU; // min number of spins before stopping
const spinTime = 6000; // in ms
const slowDownRate = 1 / 1.8; // smaller this value the greater the ease in.
// Must be > 0
var startSpin = false;
var readyTime = 0;
ctx.canvas.addEventListener("click",() => { startSpin = !wheel.spinning });
stopAt.textContent = "Click wheel to spin";
const wheel = { // hold wheel related variables
img: createWheel(wheelSteps),
endTime: performance.now() - 2000,
startPos: 0,
endPos: 0,
speed: 0,
pos: 0,
spinning: false,
set currentPos(val) {
this.speed = (val - this.pos) / 2; // for the wobble at stop
this.pos = val;
},
set endAt(pos) {
this.endPos = (Math.TAU - (pos / wheelSteps) * Math.TAU) + minSpins;
this.endTime = gTime + spinTime;
this.startTime = gTime;
stopAt.textContent = "Spin to: "+(pos + 1);
}
};
function wheelPos(currentTime, startTime, endTime, startPos, endPos) {
const x = ((currentTime - startTime) / (endTime - startTime)) ** slowDownRate;
return x * (endPos - startPos) + startPos;
}
function mainLoop(time) {
gTime = time;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, size, size);
if (startSpin && !wheel.spinning) {
startSpin = false;
wheel.spinning = true;
wheel.startPos = (wheel.pos % Math.TAU + Math.TAU) % Math.TAU;
wheel.endAt = Math.random() * wheelSteps | 0;
} else if (gTime <= wheel.endTime) { // wheel is spinning get pos
wheel.currentPos = wheelPos(gTime, wheel.startTime, wheel.endTime, wheel.startPos, wheel.endPos);
readyTime = gTime + 1500;
} else { // wobble at stop
wheel.speed += (wheel.endPos - wheel.pos) * 0.0125;
wheel.speed *= 0.95;
wheel.pos += wheel.speed;
if (wheel.spinning && gTime > readyTime) {
wheel.spinning = false;
stopAt.textContent = "Click wheel to spin";
}
}
// draw wheel
ctx.setTransform(1,0,0,1,size / 2, size / 2);
ctx.rotate(wheel.pos);
ctx.drawImage(wheel.img, -size / 2 , - size / 2);
// draw marker shadow
ctx.setTransform(1,0,0,1,1,4);
ctx.fillStyle = "#0004";
ctx.beginPath();
ctx.lineTo(size - 13, size / 2);
ctx.lineTo(size, size / 2 - 7);
ctx.lineTo(size, size / 2 + 7);
ctx.fill();
// draw marker
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.lineTo(size - 13, size / 2);
ctx.lineTo(size, size / 2 - 7);
ctx.lineTo(size, size / 2 + 7);
ctx.fill();
requestAnimationFrame(mainLoop);
}
function createWheel(steps) {
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size}).getContext("2d");
const s = size, s2 = s / 2, r = s2 - 4;
var colIdx = 0;
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const aa = a - Math.PI / steps;
ctx.fillStyle = colors[colIdx++ % colors.length];
ctx.beginPath();
ctx.moveTo(s2, s2);
ctx.arc(s2, s2, r, aa, aa + Math.TAU / steps);
ctx.fill();
}
ctx.fillStyle = "#FFF";
ctx.beginPath();
ctx.arc(s2, s2, 12, 0, Math.TAU);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.arc(s2, s2, r, 0, Math.TAU);
ctx.moveTo(s2 + 12, s2);
ctx.arc(s2, s2, 12, 0, Math.TAU);
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const aa = a - Math.PI / steps;
ctx.moveTo(Math.cos(aa) * 12 + s2, Math.sin(aa) * 12 + s2);
ctx.lineTo(Math.cos(aa) * r + s2, Math.sin(aa) * r + s2);
}
//ctx.fill("evenodd");
ctx.stroke();
ctx.fillStyle = "#000";
ctx.font = "13px arial black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const tr = r - 8;
var idx = 1;
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const dx = Math.cos(a);
const dy = Math.sin(a);
ctx.setTransform(dy, -dx, dx, dy, dx * (tr - 4) + s2, dy * (tr - 4) + s2);
ctx.fillText(""+ (idx ++), 0, 0);
}
return ctx.canvas;
}
body { font-family: arial }
This image illustrates my problem very well:
The ball moves very slow when it is close to its endpoint (orange circle) but moves very fast when it's farther away from its endpoint.
The reason this happens is because I'm using this code to calculate my vertical and horizontal velocities for the balls
var startingBallSpeed = 100;
xDistance = targetX - this.x;
yDistance = targetY - this.y;
this.horizontalVelocity = (xDistance / startingBallSpeed);
this.verticalVelocity = (yDistance / startingBallSpeed);
QUESTION: How can I make sure that the balls travel the same speed and will still hit the targetX and targetY
Current Behavior: Balls close to endpoint move slow, balls far from endpoint move fast
Desired Behavior: Balls move at the same speed regardless of how close they are to the endpoint
JS: https://jsfiddle.net/7ct1ap53/1
ball1 = new Ball(400, 480, "green");
ball2 = new Ball(20, 480, "blue");
targetX = 500;
targetY = 400;
targetBall = new Ball(targetX, targetY, "magenta", radius=10)
var gameArea = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 500;
this.canvas.height = 500;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[4]);
this.interval = setInterval(updateGame, 20); //20
}
};
function Ball(x, y, color, radius=15) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color
this.draw = function() {
gameArea.context.beginPath();
gameArea.context.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
gameArea.context.fillStyle = color;
gameArea.context.fill();
gameArea.context.closePath();
}
this.launch = function() {
var startingBallSpeed = 100;
xDistance = targetX - this.x;
yDistance = targetY - this.y;
this.horizontalVelocity = (xDistance / startingBallSpeed);
this.verticalVelocity = (yDistance / startingBallSpeed);
}
this.updatePos = function() {
this.x += this.horizontalVelocity;
this.y += this.verticalVelocity;
};
}
$(function() {
startGame();
});
function updateGame() {
gameArea.context.clearRect(0,0,500,500);
ball1.updatePos();
ball2.updatePos();
ball1.draw();
ball2.draw();
targetBall.draw();
}
function startGame() {
gameArea.start();
ball1.launch();
ball2.launch();
}
You need to normalize the vector defined by xDistance, yDistance to create a unit vector that specifies the direction but has a length of 1. Then you can multiply it by your desired ball speed to get the velocity.
Normalize by dividing by the length:
xDistance = targetX - this.x;
yDistance = targetY - this.y;
length = Math.sqrt((xDistance * xDistance) + (yDistance * yDistance));
if(length > 0) // avoid divide by zero
{
xUnitVector = xDistance / length;
yUnitVector = yDistance / length;
this.horizontalVelocity = xUnitVector * startingBallSpeed;
this.verticalVelocity = yUnitVector * startingBallSpeed;
}
else
{
// cancel the launch because you are already at your destination
}
Adjust your ball speed to whatever constant speed you require
I have a function which moves my canvas using an ease in aspect. The problem how ever is the canvas animation doesn't work. It just scrolls too far and what appears to be too fast as well.
This is my function which moves the camera to the location the user clicked on the canvas:
function moveCamera(e,parent){
clearInterval(parent.scroll);
var mouseData = mousePos(evt,parent); //get x:y relative to element
var initial = {'x':el.width/2,'y':el.height/2},
target = {'x':mouseData.x,'y':mouseData.y},
deltaX = target.x-initial.x,
deltaY = target.y-initial.y,
timeStart = Date.now(),
timeLength = 800,
x,y,deltaTime;
function update(){
function fraction(t){
x = (target.x - initial.x) - (t*deltaX),
y = (target.y - initial.y) - (t*deltaY);
camera.x -= x;
camera.y -= y;
}
function easing(x) {
return 0.5 + 0.5 * Math.sin((x - 0.5) * Math.PI);
}
deltaTime = (Date.now() - timeStart) / timeLength;
if (deltaTime > 1) {
fraction(1);
} else {
fraction(easing(deltaTime));
}
}
parent.scroll = setInterval(update, 10);
}
I have attatched a JSFiddle of the issue demonstrated: http://jsfiddle.net/p5xjmLay/ simply click on the canvas to scroll to that position, and you will see it goes a bit crazy.
I am wondering how to solve this so the camera offset changes correctly each time?
I changed a little bit your version and it seems that it is working, please try this:
var el = document.getElementById('canvas'),
initial = {'x':el.width/2,'y':el.height/2},
ctx = el.getContext('2d'),
camera = {'x':el.width/2,'y':el.height/2},
box = {'x':0,'y':0};
var x,y,deltaTime;
el.addEventListener('mousedown',function(e){moveCamera(e,this);},false);
function moveCamera(e,parent){
clearInterval(parent.scroll);
var mouseData = mousePos(e,parent);
target = {'x':mouseData.x,'y':mouseData.y},
deltaX = target.x-initial.x,
deltaY = target.y-initial.y,
timeStart = Date.now(),
timeLength = 800;
function update(){
function fraction(t){
x = target.x - (initial.x + (t*deltaX)),
y = target.y - (initial.y + (t*deltaY));
if (Math.abs(camera.x + x - target.x) > Math.abs(camera.x - target.x)) {
camera.x = target.x;
initial.x = target.x;
} else {
camera.x += x;
}
if (Math.abs(camera.y + y - target.y) > Math.abs(camera.y - target.y)) {
camera.y = target.y;
initial.y = target.y;
} else {
camera.y += y;
}
}
function easing(x) {
return 0.5 + 0.5 * Math.sin((x - 0.5) * Math.PI);
}
deltaTime = (Date.now() - timeStart) / timeLength;
if (deltaTime > 1) {
fraction(1);
} else {
fraction(easing(deltaTime));
}
draw();
}
parent.scroll = setInterval(update, 200);
}
function mousePos(evt,el){
var offsetX = 0,
offsetY = 0;
do{
offsetX += el.offsetLeft - el.scrollLeft;
offsetY += el.offsetTop - el.scrollTop;
}while(el = el.offsetParent){
return {'x':evt.pageX - offsetX, 'y':evt.pageY - offsetY}
}
}
function draw(){
ctx.clearRect(0,0,el.width,el.height);
ctx.save();
ctx.translate(camera.x,camera.y);
ctx.beginPath();
ctx.rect(box.x-25, box.y-25,50,50);
ctx.fillStyle = 'red';
ctx.fill();
ctx.restore();
}
draw();
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.
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:
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.
I have a html5 canvas in which something is animated within a given time. what i'd like to do is repeat this code ("reset and restart" the drawing) in a certain interval.
var canvas = $("#paper")[0];
var c = canvas.getContext("2d");
var startX = 50;
var startY = 50;
var endX = 100;
var endY = 100;
var amount = 0;
setInterval(function() {
amount += 0.05; // change to alter duration
if (amount > 1) amount = 1;
c.clearRect(0, 0, canvas.width, canvas.height);
c.strokeStyle = "black";
c.moveTo(startX, startY);
// lerp : a + (b - a) * f
c.lineTo(startX + (endX - startX) * amount,
startY + (endY - startY) * amount);
c.stroke();
}, 30);
Assign the setInterval function to a variable. Then you can clear it
var interval = setInterval(function () {});
clearInterval(interval);
In you case (function calle setup) you could use:
var interval = setInterval(setup, 30);
And when you want to clear it call:
clearInterval(interval).