I have a point drawn in an canvas (html5). Then I want this point to animate in a circular path.
I saw an example using time differences to set the x and y variables, in respect to time. Some of the variables and formulas used are quite vague, I have forgotten my physics, d*mn. But I have researched quite a bit on circular motion, so I can understand some of it. Here is my codepen on how it was done.
Basically here are the parts I have identified so far:
this.orbit = 100; // this is the radius of the circular orbit
this.radius = 5; // orbiting object's radius
this.velocity = 50; // yeah velocity but without direction, should be speed (agree?)
var angle = 0; starting angle of the point in the orbit inside the canvas's quadrant,
set x and y coordinates with respect to the coordinates of the canvas
first get the center of the canvas by dividing the width and the height by 2
then adding to the product of the orbit's radius and the position of x and y
with respect to the initial position in the orbit(angle), and since Math trigonometric
functions uses radians, we should multiply it to the quotient of PI and 180.
this.x = _width / 2 + this.orbit * Math.cos(angle * Math.PI / 180)
this.y = _height / 2 + this.orbit * Math.sin(angle * Math.PI / 180)
// by doing the above, we now get the initial position of x and y in the orbit.
What is quite trivial to me are the next variables _dx and _dy and also the _magnitude.
Here is how the point is animated:
Point.prototype.update = function(dt) {
var dps = this.orbit * 2 * Math.PI / this.velocity;
var angle = (360 / dps) * dt / 1000 * -1;
this.vx = this.vx * Math.cos(angle * Math.PI / 180) - this.vy*Math.sin(angle * Math.PI / 180);
this.vy = this.vx * Math.sin(angle * Math.PI / 180) + this.vy*Math.cos(angle * Math.PI / 180);
var _magnitude = Math.sqrt( this.vx * this.vx + this.vy * this.vy);
this.vx = this.vx / _magnitude * this.velocity;
this.vy = this.vy / _magnitude * this.velocity;
this.x += this.vx * dt / 1000;
this.y += this.vy * dt / 1000;
}
And here is the execution of the script:
function animate () {
dt = new Date() - ldt;
if (dt < 500) {
// context.clearRect(0, 0, canvas.width, canvas.height);
point.update(dt);
point.draw(context);
};
ldt = new Date();
setTimeout(function() {
window.requestAnimationFrame(animate);
}, 1000 / 30)
}
ldt = new Date();
animate();
With the unclear variables, like _dx _dy _magnitude I cannot understand how it works and how the computation of variables, vx vy which I assume the velocity with respect to x and y respectively.
I wanted to use greensock tweenlite for the animation and it is done like so:
Point.prototype.update = function(p){
var _to = {
x: , // change the value of x
y: , // change the value of y
ease: Cubic.easeInOut,
onComplete: function () { this.update(p) }
}
TweenLite.to(point, 2, _to)
}
As you can see the first parameter is the current object (point), then the second parameter is the time, I assume this to be the velocity and the the third parameter is the change in the object's properties, x and y.
Question
I made the codepen, now How do I use gsap tweenlite to animate the circle like what I did, I suppose using tweenlite will make it a little simple.
In your case you are trying to use TweenLite to animate point as the crow flies, and you trigger TweenLite.to() function for each new position of point. This method of using TweenLite.to() function has no sense and performance, because distance between 2 position of point is too short. So, this method will only slow down your animation because instead of just draw point in new position you want to animate it.
The best solution in this case is trying to use TweenLite's methods to animate whole circle.
Take a look on this article: Tween around circle
Especially on these examples:
1) http://codepen.io/GreenSock/pen/jCdbq (not canvas, but it displays the main idea)
TweenMax.to("#logo", 4, {rotation:360, transformOrigin:"40px -100px", repeat:10, ease:Linear.easeNone});
2) and http://codepen.io/rhernando/pen/kjmDo
Related
I have created a blob with small points. I want my blob to show noise on its surface according to mouseX and mouseY. I want it to show high noise in the quadrant in which the mouse lies. I want it to be wavy. Below is my code.
var ctx = document.querySelector("canvas").getContext("2d");
var cx = 200;
var cy = 200;
var radius = 50;
var amp = 2;
var mouseX = 0;
var mouseY = 0;
document.querySelector("canvas").addEventListener("mousemove", function (e) {
mouseX = e.clientX;
mouseY = e.clientY;
});
function drawTheBlob() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 400);
ctx.beginPath();
ctx.strokeStyle = "#fff";
for (var a = 0; a < 360; a ++) {
var angle = a * Math.PI/180;
var x = cx + radius * Math.cos(angle) + Math.random() * amp;
var y = cy + radius * Math.sin(angle) + Math.random() * amp;
ctx.lineTo(x, y);
}
ctx.stroke();
ctx.closePath();
requestAnimationFrame(drawTheBlob);
}
drawTheBlob();
<canvas width="400" height="400"></canvas>
Adds a sin wave on the circle. Use ctx.arc to draw the flat part of the circle for speed as drawing many circles with lines will be slow. See code for comments on what is done.
var ctx = document.querySelector("canvas").getContext("2d");
ctx.lineWidth = 3;
ctx.lineJoin = "round";
var cx = 100;
var cy = 100;
var radius = 50;
var mouseX = 0;
var mouseY = 0;
const quadWidth = Math.PI / 2; // area of effect PI/2 is 90 degree
const steps = radius / quadWidth; // number steps around the circle matches 1 pixel per step,
const noiseAmpMax = 5; // in pixels
const noiseWaveMoveSpeed = 2; // speed of waves on circle in radians per second
const noiseWaveFreq = 16; // how many waves per 360 deg
document.querySelector("canvas").addEventListener("mousemove", function(e) {
mouseX = e.clientX;
mouseY = e.clientY;
});
function drawTheBlob(time) { // time is passed from the requestAnimationFrame call
var amp = 0; // amplitude of noise
var wavePos = ((time / 1000) * Math.PI) * noiseWaveMoveSpeed;
var mouseDir = Math.atan2(mouseY - cy, mouseX - cx);
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 400);
ctx.beginPath();
ctx.strokeStyle = "#fff";
ctx.fillStyle = "red";
// draw arc for parts that have no noise as it is a log quicker
ctx.arc(cx, cy, radius, mouseDir + quadWidth / 2, mouseDir + Math.PI * 2 - quadWidth / 2);
for (var a = 0; a < 1; a += 1 / steps) {
var angle = (mouseDir - quadWidth / 2) + a * quadWidth;
var angDist = Math.abs(angle - mouseDir); // find angular distance from mouse
// as a positive value, it does not mater
// what the sign is
if (angDist < quadWidth / 2) { // is angle distance within the range of effect
// normalise the distance (make it 0 to 1)
amp = 1 - angDist / (quadWidth / 2);
} else {
amp = 0; // no noise
}
// amp will be zero if away from mouse direction and 0 to 1 the closer to
// mouse angle it gets.
// add a sin wave to the radius and scale it by amp
var dist = radius + Math.sin(wavePos + noiseWaveFreq * angle) * noiseAmpMax * amp;
var x = cx + dist * Math.cos(angle);
var y = cy + dist * Math.sin(angle);
ctx.lineTo(x, y);
}
ctx.closePath(); // use close path to close the gap (only needed if you need to draw a line from the end to the start. It is not needed to match beginPath
ctx.fill();
ctx.stroke();
requestAnimationFrame(drawTheBlob);
}
requestAnimationFrame(drawTheBlob); // start this way so that you get the time argument
<canvas width="200" height="200"></canvas>
How it works.
Mouse direction
First we need the direction from the circle to the mouse. To do that we use the function Math.atan2 It takes the vector from the circle to the mouse and returns the direction in radians. The function is a little weird as it takes y first, then x.
var mouseDir = Math.atan2(mouseY - cy, mouseX - cx);
Draw arc to save CPU time
Now that we have the direction to the mouse we can draw the parts of the circle that has no noise using arc .
ctx.arc(cx, cy, radius, mouseDir + quadWidth / 2, mouseDir + Math.PI * 2 - quadWidth / 2);
The variable quadWidth is angular size of the noise bit so from the mouseDir we add half that angular width and draw the arc around to mouseDir plus 360deg take half the quadWidth.
Quick word on Radians
Almost all programming languages use radians to define angles, 360deg is equal to 2 * PI or 2 * 3.1415, which can be hard to get your head around, but there is good reason to use radians. For now just remember that a full circle in radians is 2 * Math.PI = 360deg, Math.PI = 180deg, Math.PI / 2 = 90deg, Math.PI / 4 = 45Deg and Math.PI / 180 = 1deg. You dont have to remember the digits just Math.PI is half a circle.
quadWidth from above is a constant defined as const quadWidth = Math.PI / 2; which is 90deg.
The for loop
The for loop only draws the (Math.PI / 2) 90deg section around the mouseDir, from 45 deg left to 45 right. or whatever you set quadWidth to.
for (var a = 0; a < 1; a += 1 / steps) {
I loop from 0 to 1 the number of steps that give a reasonably smooth curve. We can find what part of the noisy arc we are drawing by multiplying the value a *
quadWidth and adding that to the mouseDir - quadWidth / 2. This means that we start at mouseDir - 45deg and move clock wise to mouseDir + 45deg
var angle = (mouseDir - quadWidth / 2) + a * quadWidth;
Next i find how far that angle is from the mouseDir (could optimize it here a bit here but this way is a little more flexible, if you want to draw more noise on the other part of the arc)
var angDist = Math.abs(angle - mouseDir);
If that number is less than quadWidth / 2 convert the value to the range 0 to 1 where 0 is at the angle furthest from the mouse direction and 1 closest.
if (angDist < quadWidth / 2) {
amp = 1 - angDist / (quadWidth / 2);
} else {
amp = 0;
}
The sin wave
Now we calculate the radius of the circle at the current angle and add a sin wave to it. First the radius then the sin wave multiplied by the amp calculated in the last step. Where amp is zero none of the sin wave is added, where amp is 1 (in the direction of the mouse) the full sin wave is added.
var dist = radius + Math.sin(wavePos + noiseWaveFreq * angle) * noiseAmpMax * amp
The values wavePos, noiseWaveFreq, and noiseAmpMax control the animation of the sin wave. Play around with these values to get a feel of what they do, wavePos is calculated based on the time at the start of the function.
With dist we can calculate the x,y position for the next line of the circle
var x = cx + dist * Math.cos(angle);
var y = cy + dist * Math.sin(angle);
ctx.lineTo(x, y);
Experiment
I added some constants
const quadWidth = Math.PI / 2; // area of effect PI/2 is 90 degree
const steps = radius / quadWidth; // number steps around the circle matches 1 pixel per step,
const noiseAmpMax = 5; // in pixels
const noiseWaveMoveSpeed = 2; // speed of waves on circle in radians per second
const noiseWaveFreq = 16; // how many waves per 360 deg
To get a understanding what they do experiment and change the numbers to see what happens.
I created an algorithm to move a particle diagonally and it works fine using an angle. Basically, this is what I do:
this.x += this.speed * Math.cos(this.angle * Math.PI / 180);
this.y += this.speed * Math.sin(this.angle * Math.PI / 180);
this.draw();
How can I combine this with a zigzag movement?
I recommend calculating the lateral deviation from the normal path or amplitude which is given by
// Triangle wave at position t with period p:
function amplitude(t, p) {
t %= p;
return t > p * 0.25 ? t < p * 0.75 ? p * 0.5 - t : t - p : t;
}
where t will be set to the length of the traveled path, and p is the period of the 'zigzag' triangle wave pattern.
Given the amplitude and the previous position, we can now easily compute the next position by moving ahead as described by your original code and then adding the lateral deviation to our position:
var amplitude = amplitude(distance, p) - this.amplitude(previous_distance, p);
this.x += amplitude * Math.sin(this.angle * Math.PI/180);
this.y -= amplitude * Math.cos(this.angle * Math.PI/180);
A complete example with two movable objects, one moving 'normally' and one following a 'zigzag' pattern:
function Movable(x, y, speed, angle, period) {
this.x = x;
this.y = y;
this.speed = speed;
this.angle = angle;
this.period = period;
this.distance = 0;
}
Movable.prototype.moveDiagonal = function() {
this.distance += this.speed;
this.x += this.speed * Math.cos(this.angle * Math.PI / 180);
this.y += this.speed * Math.sin(this.angle * Math.PI / 180);
}
Movable.prototype.amplitudeZigZag = function() {
var p = this.period, d = this.distance % p;
return d > p * 0.25 ? d < p * 0.75 ? p * 0.5 - d : d - p : d;
}
Movable.prototype.moveZigZag = function() {
var amplitude1 = this.amplitudeZigZag();
this.moveDiagonal();
var amplitude2 = this.amplitudeZigZag();
var amplitude = amplitude2 - amplitude1;
this.x -= amplitude * Math.sin(this.angle * Math.PI/180);
this.y += amplitude * Math.cos(this.angle * Math.PI/180);
}
Movable.prototype.draw = function(context) {
context.beginPath();
context.arc(this.x, this.y, 1, 0, 2 * Math.PI);
context.stroke();
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var m1 = new Movable(0, 0, 2, 0, 50);
var m2 = new Movable(0, 0, 2, 0, 50);
for (var i = 0; i < 1000; ++i) {
m1.angle += Math.cos(i * Math.PI/180);
m2.angle += Math.cos(i * Math.PI/180);
m1.moveDiagonal();
m2.moveZigZag();
m1.draw(context);
m2.draw(context);
}
<canvas id="canvas" width="600" height="200"></canvas>
So let's say that you're moving in the direction this.angle and you want to zig-zag in that direction moving side to side ±45° from that direction. All you need to do is have a variable like var zigzag = 45; and add zigzag to this.angle when calculating the new positions. And to make it zig-zag, you need to negate it every so often like this zigzag *= -1;. If you want to stop zig-zagging, set zigzag = 0;.
The trick is knowing when to alternate between ±45°. Maybe you can have the switch timed and keep a reference to the last time you switched using Date.now();. You can check the difference between the current time and the recorded time, then negate zigzag once you've surpassed a certain number of milliseconds. Just remember to record the new time of the last switch. You could also keep track of distance travelled and use the same approach, whatever works for you.
Im trying to make an particle move towards where on canvas was clicked however the formula returns NaN. Why is this and how can I fix this?
step 1: Particle gets placed at player.x, player.y properly.
step 2: At first tick the particle goes to the upper left point of the canvas without x,y position.
Screenie of particle props
example at jsfiddle
Particle Object
function Shuriken(mouseX, mouseY) {
this.rot = 0;
this.vel = 2;
this.angle = 0;
this.x = player.x || WIDTH / 2;
this.y = player.y || HEIGHT / 2;
this.mouseX = mouseX || WIDTH /2;
this.mouseY = mouseY || HEIGHT /2
this.width = shurikenImg.width;
this.height = shurikenImg.height;
}
update on tick
Shuriken.prototype.move = function() {
this.angle = Math.atan2(this.mouseY, this.x) * (180 / Math.PI);
this.x += this.vel * Math.cos(this.angle);
this.y += this.vel * Math.sin(this.angle);
}
You convert angle from radians into degrees. Nicer to debug (because degrees are still taught in school) but alas, the sin and cos in the next lines expect their arguments to be in radians again. Granted, the functions ought still to work; but the values you get are no longer what you expect.
this.vel is undefined in the jsfiddle. This is most likely what lead to the observed NaN.
This is a wrong calculation:
this.x = this.vel * Math.cos(this.angle);
this.y = this.vel * Math.sin(this.angle);
You don't want the x and y positions calculated this way. Add or subtract the (adjusted) velocity from the x/y coordinates.
I have circle with a line from the center to the edge of the circle. The user can click on and drag the line and get the degrees from wherever they set the line. I'm using the code from the answer here and have adjusted the code accordingly.
Everything works fine, but the farther down the page the element affected by raphael is, the farther outside of the circle the mouse has to be in order to drag the line. jsfiddle
var canvas = Raphael('pivot', 0, 0, 320, 320);
var clock = canvas.circle(200, 150, 100).attr("stroke-width", 2);
canvas.circle(200, 150, 3).attr("fill", "#000");
var angleplus = 360,
rad = Math.PI / 180,
cx = 200,
cy = 150,
r = 90,
startangle = -90,
angle = 90,
x, y, endangle;
for (i = 1; i < 5; i++) {
endangle = startangle + angle;
x = cx + r * Math.sin(endangle * rad);
y = cy - r * Math.cos(endangle * rad);
canvas.text(x, y, endangle);
startangle = endangle;
}
var hand = canvas.path("M200 50L200 150").attr("stroke-width", 5);
hand.drag( move, start, end );
function move (dx,dy,x,y) {
var pt = this.node.ownerSVGElement.createSVGPoint();
pt.x = x;
pt.y = y;
var angle = ( 90 + Math.atan2(pt.y - clock.attr('cy') - 5, pt.x - clock.attr('cx') - 5 ) * 180 / Math.PI + 360) % 360;
this.rotate(angle, 200,150);
this.angle = angle;
}
function start() {
};
function end() {
alert(parseInt(this.angle));
}
I'm wondering why this happens and if it's even fixable?
Here is an updated original one, a bit added and another bit of redundancy removed, and I've added an extra fiddle to the original answer to reflect it.
The key bits I've added are...
var cpt = clock.node.ownerSVGElement.createSVGPoint();
cpt.x = clock.attr('cx');
cpt.y = clock.attr('cy');
cpt = cpt.matrixTransform(clock.node.getScreenCTM());
The centre of the clock has now moved, so using cx of the circle alone, doesn't really make sense relative to the mouse event. We need to take into account any offset and transforms on the screen to the clock.
We can get this with getScreenCTM (get the matrix from element to the screen), and we can use that matrix with the original cx, cy to figure out where it is on the screen.
Then later, instead of cx, cy, we use the new adjusted coordinates cpt.x/y
var angle = ( 90 + Math.atan2(y - cpt.y - 5, x - cpt.x - 5 ) * 180 / Math.PI + 360)
jsfiddle - drag any hand
I am using time based movement, but its still super choppy. this code shoots in arrow in the air then calculates the difference in movement to determine to rotation of the arrow. It works most of the time, but sometimes it just jitters.
for(i=0;i<this.arrows.length;i++){
a = this.arrows[i];
point0 = [a.x,a.y];
x_speed = e.delta/1000*a.speed * Math.cos(a.angle * Math.PI / 180);
y_speed = e.delta/1000*a.speed * Math.sin(a.angle * Math.PI / 180);
a.x += x_speed;
a.y += y_speed;
a.y += a.velocity;
a.velocity += e.delta/1000*this.gravity;
alert(e.delta);
ang = Math.atan2(point0[1]-a.y,point0[0]-a.x);
ang = (ang * (180/Math.PI))+180;
a.rotation = ang;
}
1) I'm surprised you are using a.angle, ang and a.rotation. I guess ang is a var declared before the for loop, but isn't there an issue here ?
2) You might use radians, not degrees, to avoid conversions.
3) There's also an issue with speed vs velocity : you don't update speed (linear speed) when you change velocity (speed on Y).
•• Anyway there's a simpler way of proceeding : compute speed/position just like for a regular point moving, then use the speed vector to compute where the end of the arrow is.
It works fine in this fiddle : http://jsbin.com/dexaroriwixo/1/
update :
var i=0, a=null, delta_ms = 1e-3 * e.delta;
for(i=0;i<this.arrows.length;i++){
a = this.arrows[i];
a.x += delta_ms*a.speedX;
a.y += delta_ms*a.speedY;
a.speedY += delta_ms*this.gravity;
a.speedNorm = Math.sqrt(sq(a.speedX)+sq(a.speedY));
}
to draw :
Arrow.prototype.draw = function(ctx) {
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x+ this.length*this.speedX/this.speedNorm,
this.y+ this.length*this.speedY/this.speedNorm );
ctx.strokeStyle = '#F00';
ctx.stroke();
}
to 'launch' an arrow,
Arrow.prototype.launch(initialSpeed, angle) {
this.speedX = initialSpeed * Math.cos(angle);
this.speedY = initialSpeed * Math.sin(angle);
}