canvas pendulum animation - canvas translating - javascript

I'm trying to make simple pendulum in HTML5 Canvas but I'm stuck. I want to swing it for 25 degrees to the left and to the right, so I calculated I should translate every frame about -3.5 px in y axis (and 3.5 px when swings to the right). I'm using below code
var rotation = Math.PI/180, //rotate about 1deg
translation = -3.5,
counter = 0; //count rotations
function draw() {
var element = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.clearRect(0,0,element.width,element.height);
ctx.translate(0, translation);
ctx.rotate(rotation);
//function draws all objects
objects(element,ctx);
if (counter == 25) {
rotation *= -1;
translation *= -1;
counter = -25;
}
counter += 1;
window.requestAnimationFrame(draw);
}
Everything looks good but when pendulum is changing direction then everything is translating in also x axis and after few seconds disappears from screen.. What is wrong in this code? Or maybe I was miss something in my calculations? My code here https://jsfiddle.net/qskxjzv9/2/
Thanks in advance for your answers.

The problem is that when there is rotation involved, then translation, the x and y's will be translated in a different direction than what may seem logic.
To get around this we don't actually have to involve translation more than using it for placing pivot (point of rotation) and then use absolute rotation based on a different way of calculating the pendulum movement.
For example, this will take care of both the translation problem as well as smoothing the pendulum movement:
Change the draw method to draw the pendulum with origin (0,0) - it's just a matter of changing the initial coordinates so they evolve around (0,0)
Translate to pivot point of screen - this is where the rotation will take place.
Rotate using sin() as a factor - this will create a smooth animation and look more like a pendulum and it will restrict the movement to angle as range is [-1,1]
Use counter to move sin() instead - this acts as a frequency-ish factor (you can later convert this into an actual frequency to say, have the pendulum move n number of times per minute etc.). To keep it simple I have just used the existing counter variable and reduced its step value.
The main code then:
var maxRot = 25 / 180 * Math.PI, // max 25° in both directions
counter = 0,
// these are better off outside loop
element = document.getElementById('canvas');
ctx = element.getContext('2d');
function draw() {
// reset transform using absolute transformation. Include x translation:
ctx.setTransform(1,0,0,1,element.width*0.5,0);
// clear screen, compensate for initial translate
ctx.clearRect(-element.width*0.5,0,element.width,element.height);
// rotate using sin() with max angle
ctx.rotate(Math.sin(counter) * maxRot);
// draw at new orientation which now is pivot point
objects(element, ctx);
// move sin() using "frequency"-ish value
counter += 0.05;
window.requestAnimationFrame(draw);
}
Fiddle
Additional
Thanks to #Blindman67 for providing additional improvements:
To control frequency in terms of oscillations you could do some minor changes - first define frequency:
var FREQUENCY = 3;
Define a function that will do the conversion:
function sint(time) {
return Math.sin(FREQUENCY * time * Math.PI * 0.002); // 0.002 allow time in ms
}
If you now change the draw() method to take a time parameter instead of the counter:
function draw(time) {
...
}
Then you can call rotation like this:
ctx.rotate(sint(time) * maxRot);

you need to translate the origin to the point you want to rotate around:
ctx.translate(element.width / 2, 0);
Then, the rotation as you suggest:
ctx.rotate(rotation);
And finally, translate back:
ctx.translate(- element.width / 2, 0);
See this commented fork of your fiddle.

Related

detect collision between two circles and sliding them on each other

I'm trying to detect collision between two circles like this:
var circle1 = {radius: 20, x: 5, y: 5}; //moving
var circle2 = {radius: 12, x: 10, y: 5}; //not moving
var dx = circle1.x - circle2.x;
var dy = circle1.y - circle2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < circle1.radius + circle2.radius) {
// collision detected
}else{
circle1.x += 1 * Math.cos(circle1.angle);
circle1.y += 1 * Math.sin(circle1.angle);
}
Now when collision is detected I want to slide the circle1 from on the circle2 (circle1 is moving) like this:
--circle1---------------------------------circle2-------------------------
I could do this by updating the angle of circle1 and Moving it toward the new angle when collision is detected.
Now My question is that how can I detect whether to update/increase the angle or update/decrease the angle based on which part of circle2 circle1 is colliding with ?? (circle one comes from all angles)
I would appreciate any help
This will depend a bit on how you are using these circles, and how many will ever exist in a single system, but if you are trying to simulate the effect of two bodies colliding under gravity where one roles around to the edge then falls off (or similar under-thrust scenario), then you should apply a constant acceleration or velocity to the moving object and after you compute it's movement phase, you do a displacement phase where you take the angle to the object you are colliding with and move it back far enough in that direction to reach circle1.radius + circle2.radius.
[edit] To get that redirection after falling though (not sure if you intended this or if it's just your sketch), there is probably going to be another force at play. Most likely it will involve a "stickiness" applied between the bodies. Basically, on a collision, you need to make sure that on the next movement cycle, you apply Normal Movement, then movement towards the other body, then the repulsion to make sure they don't overlap. This way it will stick to the big circle until gravity pulls way at enough of a direct angle to break the connection.
[edit2] If you want to make this smoother and achieve a natural curve as you fall away you can use an acceleration under friction formula. So, instead of this:
circle1.x += 1 * Math.cos(circle1.angle);
circle1.y += 1 * Math.sin(circle1.angle);
You want to create velocity properties for your object that are acted on by acceleration and friction until they balance out to a fixed terminal velocity. Think:
// constants - adjust these to get the speed and smoothness you desire
var accelerationX = 1;
var accelerationY = 0;
var friction = 0.8;
// part of physics loop
circle1.velX += (accelerationX * Math.cos(circle1.angle)) - (friction * circle1.velX);
circle1.velY += (accelerationY * Math.sin(circle1.angle)) - (friction * circle1.velX);
circle1.x += circle1.velX;
circle1.y += circle1.velY;
This way, when things hit they will slow down (or stop), then speed back up when they start moving again. The acceleration as it gets back up to speed will achieve a more natural arc as it falls away.
You could get the tangent of the point of contact between both circles, which would indicate you how much to change your angle compared to the destination point (or any horizontal plane).

Subtract opacity instead of multiplying

I'm trying to make it appear as though movement on my <canvas> creates motion trails. In order to do this, instead of clearing the canvas between frames I reduce the opacity of the existing content by replacing a clearRect call with something like this:
// Redraw the canvas's contents at lower opacity. The 'copy' blend
// mode keeps only the new content, discarding what was previously
// there. That way we don't have to use a second canvas when copying
// data
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = 0.98;
ctx.drawImage(canvas, 0, 0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
However, since setting globalAlpha multiplies alpha values, the alpha values of the trail can approach zero but will never actually reach it. This means that graphics never quite fade, leaving traces like these on the canvas that do not fade even after thousands of frames have passed over several minutes:
To combat this, I've been subtracting alpha values pixel-by-pixel instead of using globalAlpha. Subtraction guarantees that the pixel opacity will reach zero.
// Reduce opacity of each pixel in canvas
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Iterates, hitting only the alpha values of each pixel.
for (let i = 3; i < data.length; i += 4) {
// Use 0 if the result of subtraction would be less than zero.
data[i] = Math.max(data[i] - (0.02 * 255), 0);
}
ctx.putImageData(imageData, 0, 0);
This fixes the problem, but it's extremely slow since I'm manually changing each pixel value and then using the expensive putImageData() method.
Is there a more performant way to subtract, rather than multiplying, the opacity of pixels being drawn on the canvas?
Unfortunately there is nothing we can do about it except from manually iterating over the pixels to clear low-value alpha pixels like you do already.
The problem is related to integer math and rounding (more details at this link, from the answer).
There are blending modes such as "luminosity" (and to a certain degree "multiply") which can be used to subtract luma, the problem is it works on the entire surface contrary to composite modes which only works on alpha - there is no equivalent in composite operations. So this won't help here.
There is also a new luma mask via CSS but the problem is that the image source (which in theory could've been manipulated using for example contrast) has to be updated every frame and basically, the performance would be very bad.
Workaround
One workaround is to use "particles". That is, instead of using a feedback-loop instead log and store the path points, then redraw all logged points every frame. Using a max value and reusing that to set alpha can work fine in many cases.
This simple example is just a proof-of-concept and can be implemented in various ways in regards to perhaps pre-populated arrays, order of drawing, alpha value calculations and so forth. But I think you'll get the idea.
var ctx = c.getContext("2d");
var cx = c.width>>1, cy = c.height>>1, r = c.width>>2, o=c.width>>3;
var particles = [], max = 50;
ctx.fillStyle = "#fff";
(function anim(t) {
var d = t * 0.002, x = cx + r * Math.cos(d), y = cy + r * Math.sin(d);
// store point and trim array when reached max
particles.push({x: x, y: y});
if (particles.length > max) particles.shift();
// clear frame as usual
ctx.clearRect(0,0,c.width,c.height);
// redraw all particles at a log. alpha, except last which is drawn full
for(var i = 0, p, a; p = particles[i++];) {
a = i / max * 0.6;
ctx.globalAlpha = i === max ? 1 : a*a*a;
ctx.fillRect(p.x-o, p.y-o, r, r); // or image etc.
}
requestAnimationFrame(anim);
})();
body {background:#037}
<canvas id=c width=400 height=400></canvas>

Drawing points on a canvas with Math.PI calculations

I'm having some math problems with drawing points on a canvas that are spaced out around a circle.
I have the radius, the spacing of each point and even the angles around the circle but the issue is I want it to start at a specified angle and end at a specified angle.
Code
function getPoints(x,y,radius,ticks)
{
var spacing = Math.PI*2/ticks;
var points = [];
for(var i=0; i<ticks;i++)
{
var angle = (spacing * i)+((ticks*Math.PI)/ticks);
var x1 = x+radius*Math.cos(angle);
var y1 = y+radius*Math.sin(angle);
points.push({'x':x1,'y':y1});
}
return points;
}
I'm having difficulty figuring out the needed math.
here is also a jsFiddle of the project: http://jsfiddle.net/Keleko34/EMeG2/
to help get the idea, the degrees I want to start at are -45 and end at 225.
the current degrees it starts at is 0 and it does the entire 360. as seen above code and example :/
Your spacing value is based on 360 degrees (or Math.PI*2 radians).
Your starting value (see the angle calculation) is Math.PI (or 180 degrees).
Your span is therefore 180 degrees to 540 degrees (Math.PI to 3*Math.PI radians).
You likely need to change your angle calculation (which should probably be renamed to radians) to have a different starting angle.
You also need to modify your spacing calculation to be based on the number of degrees/radians of your desired arc.

Algorithm for moving an object horizontally in javascript

I am currently working on a game using javascript and processing.js and I am having trouble trying to figure out how to move stuff diagonally. In this game, there is an object in the center that shoots other objects around it. Now I have no problem moving the bullet only vertically or only horizontally, however I am having difficulty implementing a diagonal motion for the bullet algorithm.
In terms of attempts, I tried putting on my math thinking cap and used the y=mx+b formula for motion along a straight line, but this is what my code ends up looking like:
ellipse(shuriken.xPos, shuriken.yPos, shuriken.width, shuriken.height); //this is what I want to move diagonally
if(abs(shuriken.slope) > 0.65) {
if(shuriken.targetY < shuriken.OrigYPos) {
shuriken.yPos -= 4;
} else {
shuriken.yPos += 4;
}
shuriken.xPos = (shuriken.yPos - shuriken.intercept)/shuriken.slope;
} else {
if(shuriken.targetX < shuriken.OrigXPos) {
shuriken.xPos -= 4;
} else {
shuriken.xPos += 4;
}
shuriken.yPos = shuriken.slope * shuriken.xPos + shuriken.intercept;
}
The above code is very bad and hacky as the speed varies with the slope of the line.
I tried implementing a trigonometry relationship but still in vain.
Any help/advice will be greatly appreciated!
Think of it this way: you want the shuriken to move s pixels. If the motion is horizontal, it should move s pixels horizontally; if vertical, s pixels vertically. However, if it's anything else, it will be a combination of pixels horizontally/vertically. What's the correct combination? Well, what shape do you get if you project s distance in any direction from a given point? That's right, a circle with radius s. Let's represent the direction in terms of an angle, a. So we have this picture:
How do we get the x and the y? If you notice, we have a triangle. If you recall your trigonometry, this is precisely what the sine, cosine, and tangent functions are for. I learned their definitions via the mnemonic SOHCAHTOA. That is: Sin (a) = Opposite/Hypotenuse, Cos(a) = Adjacent/Hypotenuse, Tan(a) = Opposite/Adjacent. In this case, opposite of angle a is y, and adjacent of angle a is x. Thus we have:
cos(a) = x / s
sin(a) = y / s
Solving for x and y:
x = s * cos(a)
y = s * sin(a)
So, given the angle a, and that you want to move your shuriken s pixels, you want to move it s * cos(a) horizontally and s * sin(a) vertically.
Just be sure you pass a in radians, not degrees, to javascript's Math.sin and Math.cos functions:
radians = degrees * pi / 180.0
This may be why your trigonometric solution didn't work as this has bitten me a bunch in the past.
If you know the angle and speed you are trying to move at, you can treat it as a polar coordinate, then convert to cartesian coordinates to get an x,y vector you would need to move the object by to go in that direction and speed.
If you don't know the angle, you could also come up with the vector by taking the difference in X and difference in Y (this I know you can do as you are able to calculate the slope between the 2 points). Then take the resulting vector and divide by the length of the vector to get a unit vector, which you can then scale to your speed to get a final vector in which you can move your object by.
(This is what probably what kennypu means by sticking with vectors?)

Scale a dot's X Y on HTML5 canvas by percentage

I'm working on my first canvas project, and it requires a partial map of the US, with a zoom and center on a state when clicked.
I was able to find X Y arrays of points to draw the country, with each state being its own array. I needed the states to be drawn out larger then these dimensions, so I introduced a scale varaible to multiply each point by.
My next challenge was that the client only wanted 13 states drawn out, but not placed to scale against each other. (Example, put Ohio and Illinois next to each other on the canvas and ignore Indiana). My solution to that was to introduce a fixed X, Y "constant" for each state, that after the scaling happens, add the X Y value for that state and make that the spot to draw on.
for ( var j = 0; j < state.myPolygons.length; ++j) {
context.beginPath();
context.lineWidth = lineWidth;
context.strokeStyle = stateStroke;
context.fillStyle = stateFill;
for ( var k = 0; k < state.myPolygons[j].myXVals.length; ++k ) {
var x = parseFloat(state.myPolygons[j].myXVals[k]*state.scale)+state.posX;
var y = parseFloat(state.myPolygons[j].myYVals[k]*state.scale)+state.posY;
y = canvas.height - y;
if ( k == 0 )
context.moveTo(x,y);
else
context.lineTo(x,y);
}
context.closePath();
context.fill();
context.stroke();
}
The effect of clicking on a state, and growing it and centering on the canvas was accomplished by defining a target scale and number of steps. I get the difference between the target scale and current scale, and divide that by number of steps to figure out how much to add to the scale of the state at each "frame".
Example: Ohio's initial scale is 1.97 of the found coords. My target for Ohio scale is 3.75%. I get the difference (1.78), and divide that by 45 (the defined set of steps) to draw. This gives me 0.039 as an incrementer to my scale at each frame. I then loop through while my states current scale is less than the target scale. Again however, since I need to manipulate the X Y of the rendering, I have then a zoomx and zoomy constant for each state that gets added to the calculated X Y so it can "slide" to the center of the canvas.
All of this works perfectly and I have California zoom/sliding from left to right, Ohio sliding right to left, etc. --- Here is my problem.
I have a series of dots to indicate client loctions in the state. These are simple X Ys that I draw a circle on. The initial rendering of the map includes a loop to run through each states set of locations. I'm applying the same scale factor, and posX,posY variables to adjust final placement of the dot in relation to final rendering of the state
for (var loc in state.Locations) {
var locx = parseFloat(state.Locations[loc].x*state.scale)+state.posX
var locy =parseFloat(state.Locations[loc].y*state.scale)+state.posY;
var txt=state.Locations[loc].text;
var lnk=state.Locations[loc].link;
context.beginPath();
context.arc(locx,locy,locationSize,0,Math.PI*2,true);
context.fillStyle = locationFill;
context.closePath();
context.fill();
context.stroke();
}
When the state is zooming however, the scaling logic for the dots fails. The state scale for a given frame applies
x = parseFloat(activeState.myPolygons[j].myXVals[k]*activeState.scale)+activeState.posX;
y = parseFloat(activeState.myPolygons[j].myYVals[k]*activeState.scale)+activeState.posY;
When I apply this to a given location in the state with
locx = parseFloat(activeState.Locations[loc].x*activeState.scale)+activeState.posX;
locy = parseFloat(activeState.Locations[loc].y*activeState.scale)+activeState.posY;
I end up with X following pretty closely, but in Ohio's example, the Y is somewhere near Florida. Other states like California are even worse with their dots starting more "stacked" on top of each other and end up more "spread out" beside each other.
I'm trying to figure out the trig functions needed to grow and shrink the position of the X Y on a location in relation to the current scale of the state, and keep it on the same path the state is traveling on through the animation (both zooming in and zooming out).
My final attempt before coming here was to get the inital X Y of the location, and compare its distance to the LAST X Y of the state array. I was trying to then find the angle of the line connecting those 2 points, and then use all this to scale. I still feel that I may be onto something with this approach, I just can't make it happen.
Thank you everyone for taking the time to read this, I appriciate any help you can offer
You could just look at the paper I put on your desk, the one with the equation on it. However, SVGs would be more optimal for the project, as you could easily group things together using the g tag and then could just scale the entire group.
However, since you're forced to use canvas at this point: You would have to scale up and down director, using trig given the angle of the start point to location dot and the DIFFERENCE of left or right travelled from the original distance. I will explain in more detail, with actual equations, when you allow me to give me that paper back. However, the only line you really need to modify at this point is:
locy = parseFloat(activeState.Locations[loc].y*activeState.scale)+activeState.posY;

Categories

Resources