I am studying JavaScript and I discovered the following article: http://www.html5code.nl/tutorial-canvas-animation-spiral-movement/
Could you tell me how does the function spiralMotion1() works?
I would like to customize speed and distance.
edit: breaking it down to specifics: why use cos. and sin.? why use rotationRadius? how does the setAngle function influence the result? where does the degrees variable come into play?
the code:
function spiralMotion1(){
var degrees = 0;
var Angle;
var rotationRadius=2;
var rotationRadiusIncrease = 1;
var ballRadius=20
var centerX;
var centerY;
var x;
var y;
var animate=true;
var breadcrumbs = new Array();
var crumbRadius=1;
var canvas = jQuery("#spiral_motion1");
var context = canvas.get(0).getContext("2d");
//function Ball(x,y,radius,color,strokeColor,lineWidth) in ball.js
var ball_3 = new Ball(-10,-10,20,'#f00','#000',7);
var parentWidth=jQuery(canvas).parent().width();
var canvasWidth=context.canvas.width = parentWidth;
var canvasHeight=context.canvas.height= 288;
if (!checkForCanvasSupport) {
return;
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, canvas);
if(animate){
context.clearRect(0,0,canvasWidth,canvasHeight); // clear canvas
//Make the Canvas element responsive for desktop, tablet and smartphone.
centerX = canvasWidth/2;
centerY = canvasHeight/2
Angle = degrees * (Math.PI / 180);
degrees = degrees + 1;
ball_3.x=rotationRadius * Math.cos(setAngle()) + centerX;
ball_3.y=rotationRadius * Math.sin(setAngle()) + centerY;
ball_3.draw(context);
//add a breadcrumb to the breadcrumbs array
breadcrumbs.push({x:ball_3.x,y:ball_3.y});
//draw the breadcrumbs that shows the track of the movement
context.globalCompositeOperation = "destination-over";
showBreadcrumbs(breadcrumbs);
rotationRadius += rotationRadiusIncrease/5
if ((ball_3.y + ballRadius+4) > canvas.height()){
animate=false;
}
}
}());//end drawFrame
function setAngle(){
Angle = degrees * (Math.PI / 180);
degrees = degrees + 2;
return Angle;
}//end setAngl()
function showBreadcrumbs(breadcrumbs){
for (var i = 0; i< breadcrumbs.length; i++) {
context.beginPath();
context.arc(breadcrumbs[i].x,breadcrumbs[i].y,crumbRadius,0, 2*Math.PI,false);
context.closePath();
context.fillStyle="#999";
context.fill();
}
}//end showBreadcrumbs()
}//end spiralMotion1()
It boils down to basic geometry. If you think of a body orbiting a point in 2D, it's movement can be characterised by a radius (distance from the orbited point), and an angle which is a function of time. If you know the radius and the angle, then you can calculate the body position with the cos and sin function.
]1
By changing the radius over time, you obtain a spiral instead of a simple circle.
Related
I am writing a simple JavaScript game where you are looking for a hidden image on a page. When you click on it the image appears. On every click a sound bite plays. It is essentially Marco-polo. As you get closer to the hidden object I want the volume of the sound bite to get louder. I have this working however with a linear relation between distance and volume it is quite hard to nail down exactly where the image is, so, I want to develop a relation where there is a really steep volume incline as you get really close. Something along the lines of y = x^5. Doesn't have to be x^5 but this is sort of what I have in mind.
Now, the image is placed on the page at page load randomly centred at the point (imgX, imgY). The page has dimensions (pageX, pageY) and I click on the screen at (clickX, clickY).
Now, my thinking is that there will always be a 'largest distance' LD on the page from the image coordinates (in theory this should be a corner on the screen). We can simply get the coordinates for the four corners and find the biggest distance, NBD.
The volume, going from 0 to 1 should have a function similar to
V = 1 - D
Where D is some relation I can't nail down right now.
To get a simple linear relation I am currently using
D = d / LD
Where
d = sqrt((imgX - clickX)^2 + (imgY - clickY)^2)
Edit
Just thought I would clarify my intention: 1-d/LD works however this causes a straight line increase in volume as you get closer. It's not intuitively clear but in practice as you get around 80%-100% volume it all sounds very much the same meaning that the area around the image seems to have the same volume to the human ear. I want a much more dramatic increase as you get really close. I.e., it should only get to above 80% volume when within 3-4% distance (if that makes sense)
Further to my earlier comment, here's a visualisation of what I think you need.
I've just realised that I didn't bother to re-calculate the distance from the most distant corner - I've simply used the distance from the centre of the square to the corners. This omission is the reason the red dot may be drawn to the left of the Y axis if the distance to the target exceeds the distance from the centre of the square to a corner.
Clicking on the 2nd canvas re-positions the hidden target. Moving the mouse will cause it's distance to this target to be computed. This value will then be divided by the above-mentioned max-distance-to-a-corner figure.
Finally, this value will be used as the X-coordinate of the attenuation function. The value [0..1] will be used to drive the resultant volume.
I've left a variable, steepnessFactor in the code for quick and easy modification of the attenuation curve. This value is simply the one to which the linear distance is raised to the power of.
function allByClass(clss,parent){return (parent==undefined?document:parent).getElementsByClassName(clss)}
function byId(id){return document.getElementById(id)}
window.addEventListener('load', onDocLoaded, false);
var steepnessFactor = 5; // inputs [0..1] will be raised to this power
var visSize = 128; // width/height of the 2 canvases
// click pos and corners of our window
var targetPoint;
var topLeft, topRight, botLeft, botRight;
// initialized to dist from center to (any) corner
var maxDist = (Math.sqrt(2) * visSize) / 2;
function onDocLoaded(evt)
{
targetPoint = new vec2_t(visSize/2,visSize/2);
topLeft = new vec2_t(0,0);
topRight = new vec2_t(visSize,0);
botLeft = new vec2_t(0,visSize);
botRight = new vec2_t(visSize,visSize);
var can1 = byId('graph');
var can2 = byId('map');
can1.width = visSize;
can1.height = visSize;
can2.width = visSize;
can2.height = visSize;
byId('map').addEventListener('click', onMapClicked, false);
byId('map').addEventListener('mousemove', onMapMouseMoved, false);
drawGraph();
drawMap(byId('map'));
}
function drawGraph()
{
var can = byId('graph');
var ctx = can.getContext('2d');
ctx.clearRect(0,0,can.width,can.height);
// draw the axis lines
ctx.strokeStyle = "#555555";
ctx.moveTo(0,can.height/2);
ctx.lineTo(can.width, can.height/2);
ctx.moveTo(can.width/2, 0);
ctx.lineTo(can.width/2, can.height);
ctx.stroke();
// draw the unit markers (spaced at 0.1 unit intervals)
var numDivisions = 20;
for (var x=0; x<can.width; x+= can.width/(numDivisions) )
{
ctx.moveTo(x, (can.height/2) - 4 );
ctx.lineTo(x, (can.height/2) + 4 );
}
for (var y=0; y<can.height; y+= can.height/(numDivisions) )
{
ctx.moveTo( (can.width/2)-4, y);
ctx.lineTo( (can.width/2)+4, y);
}
ctx.stroke();
var scaleX = 2 / can.width;
var scaleY = 2 / can.height;
ctx.beginPath();
ctx.moveTo(0,can.height);
for (var curX=0; curX<can.width; curX++)
{
var scaledX = -1;
scaledX += curX * scaleX;
var curY = Math.pow( scaledX, steepnessFactor); // steepness of curve
curY *= can.height/2;
curY = can.height/2 - curY;
ctx.lineTo(curX, curY);
}
ctx.strokeStyle = "#7e6cb5";
ctx.stroke();
}
function vec2_t(x,y)
{
this.x=x;
this.y=y;
this.equals = function(vec2){this.x = vec2.x; this.y = vec2.y;}
this.addVec = function(vec2){this.x += vec2.x; this.y += vec2.y;}
this.scalarMult = function(scalar){this.x *= scalar; this.y *= scalar;}
this.vecLen = function(){return Math.sqrt( this.x*this.x + this.y*this.y );}
this.normalize = function(){ let k = 1.0 / this.vecLen(); this.scalarMult(k); }
this.vecSub = function(vec2){this.x-=vec2.x;this.y-=vec2.y;}
this.toString = function(){return"<"+this.x+","+this.y+">"}
return this;
}
function onMapClicked(evt)
{
targetPoint.x = evt.offsetX;
targetPoint.y = evt.offsetY;
drawMap(this);
}
function drawMap(canvasElem)
{
var ctx = canvasElem.getContext('2d');
ctx.clearRect(0,0,canvasElem.width,canvasElem.height);
var radius = 5;
ctx.beginPath();
ctx.arc(targetPoint.x, targetPoint.y, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'green';
ctx.fill();
}
function onMapMouseMoved(evt)
{
var x = evt.offsetX, y = evt.offsetY;
var curPos = new vec2_t(x, y);
var curVec = new vec2_t();
curVec.equals( curPos );
curVec.vecSub( targetPoint );
var curDist = curVec.vecLen();
var linearDist = (1-(curDist/maxDist));
// console.log("CurDist / MaxDist = " + linearDist );
// console.log("CurValue = " + Math.pow(linearDist, 5) );
x = linearDist;
y = Math.pow(linearDist, steepnessFactor); // steepness of curve
setVolumeSVG(y * 100);
drawGraph();
var mapCan = byId('graph');
var ctx = mapCan.getContext('2d');
var scaleX = mapCan.width / 2;
var scaleY = -mapCan.height / 2;
var radius = 5;
ctx.beginPath();
ctx.arc( x*scaleX + mapCan.width/2,
y*scaleY + mapCan.height/2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.beginPath();
}
function setVolumeSVG(percent)
{
var svg = byId('mSvg');
var barWidth = (percent/100) * svg.width.baseVal.value;
var barHeight = (percent/100) * svg.height.baseVal.value;
var msg = "0,"+svg.height.baseVal.value + " "
+ barWidth + "," + (svg.height.baseVal.value-barHeight) + " "
+ barWidth + "," + svg.height.baseVal.value;
allByClass('barSlider')[0].setAttribute('points', msg);
}
#graph{ border: solid 1px black; }
#map{ border: solid 1px red; }
<canvas width=256 height=256 id='graph'></canvas>
<canvas width=256 height=256 id='map'></canvas><br>
<svg id='mSvg' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 285 100" width=285 height=100>
<g>
<polygon class="barFrame" points="0,100 285,100 285,0"></polygon>
<polygon class='barSlider' points="0,100 143,100 143,50"></polygon>
</g>
<style>
.barFrame{ fill: #d1d3d4; }
.barSlider{ fill: #69bd45; }
</style>
</svg>
I am a beginner to programming and I want to make a blackhole simulations, I can't draw objects using vectors as coordinates (and I want it to be this way so I can animate them afterwards using vectors), here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>test trou noir</title>
<script>
var canvas, ctx;
var blackhole;
var circle;
var circles = new Array();
function init (){
var G = 6.67e-11, //gravitational constant
c = 3e8, //speed of light (m/s)
M = 12e31, // masseof the blackhole in kg (60 solar masses)
Rs = (2 * G * M) / 9e16, //Schwarzchild radius
pixel_Rs = Rs / 1e3,// scaled radius
canvas = document.getElementById ("space");
ctx = canvas.getContext ('2d');
drawCircle();
blackhole = new Ball (pixel_Rs, new Vector (700, 400), "black" );
blackhole.draw (ctx);
};
function Ball (radius, pos2D, color, vx, vy){
this.radius = radius;
this.pos2D = pos2D;
this.color = color;
this.vx = vx;
this.vy = vy;
};
Ball.prototype.draw = function (ctx){
ctx.fillStyle = this.color;
ctx.beginPath ();
ctx.arc ( pos2D, this.radius, 0, 2*Math.PI);
ctx.closePath ();
ctx.fill();
};
function drawCircle (ctx){
canvas = document.getElementById ("space");
ctx = canvas.getContext ('2d');
for (var i = 0; i<200; i++){
circle = new Ball (5,new Vector( Math.floor (Math.random()*1400), Math.floor (Math.random()*800)), "grey");
circle.draw (ctx)
circles.push (circle);
}
};
function Vector2D (x,y){
this.x = x;
this.y= y;
}
Vector2D.prototype = {
length: function (){
return this.x*this.x + this.y*this.y;
},
add: function (vec){
return new Vector2D (this.x + vec.x, this.y + vec.y);
},
subtract: function (vec){
return new Vector2D (this.x - vec.x, this.y - vec.y);
},
decrementBy: function (vec){
this.x -= vec.x;
this.y -= vec.y;
}
};
window.onload = init;
</script>
<style>
body {
background-color:#021c36 ;
margin: 0px;}
</style>
</head>
<body>
<canvas id ="space", width = "1400", height = "800">
</canvas>
</body>
</html>
Now, I think it is because the arc () method doesn't have specific coordinates, but then how can I implement vector coordinates so it draws something on the canvas? If someone have an idea it'd be great, also, if someone had a few tips as to how to animate 100 objects using vectors, it'd be great. Thanks alot
after your update I created the function update, but i can't make it work, here is the code:
function update (){
for (var i = 0; i<200; i++){
var vec2D = new Vector2D (Math.floor (Math.random()*1400), Math.floor (Math.random()*800));
circle = new Ball (5,vec2D.x,vec2D.y, "grey");
circle.draw (ctx)
circles.push (circle);
var distance = Math.sqrt (((vec2D.x-700)*(vec2D.x-700))+((vec2D.y400)* (vec2D.y-400)));
if (distance > pixel_Rs){
var delta = new Vector2D(1,1);
var forceDirection = Math.atan2(vec2D.y-700,vec2D.x-400);
delta.x += Math.cos(forceDirection)*3 ;
delta.y += Math.sin(forceDirection)*3;
vec2D.x += delta.x;
vec2D.y += delta.y;
requestAnimationFrame (update);
}
}
};
I did it without the force for now, but i can't make it work, do you know why?
PS:sorry about the code, my editing doesn't want to work properly :(
To draw with a vector.
// defining
var Vec = function(x,y){
this.x = x;
this.y = y;
}
// creating
var vec2D = new Vec(100,100);
// Using
ctx.arc(vec2D.x, vec2D.y, radius, 0, Math.PI *2);
To animate the vector you need to update it many times a second. Modern browsers provide a function to help you with that.
requestAnimationFrame(yourRenderFuntion) will call yourRenderFunction when it is ready for another frame. To use you create an update function that does all the drawing, and when done requests the next frame.
function update(time){ // Time is passed by requestAnimationFrame
// code to do the drawing and animation.
// request another frame
requestAnimationFrame(update);
}
// to start it
requestAnimationFrame(update);
Then you need to do the movements. I will create a second vector to hold the delta change per frame.
var delta = new Vec(1,1);
Then in the update function add the forces to the delta and add that to the position vector
// get direction of force
var forceDirection = Math.atan2(vec2D.y-blackHole.pos.y,vec2D.x-blackHole.pos.x);
// get magnitude of force
var force = getForce(vec2D); // get force of gravity you write that function
// apply that to the delta
delta.x += Math.cos(forceDirection) * force;
delta.y += Math.sin(forceDirection) * force;
// then update the position
vec2D.x += delta.x; // do it for both x and y axis
vec2D.y += delta.y;
Then you can draw the object at the next position.
I'm working on a canvas-based animation, and I'm trying to get a 3D effect in a 2D canvas.
So far, things are going well! I've got my "orbiting line of triangles" working very well:
var c = document.createElement('canvas');
c.width = c.height = 100;
document.body.appendChild(c);
var ctx = c.getContext("2d");
function Triangles() {
this.rotation = {
x: Math.random()*Math.PI*2,
y: Math.random()*Math.PI*2,
z: Math.random()*Math.PI*2
};
/* Uncomment this for testing perspective...
this.rotation = {
x: Math.PI/2,
y: 0,
z: 0
};
*/
}
Triangles.prototype.draw = function(t) {
this.rotation.z += t/1000;
var i, points;
for( i=0; i<15; i++) {
points = [
this.computeRotation(Math.cos(0.25*i),-Math.sin(0.25*i),0),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),-0.1),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),0.1)
];
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(50+40*points[0][0],50+40*points[0][1]);
ctx.lineTo(50+40*points[1][0],50+40*points[1][1]);
ctx.lineTo(50+40*points[2][0],50+40*points[2][1]);
ctx.closePath();
ctx.fill();
}
};
Triangles.prototype.computeRotation = function(x,y,z) {
var rz, ry, rx;
rz = [
Math.cos(this.rotation.z) * x - Math.sin(this.rotation.z) * y,
Math.sin(this.rotation.z) * x + Math.cos(this.rotation.z) * y,
z
];
ry = [
Math.cos(this.rotation.y) * rz[0] + Math.sin(this.rotation.y) * rz[2],
rz[1],
-Math.sin(this.rotation.y) * rz[0] + Math.cos(this.rotation.y) * rz[2]
];
rx = [
ry[0],
Math.cos(this.rotation.x) * ry[1] - Math.sin(this.rotation.x) * ry[2],
Math.sin(this.rotation.x) * ry[1] + Math.cos(this.rotation.x) * ry[2]
];
return rx;
};
var tri = new Triangles();
requestAnimationFrame(function(start) {
function step(t) {
var delta = t-start;
ctx.clearRect(0,0,100,100)
tri.draw(delta);
start = t;
requestAnimationFrame(step);
}
step(start);
});
As you can see it's using rotation matrices for calculating the position of the points after their rotation, and I'm using this to draw the triangles using the output x and y coordinates.
I want to take this a step further by using the z coordinate and adding perspective to this animation, which will make the triangles slightly bigger when in the foreground, and smaller when in the background. However, I'm not sure how to go about doing this.
I guess this is more of a maths question than a programming one, sorry about that!
Define a focal length to control the amount of perspective. The greater the value the less the amount of perspective. Then
var fl = 200; // focal length;
var px = 100; // point in 3D space
var py = 200;
var pz = 500;
Then to get the screen X,Y
var sx = (px * fl) / pz;
var sy = (py * fl) / pz;
The resulting point is relative to the center of the veiw so you need to center it to the canvas.
sx += canvas.width/2;
sy += canvas.height/2;
That is a point.
It assumes that the point being viewed is in front of the view and further than the focal length from the focal point.
I've managed to figure out a basic solution, but I'm sure there's better ones, so if you have a more complete answer feel free to add it! But for now...
Since the coordinate system is already based around the origin with the viewpoint directly on the Z axis looking at the (x,y) plane, it's actually sufficient to just multiply the (x,y) coordinates by a value proportional to z. For example, x * (z+2)/2 will do just fine in this case
There's bound to be a more proper, general solution though!
The math behind this question has been asked numerous times, so that's not specifically what I'm after. Rather, I'm trying to program the equation for determining these points into a loop in JavaScript, so that I can display points the evenly around the circle.
So with the equations for the X and Y positions of the points:
pointX = r * cos(theta) + centerX
pointY = r * sin(theta) + centerY
I should be able to calculate it with this:
var centerX = 300;
var centerY = 175;
var radius = 100;
var numberOfPoints = 8;
var theta = 360/numberOfPoints;
for ( var i = 1; i <= numberOfPoints; i++ ) {
pointX = ( radius * Math.cos(theta * i) + centerX );
pointY = ( radius * Math.sin(theta * i) + centerY );
// Draw point ( pointX , pointY )
}
And it should give me the x,y coordinates along the perimeter for 8 points, spread 45° from each other. But this doesn't work, and I'm not understanding why.
This is the output that I get (using the HTML5 Canvas element). The points should reside on the innermost red circle, as that one has a
Incorrect:
When it "should" look like this (although this is with just 1 point, placed manually):
Correct:
Could someone help me out? It's been years since I took trig, but even with looking at other examples (from various languages), I don't see why this isn't working.
Update: Figured it out!
I didn't need to add the centerX and centerY to each calculation, because in my code, those points were already relative to the center of the circle. So, while the canvas center was at point (300, 175), all points were relative to the circle that I created (the stroke line that they need to be placed on), and so the center for them was at (0, 0). I removed this from the code, and split the theta and angle calculations into two variables for better readability, and voila!
totalPoints = 8;
for (var i = 1; i <= totalPoints ; i++) {
drawPoint(100, i, totalPoints);
}
function drawPoint(r, currentPoint, totalPoints) {
var theta = ((Math.PI*2) / totalPoints);
var angle = (theta * currentPoint);
electron.pivot.x = (r * Math.cos(angle));
electron.pivot.y = (r * Math.sin(angle));
return electron;
}
Correct:
cos and sin in Javascript accept an argument in radians, not degrees. You can change your theta calculation to
var theta = (Math.PI*2)/numberOfPoints;
See the Math.cos documentation for details
#Emmett J. Butler's solution should work. The following is a complete working example
// canvas and mousedown related variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
// save canvas size to vars b/ they're used often
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var centerX = 150;
var centerY = 175;
var radius = 100;
var numberOfPoints = 8;
var theta = 2.0*Math.PI/numberOfPoints;
ctx.beginPath();
for ( var i = 1; i <= numberOfPoints; i++ ) {
pointX = ( radius * Math.cos(theta * i) + centerX );
pointY = ( radius * Math.sin(theta * i) + centerY );
ctx.fillStyle = "Red";
ctx.fillRect(pointX-5,pointY-5,10,10);
ctx.fillStyle = "Green";
}
ctx.stroke();
Basically I've managed to layout my DIV elements into a circle shape but I've not managed to work out how to calculate the deg of rotation need to have them face OUTWARD from the center of the circle.
$(document).ready(function(){
var elems = document.getElementsByClassName('test_box');
var increase = Math.PI * 2 / elems.length;
var x = 0, y = 0, angle = 0;
for (var i = 0; i < elems.length; i++) {
var elem = elems[i];
// modify to change the radius and position of a circle
x = 400 * Math.cos(angle) + 700;
y = 400 * Math.sin(angle) + 700;
elem.style.position = 'absolute';
elem.style.left = x + 'px';
elem.style.top = y + 'px';
//need to work this part out
var rot = 45;
elem.style['-moz-transform'] = "rotate("+rot+"deg)";
elem.style.MozTransform = "rotate("+rot+"deg)";
elem.style['-webkit-transform'] = "rotate("+rot+"deg)";
elem.style['-o-transform'] = "rotate("+rot+"deg)";
elem.style['-ms-transform'] = "rotate("+rot+"deg)";
angle += increase;
console.log(angle);
}
});
does anyone have to knowledge on how I can do this.
Cheers -C
Note that rot depends on angle, except angle is in radians.
DRY, so either convert from angle to rot:
// The -90 (degrees) makes the text face outwards.
var rot = angle * 180 / Math.PI - 90;
Or just use angle when setting the style (but use radians as a unit), and drop rot's declaration:
// The -0.5*Math.PI (radians) makes the text face outwards.
elem.style.MozTransform = "rotate("+(angle-0.5*Math.PI)+"rad)";
Try this:
var rot = 90 + (i * (360 / elems.length));
Demo at http://jsfiddle.net/gWZdd/
I've added the 90 degrees at the start there to ensure the baseline of the divs face towards the centre, however you can adjust this to suit your needs.