HTML5 Canvas - How to move an object in a direction? - javascript

I have an object I want to make orbit a star. I've managed to make the object move towards the star, but now I need to set up lateral movement as well.
Obviously this isn't as easy as just adjusting X, as when it moves round to the side of the star I'll have to adjust Y as well. I'm wondering how I could use some math to figure out how much I need to adjust X and Y by as the object moves around the star.
Here's my code so far:
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
c.width = window.innerWidth;
c.height = window.innerHeight;
var star = {
x: c.width / 2,
y: c.height / 2,
r: 100,
g: 2,
draw: function()
{
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI);
ctx.fillStyle = 'orange';
ctx.fill();
ctx.closePath();
}
};
var node = {
x: c.width / 2,
y: 100,
r: 20,
draw: function()
{
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
};
//GAME LOOP
function gameLoop()
{
update();
render();
window.requestAnimationFrame(gameLoop);
}
function update()
{
//Move towards star
var dx = star.x - node.x;
var dy = star.y - node.y;
var angle = Math.atan2(dy, dx);
node.x += Math.cos(angle);
node.y += Math.sin(angle);
//Lateral movement
node.x += 2;
}
function render()
{
ctx.clearRect(0, 0, c.width, c.height);
star.draw();
node.draw();
}
window.requestAnimationFrame(gameLoop);
<html>
<head>
<style>
body
{
margin: 0;
padding: 0;
overflow: hidden;
}
#canvas
{
background-color: #001319;
}
</style>
</head>
<body>
<canvas id="canvas">
</canvas>
<script src="Orbit.js"></script>
</body>
</html>

Newton's and Kepler's clockwork universe
When Newton worked out the maths for calculating orbits he noted something that prompted him to coin the term "clockwork universe". In a two body simulation the orbital paths of both objects repeat precisely. This mean that both objects will at the same time be in the exact same position at the exact same speed they were in the last orbit, there will be no precession.
Gravity, force, mass, and distance.
For a more accurate model of gravity you can use the laws of gravity as discovered by Newton. F = G * (m1*m2)/(r*r) where F is force, G is the Gravitational constant (and for simulations it is just a scaling factor) m1,m2 are the mass of each body and r is the distance between the bodies.
Mass of a sphere
We give both the star and planet some mass. Let's say that in the computer 1 pixel cubed is equal to 1 unit mass. Thus the mass of a sphere of radius R is 4/3 * R3 * PI.
Force, mass, and acceleration
The force is always applied along the line between the bodies and is called acceleration.
When a force is applied to an object we use another of Newton's discovered laws, F=ma where a is acceleration. We have the F (force) and m (mass) so now all we need is a. Rearrange F=ma to get a = f/m.
If we look at both formulas in terms of a (acceleration) a = (G * (m1*m2)/(r*r)) / m1 we can see that the mass of the object we are apply force to is cancelled out a = G * (m2)/(r*r). Now we can calculate the acceleration due to gravity. Acceleration is just change in velocity over time, and we know that that change is in the direction of the other body. So we get the vector between the bodies (o1,o2 for object 1 and 2) dx = o2.x-o1.x, dy = o2.y-o1.y Then find the length of that vector (which is the r in the gravity formula) dist = Math.sqrt(dx* dx + dy * dy). Then we normalise the vector (make its length = one) by dividing by its length. dx /= dist, dy /= dist. Calculate the a (acceleration) and multiply the normalised vector between the object by a then add that to the object's velocity and that is it. Perfect Newtonian clockwork orbits (for two bodies that is).
Clean up with Kepler.
All that math is good but it does not make for a nice simulation. When the math is done both objects start moving and if the starting velocities are not in balance then the whole system will slowly drift of the canvas.
We could just make the display relative to one of the bodies, this would eliminate any drift in the system, but we still have the problem of getting an orbit. If one object is moving to fast it will fly off and never come back. If it is going too slow then it will fall in very close to the centerpoint of the other object. If this happens the change in velocity will approch infinity, something computers are not that good at handling.
So to get nice circular orbits we need one last bit of math.
Using Kepler's second law modified to fit into Newton's math we get a formula that will give the approximate (It is an approximate as the actual calculations involve an infinite series and I can not be bothered writing that out.) orbital velocity v = sqrt(G*(m1 + m2)/r). It looks similar to Newton's gravity law but in this the masses are summed not multiplied, and the distance is not squared.
So we use this to calculate the tangential speed of both bodies to give them near circular orbits. It is important that each object go in the opposite direction to each other.
I created a setup function that sets up the correct orbits for both the sun and the planet. But the value of G (Gravitational constant) is likely way to large. To get a better value I scale G (via kludge math) so that the sun's orbit speed is close to a desired ideal sunV (pixels per frame) To make the whole sim run quicker increase this value
As I have set up the code to have more than two bodies the calculation of starting velocity will only work if each object is significantly more massive than the next. I have added a moon (you need to un-comment to see) to the planet, but it is too big and it's starting velocity is a little too low. It gets pulled (gravity sling shot) by the Earth into a higher orbit,. but this also pulls the earth into a lower orbit making its orbit more eccentric
NOTE After all that I find that something is not quite right and there is still a tiny bit of drift in the system. As I am out of time I have just fixed the sun position to keep the system on the canvas.
var c = document.getElementById('canvas');
c.width = innerWidth;
c.height = innerHeight;
var ctx = c.getContext('2d');
const STAR_RADIUS = 100;
const PLANET_RADIUS = 10;
const MOON_RADIUS = 4.5;
var G = 1; // gravitational constant is not so constant as need to
// scale it to find best value for the system.
// for that I will scale it so that the suns orbital speed around the
// planet is approx 0.1 pixels per frame
const sunV = 0.1; // the sun's orbital desired speed. THis is used to tune G
const DRAW = function () {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI);
ctx.fillStyle = this.col;
ctx.fill();
ctx.closePath();
}
var star = {
x: c.width / 2,
y: c.height / 2,
vx : 0,
vy : 0,
r: STAR_RADIUS,
mass : (4/3) * Math.pow(STAR_RADIUS,3) * Math.PI,
col : 'orange',
draw : DRAW,
};
// kludge to fix drift
const sunStartX = star.x;
const sunStartY = star.y;
var node = {
x: c.width / 2 - STAR_RADIUS - PLANET_RADIUS * 5,
y: c.height / 2,
r: PLANET_RADIUS,
mass : (4/3) * Math.pow(PLANET_RADIUS,3) * Math.PI,
col : "blue",
draw : DRAW,
vx: -1,
vy: 0,
};
var moon = {
x: c.width / 2- STAR_RADIUS - PLANET_RADIUS * 7 ,
y: c.height / 2,
r: MOON_RADIUS,
mass : (4/3) * Math.pow(PLANET_RADIUS,3) * Math.PI,
col : "#888",
draw : DRAW,
vx: -1,
vy: 0,
};
const objects = [star, node];//, moon];
function setup(){
var dist,dx,dy,o1,o2,v,c,dv;
o1 = objects[0];
o1.vx = 0;
o1.vy = 0;
for(var j = 0; j < objects.length; j ++){
if(j !== 0){ // object can not apply force to them selves
o2 = objects[j];
dx = o2.x - o1.x;
dy = o2.y - o1.y;
dist = Math.sqrt(dx * dx + dy * dy);
dx /= dist;
dy /= dist;
// Find value og G
if(j === 1){ // is this not sun
v = Math.sqrt(G * ( o2.mass ) / dist);
dv = sunV - v;
while(Math.abs(dv) > sunV * sunV){
if(dv < 0){ // sun too fast
G *= 0.75;
}else{
G += G * 0.1;
}
v = Math.sqrt(G * ( o2.mass ) / dist);
dv = sunV - v;
}
}
v = Math.sqrt(G * ( o2.mass ) / dist);
o1.vx -= v * dy; // along the tangent
o1.vy += v * dx;
}
}
for(var i = 1; i < objects.length; i ++){
o1 = objects[i];
o1.vx = 0;
o1.vy = 0;
for(var j = 0; j <objects.length; j ++){
if(j !== i){
o2 = objects[j];
dx = o2.x - o1.x;
dy = o2.y - o1.y;
dist = Math.sqrt(dx * dx + dy * dy);
dx /= dist;
dy /= dist;
v = Math.sqrt(G * ( o2.mass ) / dist);
o1.vx += v * dy; // along the tangent
o1.vy -= v * dx;
}
}
}
}
//GAME LOOP
function gameLoop(){
update();
render();
requestAnimationFrame(gameLoop);
}
// every object exerts a force on every other object
function update(){
var dist,dx,dy,o1,o2,a;
// find force of acceleration each object applies to each object
for(var i = 0; i < objects.length; i ++){
o1 = objects[i];
for(var j = 0; j < objects.length; j ++){
if(i !== j){ // object can not apply force to them selves
o2 = objects[j];
dx = o2.x - o1.x;
dy = o2.y - o1.y;
dist = Math.sqrt(dx * dx + dy * dy);
dx /= dist; // normalise the line between the objects (makes the vector 1 unit long)
dy /= dist;
// get force
a = (G * o2.mass ) / (dist * dist);
o1.vx += a * dx;
o1.vy += a * dy;
}
}
}
// once all the forces have been found update objects positions
for(var i = 0; i < objects.length; i ++){
o1 = objects[i];
o1.x += o1.vx;
o1.y += o1.vy;
}
}
function render(){
ctx.clearRect(0, 0, c.width, c.height);
// kludge to fix drift
var offsetX = objects[0].x - sunStartX;
var offsetY = objects[0].y - sunStartY;
ctx.setTransform(1,0,0,1,-offsetX,-offsetY);
for(var i = 0; i < objects.length; i ++){
objects[i].draw();
}
ctx.setTransform(1,0,0,1,0,0);
}
setup();
requestAnimationFrame(gameLoop);
<canvas id='canvas'></canvas>

Okay, I've answered my own question.
Rather than making the star's gravity directly affect the x and y coordinates, I have a vx and vy of the object, and I cause the gravity to affect that value, and then just adjust x and y by vx and vy on each update.
Here's the code:
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
c.width = window.innerWidth;
c.height = window.innerHeight;
var star = {
x: c.width / 2,
y: c.height / 2,
r: 100,
g: 0.5,
draw: function()
{
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI);
ctx.fillStyle = 'orange';
ctx.fill();
ctx.closePath();
}
};
var node = {
x: c.width / 2,
y: 50,
r: 20,
vx: 15,
vy: 0,
draw: function()
{
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
};
//GAME LOOP
function gameLoop()
{
update();
render();
window.requestAnimationFrame(gameLoop);
}
function update()
{
node.x += node.vx;
node.y += node.vy;
//Move towards star
var dx = star.x - node.x;
var dy = star.y - node.y;
var angle = Math.atan2(dy, dx);
node.vx += (Math.cos(angle) * star.g);
node.vy += (Math.sin(angle) * star.g);
}
function render()
{
ctx.clearRect(0, 0, c.width, c.height);
star.draw();
node.draw();
}
window.requestAnimationFrame(gameLoop);
<canvas id='canvas'></canvas>

Related

Simplest way to make a spiral line go through an arbitrary point?

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
let last = 1
let start = 1
let i = 0
let origin = [250, 250]
for (let i2 = 0; i2 < 20; i2++) {
ctx.ellipse(...origin, start, start, Math.PI / 2 * i, 0, Math.PI / 2);
i++
i %= 4
if (i == 1) origin[1] -= last
else if (i == 2) origin[0] += last
else if (i == 3) origin[1] += last
else if (i == 0) origin[0] -= last;
[last, start] = [start, start + last]
}
ctx.stroke();
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 7
ctx.strokeStyle = "red";
ctx.lineTo(400, 400)
ctx.stroke()
<canvas width="500" height="500" style="border:1px solid #000000;"></canvas>
What is the simplest way to make the spiral line go through an arbitrary point in the canvas? For example 400x 400y. I think adjusting the initial start and last values based on some calculation could work. The only difference between the first code snippet and the second one is the initial last and start variables. Other solutions that rewrite the entire thing are welcome too.
const canvas = document.querySelector( 'canvas' );
const ctx = canvas.getContext( '2d' );
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
let last = 0.643
let start = 0.643
let i = 0
let origin = [250,250]
for (let i2=0; i2<20; i2++) {
ctx.ellipse(...origin, start, start, Math.PI/2 *i , 0, Math.PI /2);
i++
i%=4
if (i==1) origin[1] -= last
if (i==2) origin[0] += last
if (i==3) origin[1] += last
if (i==0) origin[0] -= last
;[last, start] = [start, start + last]
}
ctx.stroke();
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 7
ctx.strokeStyle = "red";
ctx.lineTo(400, 400)
ctx.stroke()
<canvas width="500" height="500" style="border:1px solid #000000;"></canvas>
I am not sure how you want the spiral to intercept the point. There are twp options.
Rotate the spiral to intercept point
Scale the spiral to intercept point
This answer solves using method 1. Method 2 has some problems as the number of turns can grow exponentially making the rendering very slow if we don't set limits to where the point of intercept can be.
Not a spiral
The code you provided does not draw a spiral in the mathematical definition but rather is just a set of connected ellipsoids.
This means that there is more than one function that defines a point on these connected curves. To solve for a point will require some complexity as each possible curve must be solved and the solution then vetted to locate the correct curve. On top of that ellipsoids I find to result in some very ugly math.
A spiral function
If we define the curve as just one function where the spiral radius is defined by the angle, it is then very easy to solve.
The function for the radius can be a simplified polynomial in the form Ax^P+C where x is the angle, P is the spiralness (for want of a better term), A is the scale (again for want of a better term) and C is the start angle
C is there if you want to make the step angle of the spiral be a set length eg 1 px would be angle += 1 / (Ax^P+C) If C is 0 then 1/0 would result in an infinite loop.
Drawing the spiral
As defined above there are many types of spirals that can be rendered so there should be one that is close to the spiral you have.
Any point on the spiral is found as follows
x = cos(angle) * f(angle) + origin.x
y = sin(angle) * f(angle) + origin.y
where f is the poly f(x) = Ax^P+C
The following function draws a basic linear spiral f(x) = 1*x^1+0.1
function drawSpiral(origin) {
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.beginPath();
let i = 0;
while (i < 5) {
const r = i + 0.1; // f(x) = 1*x^1+0.1
ctx.lineTo(
Math.cos(i) * r + origin.x,
Math.sin(i) * r + origin.y
);
i += 0.1
}
ctx.stroke();
}
Solve to pass though point
To solve for a point we convert the point to a polar coordinate relative to the origin. See functions pointDist , pointAngle. We then solve for Ax^P+C = dist in terms of x (the angle) and dist the distance from the origin. Then subtract the angle to the point to get the spirals orientation. (NOTE ^ means to power of, rest of answer uses JavaScripts **)
To solve an arbitrary polynomial can become rather complex that is why I used the simplified version.
The function A * x ** P + C = pointDist(point) needs to be rearranged in terms of pointDist(point).
This gives x = ((pointDist(point) - C) / A) ** (1 / P)
And then subtract the polar angle x = ((pointDist(point)- C) / A) ** (1 / P) - pointAngle(point) and we have the angle offset so that the spiral will intercept the point.
Example
A working example in case the above was TLDR or had too much math like jargon.
The example defines a spiral via the coefficients of the radius function A, C, and P.
There are 3 example spirals Black, Blue, and Green.
A spiral is drawn until its radius is greater than the diagonal distance to the canvas corner. The origin is the center of the canvas.
The point to intercept is set by the mouse position over the page.
The spirals are only rendered when the mouse position changes.
The solution for the simplified polynomial is shown in steps in the function startAngle.
While I wrote the code I seam to have lost the orientation and thus needed to add 180 deg to the start angle (Math.PI) or the point ends up midway between spiral arms.
const ctx = canvas.getContext("2d");
const mouse = {x : 0, y : 0}, mouseOld = {x : undefined, y : undefined};
document.addEventListener("mousemove", (e) => { mouse.x = e.pageX; mouse.y = e.pageY });
requestAnimationFrame(loop);
const TURNS = 4 * Math.PI * 2;
let origin = {x: canvas.width / 2, y: canvas.height / 2};
scrollTo(0, origin.y - innerHeight / 2);
const maxRadius = (origin.x ** 2 + origin.y ** 2) ** 0.5; // dist from origin to corner
const pointDist = (p1, p2) => Math.hypot(p1.x - p2.x, p1.y - p2.y);
const pointAngle = (p1, p2) => Math.atan2(p1.y - p2.y, p1.x - p2.x);
const radius = (x, spiral) => spiral.A * x ** spiral.P + spiral.C;
const startAngle = (origin, point, spiral) => {
const dist = pointDist(origin, point);
const ang = pointAngle(origin, point);
// Da math
// from radius function A * x ** P + C
// where x is ang
// A * x ** P + C = dist
// A * x ** P = dist - C
// x ** P = (dist - C) / A
// x = ((dist - C) / A) ** (1 / p)
return ((dist - spiral.C) / spiral.A) ** (1 / spiral.P) - ang;
}
// F for Fibonacci
const startAngleF = (origin, point, spiral) => {
const dist = pointDist(origin, point);
const ang = pointAngle(origin, point);
return (1 / spiral.P) * Math.log(dist / spiral.A) - ang;
}
const radiusF = (x, spiral) => spiral.A * Math.E ** (spiral.P * x);
const spiral = (P, A, C, rFc = radius, aFc = startAngle) => ({P, A, C, rFc, aFc});
const spirals = [
spiral(2, 1, 0.1),
spiral(3, 0.25, 0.1),
spiral(0.3063489,0.2972713047, null, radiusF, startAngleF),
spiral(0.8,4, null, radiusF, startAngleF),
];
function drawSpiral(origin, point, spiral, col) {
const start = spiral.aFc(origin, point, spiral);
ctx.strokeStyle = col;
ctx.beginPath();
let i = 0;
while (i < TURNS) {
const r = spiral.rFc(i, spiral);
const ang = i - start - Math.PI;
ctx.lineTo(
Math.cos(ang) * r + origin.x,
Math.sin(ang) * r + origin.y
);
if (r > maxRadius) { break }
i += 0.1
}
ctx.stroke();
}
loop()
function loop() {
if (mouse.x !== mouseOld.x || mouse.y !== mouseOld.y) {
ctx.clearRect(0, 0, 500, 500);
ctx.lineWidth = 1;
drawSpiral(origin, mouse, spirals[0], "#FFF");
drawSpiral(origin, mouse, spirals[1], "#0FF");
ctx.lineWidth = 4;
drawSpiral(origin, mouse, spirals[2], "#FF0");
drawSpiral(origin, mouse, spirals[3], "#AF0");
ctx.beginPath();
ctx.lineCap = "round";
ctx.lineWidth = 7;
ctx.strokeStyle = "red";
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
Object.assign(mouseOld, mouse);
}
requestAnimationFrame(loop);
}
canvas { position : absolute; top : 0px; left : 0px; background: black }
<canvas id="canvas" width = "500" height = "500"></canvas>
UPDATE
As requested in the comments
I have added the Fibonacci spiral to the example
The radius function is radiusF
The function to find the start angle to intercept a point is startAngleF
The two new Fibonacci spirals are colored limeGreen and Yellow
To use the Fibonacci spiral you must include the functions radiusF and startAngleF when defining the spiral eg spiral(1, 1, 0, radiusF, startAngleF)
Note the 3rd argument is not used and is zero in the eg above. as I don't think you will need it

Dynamic Wavy Path/Border

There is something I need to build, but my math ability is not up to par. What I am looking to build is something like this demo, but I need it to be a hybrid of a circle and polygon instead of a line, so to speak. The black line should be dynamic and randomly generated that basically acts as a border on the page.
Currently, I am dissecting this answer with the aim of hopefully being able to transpose it into this, but I am having massive doubts that I will be able to figure this out.
Any idea how to do this or can anybody explain the mathematics?
Below are my notes about the code from the answer I linked above.
var
cw = cvs.width = window.innerWidth,
ch = cvs.height = window.innerHeight,
cx = cw / 2,
cy = ch / 2,
xs = Array(),
ys = Array(),
npts = 20,
amplitude = 87, // can be val from 1 to 100
frequency = -2, // can be val from -10 to 1 in steps of 0.1
ctx.lineWidth = 4
// creates array of coordinates that
// divides page into regular portions
// creates array of weights
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i
ys[i] = 2.0*(Math.random()-0.5)*amplitude
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (let x = 0; x < cw; x++) {
y = 0.0
wsum = 0.0
for (let i = -5; i <= 5; i++) {
xx = x; // 0 / 1 / 2 / to value of screen width
// creates sequential sets from [-5 to 5] to [15 to 25]
ii = Math.round(x/xs[1]) + i
// `xx` is a sliding range with the total value equal to client width
// keeps `ii` within range of 0 to 20
if (ii < 0) {
xx += cw
ii += npts
}
if (ii >= npts){
xx -= cw
ii -= npts
}
// selects eleven sequential array items
// which are portions of the screen width and height
// to create staggered inclines in increments of those portions
w = Math.abs(xs[ii] - xx)
// creates irregular arcs
// based on the inclining values
w = Math.pow(w, frequency)
// also creates irregular arcs therefrom
y += w*ys[ii];
// creates sets of inclining values
wsum += w;
}
// provides a relative position or weight
// for each y-coordinate in the total path
y /= wsum;
//y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
This is my answer. Please read the comments in the code. I hope this is what you need.
// initiate the canvas
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = (canvas.width = 600),
cx = cw / 2;
let ch = (canvas.height = 400),
cy = ch / 2;
ctx.fillStyle = "white"
// define the corners of an rectangle
let corners = [[100, 100], [500, 100], [500, 300], [100, 300]];
let amplitud = 20;// oscilation amplitude
let speed = 0.01;// the speed of the oscilation
let points = []; // an array of points to draw the curve
class Point {
constructor(x, y, hv) {
// the point is oscilating around this point (cx,cy)
this.cx = x;
this.cy = y;
// the current angle of oscilation
this.a = Math.random() * 2 * Math.PI;
this.hv = hv;// a variable to know if the oscilation is horizontal or vertical
this.update();
}
// a function to update the value of the angle
update() {
this.a += speed;
if (this.hv == 0) {
this.x = this.cx;
this.y = this.cy + amplitud * Math.cos(this.a);
} else {
this.x = this.cx + amplitud * Math.cos(this.a);
this.y = this.cy;
}
}
}
// a function to divide a line that goes from a to b in n segments
// I'm using the resulting points to create a new point object and push this new point into the points array
function divide(n, a, b) {
for (var i = 0; i <= n; i++) {
let p = {
x: (b[0] - a[0]) * i / n + a[0],
y: (b[1] - a[1]) * i / n + a[1],
hv: b[1] - a[1]
};
points.push(new Point(p.x, p.y, p.hv));
}
}
divide(10, corners[0], corners[1]);points.pop();
divide(5, corners[1], corners[2]);points.pop();
divide(10, corners[2], corners[3]);points.pop();
divide(5, corners[3], corners[0]);points.pop();
// this is a function that takes an array of points and draw a curved line through those points
function drawCurves() {
//find the first midpoint and move to it
let p = {};
p.x = (points[points.length - 1].x + points[0].x) / 2;
p.y = (points[points.length - 1].y + points[0].y) / 2;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
//curve through the rest, stopping at each midpoint
for (var i = 0; i < points.length - 1; i++) {
let mp = {};
mp.x = (points[i].x + points[i + 1].x) / 2;
mp.y = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, mp.x, mp.y);
}
//curve through the last point, back to the first midpoint
ctx.quadraticCurveTo(
points[points.length - 1].x,
points[points.length - 1].y,
p.x,
p.y
);
ctx.stroke();
ctx.fill();
}
function Draw() {
window.requestAnimationFrame(Draw);
ctx.clearRect(0, 0, cw, ch);
points.map(p => {
p.update();
});
drawCurves();
}
Draw();
canvas{border:1px solid; background:#6ab150}
<canvas></canvas>

Javascript Canvas apply Radial Gradient to Segment?

I am trying to create a shadow system for my 2D Game in a HTML5 Canvas. Right now, I am rendering my shadows like so:
function drawShadows(x, y, width) {
if (shadowSprite == null) {
shadowSprite = document.createElement('canvas');
var tmpCtx = shadowSprite.getContext('2d');
var shadowBlur = 20;
shadowSprite.width = shadowResolution;
shadowSprite.height = shadowResolution;
var grd = tmpCtx.createLinearGradient(-(shadowResolution / 4), 0,
shadowResolution, 0);
grd.addColorStop(0, "rgba(0, 0, 0, 0.1)");
grd.addColorStop(1, "rgba(0, 0, 0, 0)");
tmpCtx.fillStyle = grd;
tmpCtx.shadowBlur = shadowBlur;
tmpCtx.shadowColor = "#000";
tmpCtx.fillRect(0, 0, shadowResolution, shadowResolution);
}
graph.save();
graph.rotate(sun.getDir(x, y));
graph.drawImage(shadowSprite, 0, -(width / 2), sun.getDist(x, y), width);
graph.restore();
}
This renders a cube with a linear gradient that fades from black to alpha 0.
This however does not produce a realistic result, since it will always be a rectangle. Here is an illustration to describe the problem:
Sorry i'm not very artistic. It would not be an issue to draw the trapezoid shape. (Seen in blue). The issue is that I still there to be a gradient. Is it possible to draw a shape like that with a gradient?
The canvas is very flexible. Almost anything is possible. This example draws the light being cast. But it can just as easily be the reverse. Draw the shadows as a gradient.
If you are after realism then instead of rendering a gradient for the lighting (or shadows) use the shape created to set a clipping area and then render a accurate lighting and shadow solution.
With lineTo and gradients you can create any shape and gradient you my wish. Also to get the best results use globalCompositeOperation as they have a large variety of filters.
The demo just shows how to mix a gradient and a shadow map. (Very basic no recursion implemented, and shadows are just approximations.)
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var mouse = {
x:0,
y:0,
};
function mouseMove(event){
mouse.x = event.offsetX; mouse.y = event.offsetY;
if(mouse.x === undefined){ mouse.x = event.clientX; mouse.y = event.clientY;}
}
// add mouse controls
canvas.addEventListener('mousemove',mouseMove);
var boundSize = 10000; // a number....
var createImage = function(w,h){ // create an image
var image;
image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
var directionC = function(x,y,xx,yy){ // this should be inLine but the angles were messing with my head
var a; // so moved it out here
a = Math.atan2(yy - y, xx - x); // for clarity and the health of my sanity
return (a + Math.PI * 2) % (Math.PI * 2); // Dont like negative angles.
}
// Create background image
var back = createImage(20, 20);
back.ctx.fillStyle = "#333";
back.ctx.fillRect(0, 0, 20, 20);
// Create background image
var backLight = createImage(20, 20);
backLight .ctx.fillStyle = "#ACD";
backLight .ctx.fillRect(0, 0, 20, 20);
// create circle image
var circle = createImage(64, 64);
circle.ctx.fillStyle = "red";
circle.ctx.beginPath();
circle.ctx.arc(32, 32, 30, 0, Math.PI * 2);
circle.ctx.fill();
// create some circles semi random
var circles = [];
circles.push({
x : 200 * Math.random(),
y : 200 * Math.random(),
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random() + 200,
y : 200 * Math.random(),
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random() + 200,
y : 200 * Math.random() + 200,
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random(),
y : 200 * Math.random() + 200,
scale : Math.random() * 0.8 + 0.3,
});
// shadows on for each circle;
var shadows = [{},{},{},{}];
var update = function(){
var c, dir, dist, x, y, x1, y1, x2, y2, dir1, dir2, aAdd, i, j, s, s1 ,nextDir, rev, revId;
rev = false; // if inside a circle reverse the rendering.
// set up the gradient at the mouse pos
var g = ctx.createRadialGradient(mouse.x, mouse.y, canvas.width * 1.6, mouse.x, mouse.y, 2);
// do each circle and work out the two shadow lines coming from it.
for(var i = 0; i < circles.length; i++){
c = circles[i];
dir = directionC(mouse.x, mouse.y, c.x, c.y);
dist = Math.hypot(mouse.x - c.x, mouse.y - c.y);
// cludge factor. Could not be bother with the math as the light sourse nears an object
if(dist < 30* c.scale){
rev = true;
revId = i;
}
aAdd = (Math.PI / 2) * (0.5 / (dist - 30 * c.scale));
x1 = Math.cos(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
y1 = Math.sin(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
x2 = Math.cos(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
y2 = Math.sin(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
// direction of both shadow lines
dir1 = directionC(mouse.x, mouse.y, c.x + x1, c.y + y1);
dir2 = directionC(mouse.x, mouse.y, c.x + x2, c.y + y2);
// create the shadow object to hold details
shadows[i].dir = dir;
shadows[i].d1 = dir1;
if (dir2 < dir1) { // make sure second line is always greater
dir2 += Math.PI * 2;
}
shadows[i].d2 = dir2;
shadows[i].x1 = (c.x + x1); // set the shadow start pos
shadows[i].y1 = (c.y + y1);
shadows[i].x2 = (c.x + x2); // for both lines
shadows[i].y2 = (c.y + y2);
shadows[i].circle = c; // ref the circle
shadows[i].dist = dist; // set dist from light
shadows[i].branch1 = undefined; //.A very basic tree for shadows that interspet other object
shadows[i].branch2 = undefined; //
shadows[i].branch1Dist = undefined;
shadows[i].branch2Dist = undefined;
shadows[i].active = true; // false if the shadow is in a shadow
shadows[i].id = i;
}
shadows.sort(function(a,b){ // sort by distance from light
return a.dist - b.dist;
});
// cull shdows with in shadows and connect circles with joined shadows
for(i = 0; i < shadows.length; i++){
s = shadows[i];
for(j = i + 1; j < shadows.length; j++){
s1 = shadows[j];
if(s1.d1 > s.d1 && s1.d2 < s.d2){ // if shadow in side another
s1.active = false; // cull it
}else
if(s.d1 > s1.d1 && s.d1 < s1.d2){ // if shodow intercepts going twards light
s1.branch1 = s;
s.branch1Dist = s1.dist - s.dist;
s.active = false;
}else
if(s.d2 > s1.d1 && s.d2 < s1.d2){ // away from light
s.branch2 = s1;
s.branch2Dist = s1.dist - s.dist;
s1.active = false;
}
}
}
// keep it quick so not using filter
// filter culled shadows
var shadowsShort = [];
for (i = 0; i < shadows.length; i++) {
if ((shadows[i].active && !rev) || (rev && shadows[i].id === revId)) { // to much hard work makeng shadow from inside the circles. Was a good idea at the time. But this i just an example after all;
shadowsShort.push(shadows[i])
}
}
// sort shadows in clock wise render order
if(rev){
g.addColorStop(0.3, "rgba(210,210,210,0)");
g.addColorStop(0.6, "rgba(128,128,128,0.5)");
g.addColorStop(1, "rgba(0,0,0,0.9)");
shadowsShort.sort(function(a,b){
return b.dir - a.dir;
});
// clear by drawing background image.
ctx.drawImage(backLight, 0, 0, canvas.width, canvas.height);
}else{
g.addColorStop(0.3, "rgba(0,0,0,0)");
g.addColorStop(0.6, "rgba(128,128,128,0.5)");
g.addColorStop(1, "rgba(215,215,215,0.9)");
shadowsShort.sort(function(a,b){
return a.dir - b.dir;
});
// clear by drawing background image.
ctx.drawImage(back, 0, 0, canvas.width, canvas.height);
}
// begin drawin the light area
ctx.fillStyle = g; // set the gradient as the light
ctx.beginPath();
for(i = 0; i < shadowsShort.length; i++){ // for each shadow move in to the light across the circle and then back out away from the light
s = shadowsShort[i];
x = s.x1 + Math.cos(s.d1) * boundSize;
y = s.y1 + Math.sin(s.d1) * boundSize;
if (i === 0) { // if the start move to..
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
ctx.lineTo(s.x1, s.y1);
if (s.branch1 !== undefined) { // if braching. (NOTE this is not recursive. the correct solution would to math this a function and use recursion to climb in an out)
s = s.branch1;
x = s.x1 + Math.cos(s.d1) * s.branch1Dist;
y = s.y1 + Math.sin(s.d1) * s.branch1Dist;
ctx.lineTo(x, y);
ctx.lineTo(s.x1, s.y1);
}
ctx.lineTo(s.x2, s.y2);
if (s.branch2 !== undefined) {
x = s.x2 + Math.cos(s.d2) * s.branch2Dist;
y = s.y2 + Math.sin(s.d2) * s.branch2Dist;
ctx.lineTo(x, y);
s = s.branch2;
ctx.lineTo(s.x2, s.y2);
}
x = s.x2 + Math.cos(s.d2) * boundSize;
y = s.y2 + Math.sin(s.d2) * boundSize;
ctx.lineTo(x, y);
// now fill in the light between shadows
s1 = shadowsShort[(i + 1) % shadowsShort.length];
nextDir = s1.d1;
if(rev){
if (nextDir > s.d2) {
nextDir -= Math.PI * 2
}
}else{
if (nextDir < s.d2) {
nextDir += Math.PI * 2
}
}
x = Math.cos((nextDir+s.d2)/2) * boundSize + canvas.width / 2;
y = Math.sin((nextDir+s.d2)/2) * boundSize + canvas.height / 2;
ctx.lineTo(x, y);
}
// close the path.
ctx.closePath();
// set the comp to lighten or multiply
if(rev){
ctx.globalCompositeOperation ="multiply";
}else{
ctx.globalCompositeOperation ="lighter";
}
// draw the gradient
ctx.fill()
ctx.globalCompositeOperation ="source-over";
// draw the circles
for (i = 0; i < circles.length; i++) {
c = circles[i];
ctx.drawImage(circle, c.x - 32 * c.scale, c.y - 32 * c.scale, 64 * c.scale, 64 * c.scale);
}
// feed the herbervors.
window.requestAnimationFrame(update);
}
update();
.canC { width:400px; height:400px;}
<canvas class="canC" id="canV" width=400 height=400></canvas>

Canvas Rotating Star Field

I'm taking the following approach to animate a star field across the screen, but I'm stuck for the next part.
JS
var c = document.getElementById('stars'),
ctx = c.getContext("2d"),
t = 0; // time
c.width = 300;
c.height = 300;
var w = c.width,
h = c.height,
z = c.height,
v = Math.PI; // angle of vision
(function animate() {
Math.seedrandom('bg');
ctx.globalAlpha = 1;
for (var i = 0; i <= 100; i++) {
var x = Math.floor(Math.random() * w), // pos x
y = Math.floor(Math.random() * h), // pos y
r = Math.random()*2 + 1, // radius
a = Math.random()*0.5 + 0.5, // alpha
// linear
d = (r*a), // depth
p = t*d; // pixels per t
x = x - p; // movement
x = x - w * Math.floor(x / w); // go around when x < 0
(function draw(x,y) {
var gradient = ctx.createRadialGradient(x, y, 0, x + r, y + r, r * 2);
gradient.addColorStop(0, 'rgba(255, 255, 255, ' + a + ')');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.beginPath();
ctx.arc(x, y, r, 0, 2*Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
return draw;
})(x, y);
}
ctx.restore();
t += 1;
requestAnimationFrame(function() {
ctx.clearRect(0, 0, c.width, c.height);
animate();
});
})();
HTML
<canvas id="stars"></canvas>
CSS
canvas {
background: black;
}
JSFiddle
What it does right now is animate each star with a delta X that considers the opacity and size of the star, so the smallest ones appear to move slower.
Use p = t; to have all the stars moving at the same speed.
QUESTION
I'm looking for a clearly defined model where the velocities give the illusion of the stars rotating around the expectator, defined in terms of the center of the rotation cX, cY, and the angle of vision v which is what fraction of 2π can be seen (if the center of the circle is not the center of the screen, the radius should be at least the largest portion). I'm struggling to find a way that applies this cosine to the speed of star movements, even for a centered circle with a rotation of π.
These diagrams might further explain what I'm after:
Centered circle:
Non-centered:
Different angle of vision:
I'm really lost as to how to move forwards. I already stretched myself a bit to get here. Can you please help me with some first steps?
Thanks
UPDATE
I have made some progress with this code:
// linear
d = (r*a)*z, // depth
v = (2*Math.PI)/w,
p = Math.floor( d * Math.cos( t * v ) ); // pixels per t
x = x + p; // movement
x = x - w * Math.floor(x / w); // go around when x < 0
JSFiddle
Where p is the x coordinate of a particle in uniform circular motion and v is the angular velocity, but this generates a pendulum effect. I am not sure how to change these equations to create the illusion that the observer is turning instead.
UPDATE 2:
Almost there. One user at the ##Math freenode channel was kind enough to suggest the following calculation:
// linear
d = (r*a), // depth
p = t*d; // pixels per t
x = x - p; // movement
x = x - w * Math.floor(x / w); // go around when x < 0
x = (x / w) - 0.5;
y = (y / h) - 0.5;
y /= Math.cos(x);
x = (x + 0.5) * w;
y = (y + 0.5) * h;
JSFiddle
This achieves the effect visually, but does not follow a clearly defined model in terms of the variables (it just "hacks" the effect) so I cannot see a straightforward way to do different implementations (change the center, angle of vision). The real model might be very similar to this one.
UPDATE 3
Following from Iftah's response, I was able to use Sylvester to apply a rotation matrix to the stars, which need to be saved in an array first. Also each star's z coordinate is now determined and the radius r and opacity a are derived from it instead. The code is substantially different and lenghthier so I am not posting it, but it might be a step in the right direction. I cannot get this to rotate continuously yet. Using matrix operations on each frame seems costly in terms of performance.
JSFiddle
Here's some pseudocode that does what you're talking about.
Make a bunch of stars not too far but not too close (via rejection sampling)
Set up a projection matrix (defines the camera frustum)
Each frame
Compute our camera rotation angle
Make a "view" matrix (repositions the stars to be relative to our view)
Compose the view and projection matrix into the view-projection matrix
For each star
Apply the view-projection matrix to give screen star coordinates
If the star is behind the camera skip it
Do some math to give the star a nice seeming 'size'
Scale the star coordinate to the canvas
Draw the star with its canvas coordinate and size
I've made an implementation of the above. It uses the gl-matrix Javascript library to handle some of the matrix math. It's good stuff. (Fiddle for this is here, or see below.)
var c = document.getElementById('c');
var n = c.getContext('2d');
// View matrix, defines where you're looking
var viewMtx = mat4.create();
// Projection matrix, defines how the view maps onto the screen
var projMtx = mat4.create();
// Adapted from http://stackoverflow.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api
function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) {
// We'll assume input parameters are sane.
field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians
var frustum_depth = far_dist - near_dist;
var one_over_depth = 1 / frustum_depth;
var e11 = 1.0 / Math.tan(0.5 * field_of_view);
var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio;
var e22 = far_dist * one_over_depth;
var e32 = (-far_dist * near_dist) * one_over_depth;
return [
e00, 0, 0, 0,
0, e11, 0, 0,
0, 0, e22, e32,
0, 0, 1, 0
];
}
// Make a view matrix with a simple rotation about the Y axis (up-down axis)
function ComputeViewMtx(angle) {
angle = angle * Math.PI / 180.0; // Convert degrees to radians
return [
Math.cos(angle), 0, Math.sin(angle), 0,
0, 1, 0, 0,
-Math.sin(angle), 0, Math.cos(angle), 0,
0, 0, 0, 1
];
}
projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true);
var angle = 0;
var viewProjMtx = mat4.create();
var minDist = 100;
var maxDist = 1000;
function Star() {
var d = 0;
do {
// Create random points in a cube.. but not too close.
this.x = Math.random() * maxDist - (maxDist / 2);
this.y = Math.random() * maxDist - (maxDist / 2);
this.z = Math.random() * maxDist - (maxDist / 2);
var d = this.x * this.x +
this.y * this.y +
this.z * this.z;
} while (
d > maxDist * maxDist / 4 || d < minDist * minDist
);
this.dist = Math.sqrt(d);
}
Star.prototype.AsVector = function() {
return [this.x, this.y, this.z, 1];
}
var stars = [];
for (var i = 0; i < 5000; i++) stars.push(new Star());
var lastLoop = Date.now();
function loop() {
var now = Date.now();
var dt = (now - lastLoop) / 1000.0;
lastLoop = now;
angle += 30.0 * dt;
viewMtx = ComputeViewMtx(angle);
//console.log('---');
//console.log(projMtx);
//console.log(viewMtx);
mat4.multiply(viewProjMtx, projMtx, viewMtx);
//console.log(viewProjMtx);
n.beginPath();
n.rect(0, 0, c.width, c.height);
n.closePath();
n.fillStyle = '#000';
n.fill();
n.fillStyle = '#fff';
var v = vec4.create();
for (var i = 0; i < stars.length; i++) {
var star = stars[i];
vec4.transformMat4(v, star.AsVector(), viewProjMtx);
v[0] /= v[3];
v[1] /= v[3];
v[2] /= v[3];
//v[3] /= v[3];
if (v[3] < 0) continue;
var x = (v[0] * 0.5 + 0.5) * c.width;
var y = (v[1] * 0.5 + 0.5) * c.height;
// Compute a visual size...
// This assumes all stars are the same size.
// It also doesn't scale with canvas size well -- we'd have to take more into account.
var s = 300 / star.dist;
n.beginPath();
n.arc(x, y, s, 0, Math.PI * 2);
//n.rect(x, y, s, s);
n.closePath();
n.fill();
}
window.requestAnimationFrame(loop);
}
loop();
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script>
<canvas id="c" width="500" height="500"></canvas>
Some links:
More on projection matrices
gl-matrix
Using view/projection matrices
Update
Here's another version that has keyboard controls. Kinda fun. You can see the difference between rotating and parallax from strafing. Works best full page. (Fiddle for this is here or see below.)
var c = document.getElementById('c');
var n = c.getContext('2d');
// View matrix, defines where you're looking
var viewMtx = mat4.create();
// Projection matrix, defines how the view maps onto the screen
var projMtx = mat4.create();
// Adapted from http://stackoverflow.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api
function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) {
// We'll assume input parameters are sane.
field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians
var frustum_depth = far_dist - near_dist;
var one_over_depth = 1 / frustum_depth;
var e11 = 1.0 / Math.tan(0.5 * field_of_view);
var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio;
var e22 = far_dist * one_over_depth;
var e32 = (-far_dist * near_dist) * one_over_depth;
return [
e00, 0, 0, 0,
0, e11, 0, 0,
0, 0, e22, e32,
0, 0, 1, 0
];
}
// Make a view matrix with a simple rotation about the Y axis (up-down axis)
function ComputeViewMtx(angle) {
angle = angle * Math.PI / 180.0; // Convert degrees to radians
return [
Math.cos(angle), 0, Math.sin(angle), 0,
0, 1, 0, 0,
-Math.sin(angle), 0, Math.cos(angle), 0,
0, 0, -250, 1
];
}
projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true);
var angle = 0;
var viewProjMtx = mat4.create();
var minDist = 100;
var maxDist = 1000;
function Star() {
var d = 0;
do {
// Create random points in a cube.. but not too close.
this.x = Math.random() * maxDist - (maxDist / 2);
this.y = Math.random() * maxDist - (maxDist / 2);
this.z = Math.random() * maxDist - (maxDist / 2);
var d = this.x * this.x +
this.y * this.y +
this.z * this.z;
} while (
d > maxDist * maxDist / 4 || d < minDist * minDist
);
this.dist = 100;
}
Star.prototype.AsVector = function() {
return [this.x, this.y, this.z, 1];
}
var stars = [];
for (var i = 0; i < 5000; i++) stars.push(new Star());
var lastLoop = Date.now();
var dir = {
up: 0,
down: 1,
left: 2,
right: 3
};
var dirStates = [false, false, false, false];
var shiftKey = false;
var moveSpeed = 100.0;
var turnSpeed = 1.0;
function loop() {
var now = Date.now();
var dt = (now - lastLoop) / 1000.0;
lastLoop = now;
angle += 30.0 * dt;
//viewMtx = ComputeViewMtx(angle);
var tf = mat4.create();
if (dirStates[dir.up]) mat4.translate(tf, tf, [0, 0, moveSpeed * dt]);
if (dirStates[dir.down]) mat4.translate(tf, tf, [0, 0, -moveSpeed * dt]);
if (dirStates[dir.left])
if (shiftKey) mat4.rotate(tf, tf, -turnSpeed * dt, [0, 1, 0]);
else mat4.translate(tf, tf, [moveSpeed * dt, 0, 0]);
if (dirStates[dir.right])
if (shiftKey) mat4.rotate(tf, tf, turnSpeed * dt, [0, 1, 0]);
else mat4.translate(tf, tf, [-moveSpeed * dt, 0, 0]);
mat4.multiply(viewMtx, tf, viewMtx);
//console.log('---');
//console.log(projMtx);
//console.log(viewMtx);
mat4.multiply(viewProjMtx, projMtx, viewMtx);
//console.log(viewProjMtx);
n.beginPath();
n.rect(0, 0, c.width, c.height);
n.closePath();
n.fillStyle = '#000';
n.fill();
n.fillStyle = '#fff';
var v = vec4.create();
for (var i = 0; i < stars.length; i++) {
var star = stars[i];
vec4.transformMat4(v, star.AsVector(), viewProjMtx);
if (v[3] < 0) continue;
var d = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
v[0] /= v[3];
v[1] /= v[3];
v[2] /= v[3];
//v[3] /= v[3];
var x = (v[0] * 0.5 + 0.5) * c.width;
var y = (v[1] * 0.5 + 0.5) * c.height;
// Compute a visual size...
// This assumes all stars are the same size.
// It also doesn't scale with canvas size well -- we'd have to take more into account.
var s = 300 / d;
n.beginPath();
n.arc(x, y, s, 0, Math.PI * 2);
//n.rect(x, y, s, s);
n.closePath();
n.fill();
}
window.requestAnimationFrame(loop);
}
loop();
function keyToDir(evt) {
var d = -1;
if (evt.keyCode === 38) d = dir.up
else if (evt.keyCode === 37) d = dir.left;
else if (evt.keyCode === 39) d = dir.right;
else if (evt.keyCode === 40) d = dir.down;
return d;
}
window.onkeydown = function(evt) {
var d = keyToDir(evt);
if (d >= 0) dirStates[d] = true;
if (evt.keyCode === 16) shiftKey = true;
}
window.onkeyup = function(evt) {
var d = keyToDir(evt);
if (d >= 0) dirStates[d] = false;
if (evt.keyCode === 16) shiftKey = false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script>
<div>Click in this pane. Use up/down/left/right, hold shift + left/right to rotate.</div>
<canvas id="c" width="500" height="500"></canvas>
Update 2
Alain Jacomet Forte asked:
What is your recommended method of creating general purpose 3d and if you would recommend working at the matrices level or not, specifically perhaps to this particular scenario.
Regarding matrices: If you're writing an engine from scratch on any platform, then you're unavoidably going to end up working with matrices since they help generalize the basic 3D mathematics. Even if you use OpenGL/WebGL or Direct3D you're still going to end up making a view and projection matrix and additional matrices for more sophisticated purposes. (Handling normal maps, aligning world objects, skinning, etc...)
Regarding a method of creating general purpose 3d... Don't. It will run slow, and it won't be performant without a lot of work. Rely on a hardware-accelerated library to do the heavy lifting. Creating limited 3D engines for specific projects is fun and instructive (e.g. I want a cool animation on my webpage), but when it comes to putting the pixels on the screen for anything serious, you want hardware to handle that as much as you can for performance purposes.
Sadly, the web has no great standard for that yet, but it is coming in WebGL -- learn WebGL, use WebGL. It runs great and works well when it's supported. (You can, however, get away with an awful lot just using CSS 3D transforms and Javascript.)
If you're doing desktop programming, I highly recommend OpenGL via SDL (I'm not sold on SFML yet) -- it's cross-platform and well supported.
If you're programming mobile phones, OpenGL ES is pretty much your only choice (other than a dog-slow software renderer).
If you want to get stuff done rather than writing your own engine from scratch, the defacto for the web is Three.js (which I find effective but mediocre). If you want a full game engine, there's some free options these days, the main commercial ones being Unity and Unreal. Irrlicht has been around a long time -- never had a chance to use it, though, but I hear it's good.
But if you want to make all the 3D stuff from scratch... I always found how the software renderer in Quake was made a pretty good case study. Some of that can be found here.
You are resetting the stars 2d position each frame, then moving the stars (depending on how much time and speed of each star) - this is a bad way to achieve your goal. As you discovered, it gets very complex when you try to extend this solution to more scenarios.
A better way would be to set the stars 3d location only once (at initialization) then move a "camera" each frame (depending on time). When you want to render the 2d image you then calculate the stars location on screen. The location on screen depends on the stars 3d location and the current camera location.
This will allow you to move the camera (in any direction), rotate the camera (to any angle) and render the correct stars position AND keep your sanity.

Drawing a spiral on an HTML canvas using JavaScript

I have searched and haven't found anything really on how to draw spirals in canvas using JavaScript.
I thought it might be possible to do it with the bezier curve and if that didn't work use lineTo(), but that seemed a lot harder.
Also, to do that I'm guessing I would have to use trigonometry and graphing with polar coordinates and its been a while since I did that. If that is the case could you point me in the right direction on the math.
The Archimedean spiral is expressed as r=a+b(angle). Convert that into x, y coordinate, it will be expressed as x=(a+b*angle)*cos(angle), y=(a+b*angle)*sin(angle). Then you can put angle in a for loop and do something like this:
for (i=0; i< 720; i++) {
angle = 0.1 * i;
x=(1+angle)*Math.cos(angle);
y=(1+angle)*Math.sin(angle);
context.lineTo(x, y);
}
Note the above assumes a = 1 and b = 1.
Here is a jsfiddle link: http://jsfiddle.net/jingshaochen/xJc7M/
This is a slightly-changed, javascript-ified version of a Java spiral I once borrowed from here
It uses lineTo() and its not all that hard.
<!DOCTYPE HTML>
<html><body>
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #c3c3c3;"></canvas>
<script type="text/javascript">
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
var centerX = 150;
var centerY = 150;
cxt.moveTo(centerX, centerY);
var STEPS_PER_ROTATION = 60;
var increment = 2*Math.PI/STEPS_PER_ROTATION;
var theta = increment;
while( theta < 40*Math.PI) {
var newX = centerX + theta * Math.cos(theta);
var newY = centerY + theta * Math.sin(theta);
cxt.lineTo(newX, newY);
theta = theta + increment;
}
cxt.stroke();
</script></body></html>
Here's a function I wrote for drawing Archimedean spirals:
CanvasRenderingContext2D.prototype.drawArchimedeanSpiral =
CanvasRenderingContext2D.prototype.drawArchimedeanSpiral ||
function(centerX, centerY, stepCount, loopCount,
innerDistance, loopSpacing, rotation)
{
this.beginPath();
var stepSize = 2 * Math.PI / stepCount,
endAngle = 2 * Math.PI * loopCount,
finished = false;
for (var angle = 0; !finished; angle += stepSize) {
// Ensure that the spiral finishes at the correct place,
// avoiding any drift introduced by cumulative errors from
// repeatedly adding floating point numbers.
if (angle > endAngle) {
angle = endAngle;
finished = true;
}
var scalar = innerDistance + loopSpacing * angle,
rotatedAngle = angle + rotation,
x = centerX + scalar * Math.cos(rotatedAngle),
y = centerY + scalar * Math.sin(rotatedAngle);
this.lineTo(x, y);
}
this.stroke();
}
there is a fine free tool that will help if you have illustrator
ai2canvas
it will create all the curves to javascript in html canvas tag for you!
(if you are looking for archmedes spiral than you will first have to get it from coreldraw and copy that to illustrator, because the default spiral tool enlarges the angle with each point)
this is example of drawing spiral using function below:
spiral(ctx, {
start: {//starting point of spiral
x: 200,
y: 200
},
angle: 30 * (Math.PI / 180), //angle from starting point
direction: false,
radius: 100, //radius from starting point in direction of angle
number: 3 // number of circles
});
spiral drawing code:
spiral = function(ctx,obj) {
var center, eAngle, increment, newX, newY, progress, sAngle, tempTheta, theta;
sAngle = Math.PI + obj.angle;
eAngle = sAngle + Math.PI * 2 * obj.number;
center = {
x: obj.start.x + Math.cos(obj.angle) * obj.radius,
y: obj.start.y + Math.sin(obj.angle) * obj.radius
};
increment = 2 * Math.PI / 60/*steps per rotation*/;
theta = sAngle;
ctx.beginPath();
ctx.moveTo(center.x, center.y);
while (theta <= eAngle + increment) {
progress = (theta - sAngle) / (eAngle - sAngle);
tempTheta = obj.direction ? theta : -1 * (theta - 2 * obj.angle);
newX = obj.radius * Math.cos(tempTheta) * progress;
newY = obj.radius * Math.sin(tempTheta) * progress;
theta += increment;
ctx.lineTo(center.x + newX, center.y + newY);
}
ctx.stroke();
};
The following code approximates a spiral as a collection of quarters of a circle each with a slightly larger radius. It might look worse than an Archimedes spiral for small turning numbers but it should run faster.
function drawSpiral(ctx, centerx, centery, innerRadius, outerRadius, turns=2, startAngle=0){
ctx.save();
ctx.translate(centerx, centery);
ctx.rotate(startAngle);
let r = innerRadius;
let turns_ = Math.floor(turns*4)/4;
let dr = (outerRadius - innerRadius)/turns_/4;
let cx = 0, cy = 0;
let directionx = 0, directiony = -1;
ctx.beginPath();
let angle=0;
for(; angle < turns_*2*Math.PI; angle += Math.PI/2){
//draw a quarter arc around the center point (x, cy)
ctx.arc( cx, cy, r, angle, angle + Math.PI/2);
//move the center point and increase the radius so we can draw a bigger arc
cx += directionx*dr;
cy += directiony*dr;
r+= dr;
//rotate direction vector by 90 degrees
[directionx, directiony] = [ - directiony, directionx ];
}
//draw the remainder of the last quarter turn
ctx.arc( cx, cy, r, angle, angle + 2*Math.PI*( turns - turns_ ))
ctx.stroke();
ctx.restore();
}
Result:

Categories

Resources