JavaScript Canvas Fabric Animation: set Background Image - javascript

I found this amazing plugin which simulates the physics of fabric: http://codepen.io/anon/pen/mxjKC
document.getElementById('close').onmousedown = function(e) {
e.preventDefault();
document.getElementById('info').style.display = 'none';
return false;
};
// settings
var physics_accuracy = 3,
mouse_influence = 20,
mouse_cut = 5,
gravity = 1200,
cloth_height = 30,
cloth_width = 50,
start_y = 20,
spacing = 7,
tear_distance = 60;
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
var canvas,
ctx,
cloth,
boundsx,
boundsy,
mouse = {
down: false,
button: 1,
x: 0,
y: 0,
px: 0,
py: 0
};
var Point = function (x, y) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.vx = 0;
this.vy = 0;
this.pin_x = null;
this.pin_y = null;
this.constraints = [];
};
Point.prototype.update = function (delta) {
if (mouse.down) {
var diff_x = this.x - mouse.x,
diff_y = this.y - mouse.y,
dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y);
if (mouse.button == 1) {
if (dist < mouse_influence) {
this.px = this.x - (mouse.x - mouse.px) * 1.8;
this.py = this.y - (mouse.y - mouse.py) * 1.8;
}
} else if (dist < mouse_cut) this.constraints = [];
}
this.add_force(0, gravity);
delta *= delta;
nx = this.x + ((this.x - this.px) * .99) + ((this.vx / 2) * delta);
ny = this.y + ((this.y - this.py) * .99) + ((this.vy / 2) * delta);
this.px = this.x;
this.py = this.y;
this.x = nx;
this.y = ny;
this.vy = this.vx = 0
};
Point.prototype.draw = function () {
if (!this.constraints.length) return;
var i = this.constraints.length;
while (i--) this.constraints[i].draw();
};
Point.prototype.resolve_constraints = function () {
if (this.pin_x != null && this.pin_y != null) {
this.x = this.pin_x;
this.y = this.pin_y;
return;
}
var i = this.constraints.length;
while (i--) this.constraints[i].resolve();
this.x > boundsx ? this.x = 2 * boundsx - this.x : 1 > this.x && (this.x = 2 - this.x);
this.y < 1 ? this.y = 2 - this.y : this.y > boundsy && (this.y = 2 * boundsy - this.y);
};
Point.prototype.attach = function (point) {
this.constraints.push(
new Constraint(this, point)
);
};
Point.prototype.remove_constraint = function (constraint) {
this.constraints.splice(this.constraints.indexOf(constraint), 1);
};
Point.prototype.add_force = function (x, y) {
this.vx += x;
this.vy += y;
};
Point.prototype.pin = function (pinx, piny) {
this.pin_x = pinx;
this.pin_y = piny;
};
var Constraint = function (p1, p2) {
this.p1 = p1;
this.p2 = p2;
this.length = spacing;
};
Constraint.prototype.resolve = function () {
var diff_x = this.p1.x - this.p2.x,
diff_y = this.p1.y - this.p2.y,
dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y),
diff = (this.length - dist) / dist;
if (dist > tear_distance) this.p1.remove_constraint(this);
var px = diff_x * diff * 0.5;
var py = diff_y * diff * 0.5;
this.p1.x += px;
this.p1.y += py;
this.p2.x -= px;
this.p2.y -= py;
};
Constraint.prototype.draw = function () {
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
};
var Cloth = function () {
this.points = [];
var start_x = canvas.width / 3 - cloth_width * spacing / 2;
for (var y = 0; y <= cloth_height; y++) {
for (var x = 0; x <= cloth_width; x++) {
var p = new Point(start_x + x * spacing, start_y + y * spacing);
x != 0 && p.attach(this.points[this.points.length - 1]);
y == 0 && p.pin(p.x, p.y);
y != 0 && p.attach(this.points[x + (y - 1) * (cloth_width + 1)])
this.points.push(p);
}
}
};
Cloth.prototype.update = function () {
var i = physics_accuracy;
while (i--) {
var p = this.points.length;
while (p--) this.points[p].resolve_constraints();
}
i = this.points.length;
while (i--) this.points[i].update(.016);
};
Cloth.prototype.draw = function () {
ctx.beginPath();
var i = cloth.points.length;
while (i--) cloth.points[i].draw();
ctx.stroke();
};
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
cloth.update();
cloth.draw();
requestAnimFrame(update);
}
function start() {
canvas.onmousedown = function (e) {
mouse.button = e.which;
mouse.px = mouse.x;
mouse.py = mouse.y;
var rect = canvas.getBoundingClientRect();
mouse.x = e.clientX - rect.left,
mouse.y = e.clientY - rect.top,
mouse.down = true;
e.preventDefault();
};
canvas.onmouseup = function (e) {
mouse.down = false;
e.preventDefault();
};
canvas.onmousemove = function (e) {
mouse.px = mouse.x;
mouse.py = mouse.y;
var rect = canvas.getBoundingClientRect();
mouse.x = e.clientX - rect.left,
mouse.y = e.clientY - rect.top,
e.preventDefault();
};
canvas.oncontextmenu = function (e) {
e.preventDefault();
};
boundsx = canvas.width - 1;
boundsy = canvas.height - 1;
ctx.strokeStyle = 'red';
cloth = new Cloth();
update();
}
window.onload = function () {
canvas = document.getElementById('c');
ctx = canvas.getContext('2d');
canvas.width = 1120;
canvas.height = 700;
canvas.style.width = '560px';
canvas.style.height = '350px';
ctx.scale(2,2);
start();
};
<div id=info>
<input type="button" value="close" id="close"></input>
<canvas id="c"></canvas>
</div>
I would love to know how to use it for an image instead of the net but I don't know where to start at all.
What is the theory behind that - where could I place the image?

You can not put an image on that mesh because the resulting squares are not 2d. This answer gives a little detail as to why.
To do what you want you need to use webGL. It is a straightforward conversion, the cloth is a mesh (vertices and polygons connecting the vertices) where each vertex will hold a texture coordinate and you let the sim run as is. There should be plenty of examples of how to map a image onto a mesh in stackoverflow. You might consider using three.js if you are new to 3D and coding.
Here is an example using three.js and a verlet cloth with texture mapped onto it.

Related

How to implement object rotation animation?

There is a falling balls code https://jsfiddle.net/d1e8x7wk/,
which is generated using canvas
Code also added to this editor
window.addEventListener('load', () => {
//---------------------------------------
// Set up ball options
//---------------------------------------
const imgBalls = [
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png'
]
let ballCount = imgBalls.length, // How many balls
DAMPING = 0.4, // Damping
GRAVITY = 0.01, // Gravity strength
SPEED = 5, // Ball speed
ballAdditionTime = 100, // How fast are balls added
ballSrc = imgBalls, // Ball image source
topOffset = 400, // Adjust this for initial ball spawn point
xOffset = 0, // left offset
yOffset = 0, // bottom offset
ballDensity = 20, // How dense are the balls
ball_1_size = 200, // Ball 1 size
ball_2_size = 180, // Ball 2 size
ball_3_size = 62, // Ball 6 size
canvasWidth = 1500, // Canvas width
canvasHeight = 1000, // Canvas height
stackBall = true, // Stack the balls (or false is overlap)
ballsLoaded = 0,
stopAnimation = false;
//---------------------------------------
// Canvas globals
//---------------------------------------
let canvas,
ctx,
TWO_PI = Math.PI * 2,
balls = [],
vel_x,
vel_y;
let rect = {
x: 0,
y: 0,
w: canvasWidth,
h: canvasHeight
};
//---------------------------------------
// do the animation
//---------------------------------------
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, ballAdditionTime);
};
//---------------------------------------
// set up the ball
//---------------------------------------
const Ball = function(x, y, radius, num) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.fx = 0;
this.fy = 0;
this.radius = radius;
this.num = num;
this.angle = 0;
// Different ball sizes
let random = Math.round(Math.random() * imgBalls.length)
if (random === 0) {
this.width = ball_1_size;
this.height = ball_1_size;
if (stackBall) {
this.radius = ball_1_size / 2;
}
} else if (random === 1 || random === 2) {
this.width = ball_2_size;
this.height = ball_2_size;
if (stackBall) {
this.radius = ball_2_size / 2;
}
} else {
this.width = ball_3_size;
this.height = ball_3_size;
if (stackBall) {
this.radius = ball_3_size / 2;
}
}
ctx.rotate(this.angle * Math.PI / 180);
};
//---------------------------------------
// Apply the physics
//---------------------------------------
Ball.prototype.apply_force = function(delta) {
delta *= delta;
this.fy += GRAVITY;
this.x += this.fx * delta;
this.y += this.fy * delta;
this.fx = this.fy = 0;
};
//---------------------------------------
// Newtonian motion algorithm
//---------------------------------------
Ball.prototype.velocity = function() {
var nx = this.x * 2 - this.px;
var ny = this.y * 2 - this.py;
this.px = this.x;
this.py = this.y;
this.x = nx;
this.y = ny;
};
//---------------------------------------
// Ball prototype
//---------------------------------------
Ball.prototype.draw = function(ctx) {
img = new Image();
img.src = imgBalls[this.num];
if (stackBall) {
ctx.drawImage(
img,
this.x - this.radius - xOffset,
this.y - this.radius - xOffset,
this.width,
this.height
);
} else {
ctx.drawImage(
img,
this.x - xOffset,
this.y - yOffset,
this.width,
this.height
);
}
};
//---------------------------------------
// resolve collisions (ball on ball)
//---------------------------------------
let resolve_collisions = function(ip) {
let i = balls.length;
while (i--) {
let ball_1 = balls[i];
let n = balls.length;
while (n--) {
if (n == i) continue;
let ball_2 = balls[n];
let diff_x = ball_1.x - ball_2.x;
let diff_y = ball_1.y - ball_2.y;
let length = diff_x * diff_x + diff_y * diff_y;
let dist = Math.sqrt(length);
let real_dist = dist - (ball_1.radius + ball_2.radius);
if (real_dist < 0) {
let vel_x1 = ball_1.x - ball_1.px;
let vel_y1 = ball_1.y - ball_1.py;
let vel_x2 = ball_2.x - ball_2.px;
let vel_y2 = ball_2.y - ball_2.py;
let depth_x = diff_x * (real_dist / dist);
let depth_y = diff_y * (real_dist / dist);
ball_1.x -= depth_x * 0.5;
ball_1.y -= depth_y * 0.5;
ball_2.x += depth_x * 0.5;
ball_2.y += depth_y * 0.5;
if (ip) {
let pr1 = (DAMPING * (diff_x * vel_x1 + diff_y * vel_y1)) / length;
let pr2 = (DAMPING * (diff_x * vel_x2 + diff_y * vel_y2)) / length;
vel_x1 += pr2 * diff_x - pr1 * diff_x;
vel_x2 += pr1 * diff_x - pr2 * diff_x;
vel_y1 += pr2 * diff_y - pr1 * diff_y;
vel_y2 += pr1 * diff_y - pr2 * diff_y;
ball_1.px = ball_1.x - vel_x1;
ball_1.py = ball_1.y - vel_y1;
ball_2.px = ball_2.x - vel_x2;
ball_2.py = ball_2.y - vel_y2;
}
}
}
}
};
//---------------------------------------
// Bounce off the walls
//---------------------------------------
let check_walls = function() {
let i = balls.length;
while (i--) {
let ball = balls[i];
if (ball.x < ball.radius) {
let vel_x = ball.px - ball.x;
ball.x = ball.radius;
ball.px = ball.x - vel_x * DAMPING;
} else if (ball.x + ball.radius > canvas.width) {
vel_x = ball.px - ball.x;
ball.x = canvas.width - ball.radius;
ball.px = ball.x - vel_x * DAMPING;
}
// Ball is new. So don't do collision detection until it hits the canvas. (with an offset to stop it snapping)
if (ball.y > 100) {
if (ball.y < ball.radius) {
let vel_y = ball.py - ball.y;
ball.y = ball.radius;
ball.py = ball.y - vel_y * DAMPING;
} else if (ball.y + ball.radius > canvas.height) {
vel_y = ball.py - ball.y;
ball.y = canvas.height - ball.radius;
ball.py = ball.y - vel_y * DAMPING;
}
}
}
};
//---------------------------------------
// Add a ball to the canvas
//---------------------------------------
let add_ball = function(num) {
let x = Math.random() * canvas.width;
let y = Math.random() * canvas.height;
let r = 30 + Math.random() * ballDensity;
let diff_x = x;
let diff_y = y;
let dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y);
balls.push(new Ball(x, y, r, num));
};
//---------------------------------------
// iterate balls
//---------------------------------------
let update = function() {
let iter = 1;
let delta = SPEED / iter;
while (iter--) {
let i = balls.length;
while (i--) {
balls[i].apply_force(delta);
balls[i].velocity();
}
resolve_collisions();
check_walls();
i = balls.length;
while (i--) {
balls[i].velocity();
let ball = balls[i];
}
resolve_collisions();
check_walls();
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
i = balls.length;
while (i--) {
balls[i].draw(ctx);
}
requestAnimFrame(update);
};
//---------------------------------------
// Set up the canvas object
//---------------------------------------
function doBalls() {
stopAnimation = false;
canvas = document.getElementById("balls");
ctx = canvas.getContext("2d");
let $canvasDiv = document.querySelector(".section");
function respondCanvas() {
canvas.height = $canvasDiv.clientHeight;
canvas.width = $canvasDiv.clientWidth;
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
respondCanvas();
ballAdd();
}
function ballAdd() {
let count = 0;
let timer = setInterval(function() {
addBallTimer();
}, 100);
let addBallTimer = function() {
add_ball(count % ballCount);
count++;
if (count === ballCount) {
stopTimer();
}
};
let stopTimer = function() {
clearInterval(timer);
};
update();
}
doBalls();
});
.section {
position: relative;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #000;
}
<div class="section">
<canvas id="balls"></canvas>
</div>
I'm trying to make a rotation the balls
const Ball = function(x, y, radius, num) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.fx = 0;
this.fy = 0;
this.radius = radius;
this.num = num;
this.angle = 0;
// Different ball sizes
let random = Math.round(Math.random() * imgBalls.length)
if (random === 0) {
this.width = ball_1_size;
this.height = ball_1_size;
if (stackBall) {
this.radius = ball_1_size / 2;
}
} else if (random === 1 || random === 2) {
this.width = ball_2_size;
this.height = ball_2_size;
if (stackBall) {
this.radius = ball_2_size / 2;
}
} else {
this.width = ball_3_size;
this.height = ball_3_size;
if (stackBall) {
this.radius = ball_3_size / 2;
}
}
ctx.rotate(this.angle * Math.PI / 180);
};
After reading the information,
I wrote this code, but it does not work for me
ctx.rotate(this.angle * Math.PI / 180);
I must have misunderstood how to do this, tell me how to properly rotate the balls
Thanks in advance
You will need to
increase the angle at some point in your code
apply a rotation transform to rotate around each ball center before each drawImage call
ctx.save();
ctx.translate(+this.x, +this.y);
ctx.rotate(this.angle++ * Math.PI / 180);
ctx.translate(-this.x, -this.y);
ctx.drawImage
ctx.restore()
ctx.save() and ctx.restore() is called to restore the canvas transform after each draw call.
Please read
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate#rotating_a_shape_around_its_center
for details on applying transforms to Canvas.

Is there an error in the way this simulation calculates gravitational attraction and body collision?

N-Body gravity simulation seems to be working fine at first glance, and the same is true for body collisions, but once gravitationally attracted objects start to collide, they start to spiral around each other frantically and the collection of them as a whole have very erratic motion... The code (html-javascript) will be included below, and to reproduce what I'm talking about, you can create a new body by clicking in a random location on the screen.
The math for gravitational attraction is done in the Body.prototype.gravityCalc() method of the Body object type (line 261). The math for the collision resolution is found in the dynamic collision section of the bodyHandle() function (line 337).
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// event handling
document.addEventListener('keydown', keyDown);
document.addEventListener('mousedown', mouseDown)
document.addEventListener('mouseup', mouseUp)
document.addEventListener('mousemove', mouseMove);
document.addEventListener('touchstart', touchStart);
document.addEventListener('touchmove', touchMove);
document.addEventListener('touchend', touchEnd);
window.addEventListener('resize', resize);
window.onload = function() {reset()}
mouseDown = false;
nothingGrabbed = true;
mouseX = 0;
mouseY = 0;
function keyDown(data) {
if(data.key == "r") {
clearInterval(loop);
reset();
}
else if(data.key == 'g') {
gravityOn = !gravityOn;
}
else if(data.key == 'Delete') {
for(i = 0; i < bodies.length ; i++) {
if(((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies.splice(i, 1);
}
}
}
else if(data.key == 'c') {
gravity_c *= -1;
}
else if(data.key == 'f') {
falling = !falling;
}
else if(data.key == 'a') {
acceleration *= -1;
}
}
function mouseDown(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function mouseUp(data) {
mouseDown = false;
nothingGrabbed = true;
for(i = 0; i < bodies.length; i++) {
bodies[i].grabbed = false
}
}
function mouseMove(data) {
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function touchStart(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchMove(data) {
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchEnd(data) {
mouseDown = false;
nothingGrabbed = true;
for(i=0;i<bodies.length;i++) {
bodies[i].grabbed = false;
}
}
function resize(data) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize Variables
function reset() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.color = 'rgb(70, 70, 70)';
scale = Math.min(canvas.width, canvas.height);
fps = 120;
running = true;
loop = setInterval(main, 1000/fps);
gravityOn = true // if true, objects are gravitationally attracted to each other
gravity_c = 334000 // universe's gravitational constant
boundaryCollision = true // if true, objects collide with edges of canvas
wallDampen = 0.7 // number to multiply by when an objects hit a wall
bodyCollision = true // if true, bodies will collide with each other
bodyDampen = 0.4 // number to multiply when two objects collide
falling = false // if true, objects will fall to the bottom of the screen
acceleration = 400
bodies = [] // a list of each Body object
collidingPairs = [] // a list of pairs of colliding bodies
/*
var bounds = 200;
for(i = 0; i<70; i++) { // randomly place bodies
Body.create({
x: Math.floor(Math.random()*canvas.width),
y: Math.floor(Math.random()*canvas.height),
a: Math.random()*Math.PI*2,
xV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
yV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
mass: Math.ceil(Math.random()*23)
})
} */
/*
Body.create({
x: canvas.width/2 - 50,
xV: 10,
yV: 0,
aV: 3,
y: canvas.height/2 + 0,
mass: 10
});
Body.create({
x: canvas.width/2 + 50,
xV: 0,
aV: 0,
y: canvas.height/2,
mass: 10
});
*/
Body.create({
x: canvas.width/2,
y: canvas.height/2,
mass: 24,
xV: -10.83
});
Body.create({
x: canvas.width/2,
y: canvas.height/2 + 150,
mass: 1,
xV: 260,
color: 'teal'
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Body Type Object
function Body(params) {
this.x = params.x || canvas.width/2;
this.y = params.y || canvas.height/2;
this.a = params.a || 0;
this.xV = params.xV || 0;
this.yV = params.yV || 0;
this.aV = params.aV || 0;
this.xA = params.xA || 0;
this.yA = params.yA || 0;
this.aA = params.aA || 0;
this.grabbed = false;
this.edgeBlock = params.edgeBlock || boundaryCollision;
this.gravity = params.gravityOn || gravityOn;
this.mass = params.mass || 6;
this.density = params.density || 0.008;
this.radius = params.radius || (this.mass/(Math.PI*this.density))**0.5;
this.color = params.color || 'crimson';
this.lineWidth = params.lineWidth || 2;
}
Body.create = function(params) {
bodies.push(new Body(params));
}
Body.prototype.move = function() {
this.xV += this.xA/fps;
this.yV += this.yA/fps;
this.aV += this.aA/fps;
this.x += this.xV/fps;
this.y += this.yV/fps;
this.a += this.aV/fps;
if(this.edgeBlock) {
if(this.x + this.radius > canvas.width) {
this.x = canvas.width - this.radius;
this.xV *= -wallDampen
}
else if(this.x - this.radius < 0) {
this.x = this.radius;
this.xV *= -wallDampen;
}
if(this.y + this.radius > canvas.height) {
this.y = canvas.height - this.radius;
this.yV *= -wallDampen;
}
else if(this.y - this.radius < 0) {
this.y = this.radius;
this.yV *= -wallDampen;
}
}
if(this.grabbed) {
this.xA = 0;
this.yA = 0;
this.xV = 0;
this.yV = 0;
this.x = mouseX;
this.y = mouseY;
}
}
Body.prototype.draw = function() {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.lineWidth;
ctx.fillStyle = this.color;
ctx.arc(this.x, canvas.height - this.y, this.radius, 0, Math.PI*2, true);
ctx.fill();
ctx.stroke();
ctx.closePath()
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.linewidth;
ctx.moveTo(this.x, canvas.height - this.y);
ctx.lineTo(this.x + this.radius*Math.cos(this.a), canvas.height - (this.y + this.radius*Math.sin(this.a)))
ctx.stroke();
ctx.closePath();
}
// calculates gravitational attraction to 'otherObject'
Body.prototype.gravityCalc = function(otherObject) {
var x1 = this.x;
var y1 = this.y;
var x2 = otherObject.x;
var y2 = otherObject.y;
var distSquare = ((x2-x1)**2 + (y2-y1)**2);
var val = (gravity_c*otherObject.mass)/((distSquare)**(3/2));
var xA = val * (x2 - x1);
var yA = val * (y2 - y1);
return [xA, yA]
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Physics Code
function bodyHandle() {
for(i = 0; i < bodies.length; i++) {
if(mouseDown && nothingGrabbed) {
if(Math.abs((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies[i].grabbed = true;
nothingGrabbed = false;
}
}
bodies[i].draw()
if(running) {
if(falling) {
bodies[i].yV -= acceleration/fps;
}
bodies[i].move();
}
bodies[i].xA = 0;
bodies[i].yA = 0;
collidingPairs = []
if(gravityOn || bodyCollision) {
for(b = 0; b < bodies.length; b++) {
if(i != b) {
if(bodyCollision) {
var x1 = bodies[i].x;
var y1 = bodies[i].y;
var x2 = bodies[b].x;
var y2 = bodies[b].y;
var rSum = bodies[i].radius + bodies[b].radius;
var dist = { // vector
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
if(dist.mag <= rSum) { // static collision
var overlap = rSum - dist.mag;
bodies[i].x -= overlap/2 * dist.norm.i;
bodies[i].y -= overlap/2 * dist.norm.j;
bodies[b].x += overlap/2 * dist.norm.i;
bodies[b].y += overlap/2 * dist.norm.j;
collidingPairs.push([bodies[i], bodies[b]]);
}
}
if(gravityOn) {
if(bodies[i].gravity) {
var accel = bodies[i].gravityCalc(bodies[b]);
bodies[i].xA += accel[0];
bodies[i].yA += accel[1];
}
}
}
}
}
for(c = 0; c < collidingPairs.length; c++) { // dynamic collision
var x1 = collidingPairs[c][0].x;
var y1 = collidingPairs[c][0].y;
var r1 = collidingPairs[c][0].radius;
var x2 = collidingPairs[c][1].x;
var y2 = collidingPairs[c][1].y;
var r2 = collidingPairs[c][1].radius;
var dist = { // vector from b1 to b2
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
var m1 = collidingPairs[c][0].mass;
var m2 = collidingPairs[c][1].mass;
var norm = { // vector normal along 'wall' of collision
i: -dist.j/(((dist.i)**2 + (-dist.j)**2)**0.5),
j: dist.i/(((dist.i)**2 + (-dist.j)**2)**0.5)
}
var perp = { // vector normal pointing from b1 to b2
i: dist.norm.i,
j: dist.norm.j
}
var vel1 = { // vector of b1 velocity
i: collidingPairs[c][0].xV,
j: collidingPairs[c][0].yV,
dot: function(vect) {
return collidingPairs[c][0].xV * vect.i + collidingPairs[c][0].yV * vect.j
}
}
var vel2 = { // vector of b2 velocity
i: collidingPairs[c][1].xV,
j: collidingPairs[c][1].yV,
dot: function(vect) {
return collidingPairs[c][1].xV * vect.i + collidingPairs[c][1].yV * vect.j
}
}
// new velocities along perp^ of b1 and b2
var nV1Perp = (vel1.dot(perp))*(m1-m2)/(m1+m2) + (vel2.dot(perp))*(2*m2)/(m1+m2);
var nV2Perp = (vel1.dot(perp))*(2*m1)/(m1+m2) + (vel2.dot(perp))*(m2-m1)/(m1+m2);
/* testing rotation after collision
// velocities of the points of collision on b1 and b2
var pVel1M = vel1.dot(norm) + collidingPairs[c][0].aV*r1;
var pVel2M = vel2.dot(norm) + collidingPairs[c][1].aV*r2;
// moment of inertia for b1 and b2
var I1 = 1/2 * m1 * r1**2;
var I2 = 1/2 * m2 * r2**2;
// new velocities of the points of collisions on b1 and b2
var newpVel1M = ((I1-I2)/(I1+I2))*pVel1M + ((2*I2)/(I1+I2))*pVel2M;
var newpVel2M = ((2*I1)/(I1+I2))*pVel1M + ((I2-I1)/(I1+I2))*pVel2M;
var vectToCol1 = { // vector from x1,y1 to point of collision on b1
i: r1*perp.i,
j: r1*perp.j
};
var vectToCol2 = { // vector from x2,y2 to point of collision on b2
i: r2*-perp.i,
j: r2*-perp.j
};
// sign of cross product of pVelM and vectToCol
var vCrossR1 = (pVel1M*norm.i)*(vectToCol1.j) - (pVel1M*norm.j)*(vectToCol1.i);
vCrossR1 = vCrossR1/Math.abs(vCrossR1);
var vCrossR2 = (pVel2M*norm.i)*(vectToCol2.j) - (pVel2M*norm.j)*(vectToCol2.i);
vCrossR2 = vCrossR2/Math.abs(vCrossR2);
collidingPairs[c][0].aV = vCrossR1 * (newpVel1M)/r1;
collidingPairs[c][1].aV = vCrossR2 * (newpVel2M)/r2;
/* draw collision point velocity vectors [debugging]
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(x1 + vectToCol1.i, canvas.height - (y1 + vectToCol1.j));
ctx.lineTo((x1+vectToCol1.i) + pVel1M*norm.i, (canvas.height- (y1+vectToCol1.j + pVel1M*norm.j)));
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = 'white';
ctx.moveTo(x2 + vectToCol2.i, canvas.height - (y2 + vectToCol2.j));
ctx.lineTo((x2+vectToCol2.i) + pVel2M*norm.i, (canvas.height- (y2+vectToCol2.j + pVel2M*norm.j)));
ctx.stroke();
ctx.closePath();
console.log(pVel1M, pVel2M);
clearInterval(loop);
*/
collidingPairs[c][0].xV = vel1.dot(norm)*norm.i + nV1Perp*perp.i * bodyDampen;
collidingPairs[c][0].yV = vel1.dot(norm)*norm.j + nV1Perp*perp.j * bodyDampen;
collidingPairs[c][1].xV = vel2.dot(norm)*norm.i + nV2Perp*perp.i * bodyDampen;
collidingPairs[c][1].yV = vel2.dot(norm)*norm.j + nV2Perp*perp.j * bodyDampen;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Main Loop
function main() {
// blank out canvas
ctx.fillStyle = canvas.color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
bodyHandle();
if(nothingGrabbed && mouseDown) {
bodies.push(new Body({x: mouseX,
y: mouseY,
mass: 90}));
bodies[bodies.length-1].move();
bodies[bodies.length-1].draw();
}
}
<html>
<meta name='viewport' content='width=device-width,height=device-height'>
<body>
<canvas id="canvas" width='300px' height='300px'></canvas>
<style>
body {
padding: 0;
margin: 0;
}
canvas {
padding: 0;
margin: 0;
}
</style>
</html>
I cannot tell you much about the code. Personally it seems to me that the animations could be correct.
If you want to test your code you could try to test if laws of conservation of energy and momentum are respected. You could, for example, sum the momentum of every object (mass times velocity) and see if the number are maintained constant when there are no forces from the outside (collisions with the wall). To do this I would suggest to make the free space available larger. Another quantity is the total energy (kinetic plus potential) which is a bit harder, but still easy to compute (to compute tot. pot. energy you have to sum over all pairs).

Javascript line and point angle collision

I want to move the balls in correct direction when bat collide with them, like in this example
It doesn't have to be this good though. Working correctly is the goal here.
So far, what I've got can be seen in the following url:
https://jsfiddle.net/qphqvntx/4/
The problem is with the collision, when bat hit the balls. It's behaving very weird.
//Canvas Variable
var mainCanvas = document.getElementById("mainCanvas");
var ctx = mainCanvas.getContext("2d");
var canvasHeight = mainCanvas.height;
var canvasWidth = mainCanvas.width;
var borderRadius = 10;
var bottomRadius = 120;
var gravity = 0.2;
var animate = false;
//Ball Object related variable
var balls = [];
var ballObj = {
x: 0,
y: 0,
velocity: {x: 0, y: 0},
};
var ball = '';
var debugBall = '';
var ballRadius = 10;
var dumping = 0.99;
var ballSpeed = 6;
var force = 1.8;
var setBallTimer = 150;
var updateBallTimer = 0;
var target;
var distanceX;
var distancey;
var j, balls2;
//Bat related variable
var batXPos = 200;
var batYPos = canvasHeight - bottomRadius - 120;
var batHeight = 100;
var batWidth = 10;
var mouse = [0, 0];
var point = [170, 175];
var dx, dy;
//run related variable
var oneRunLine = 60;
var twoRunLine = 90;
var fourRunLine = 100;
var outTopLine = 450;
var oneRunChain = canvasHeight - bottomRadius;
var twoRunChain = canvasHeight - bottomRadius - oneRunLine;
var fourRunChain = canvasHeight - bottomRadius - oneRunLine - twoRunLine;
var sixRunChain = canvasHeight - bottomRadius - oneRunLine - twoRunLine - fourRunLine;
var circleUnderMouse;
var mouse = {
x: 0,
y: 0,
down: false
}
var res;
var previousEvent = false;
function executeFrame() {
if (animate) {
requestAnimFrame(executeFrame);
}
ctx.lineWidth = "3";
ctx.strokeRect(0, 0, mainCanvas.width, mainCanvas.height);
ctx.strokeRect(0, 0, mainCanvas.width, mainCanvas.height - bottomRadius);
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.fillRect(0, 0, mainCanvas.width, mainCanvas.height);
update();
if (balls.length != '0') {
balls.forEach(function (ball, j, i) {
drawBall(ball);
createBat();
});
}
}
initializeGame();
function update() {
if (balls.length != '0') {
balls.forEach(function (ball, balli, ballj) {
ball.addGravity();
ball.velocity.y *= dumping;
ball.velocity.x *= dumping;
ball.y += ball.velocity.y;
ball.x += ball.velocity.x * force;
if (hitGround(ball) == true) {
ball.y = mainCanvas.height - bottomRadius;
ball.velocity.y = -Math.abs(ball.velocity.y);
}
if (hitLeft(ball) == true) {
ball.velocity.x = Math.abs(ball.velocity.x);
}
var hit = linePoint(point[0], point[1], mouse[0], mouse[1], ball.x, ball.y, ball);
if (hit == true) {
//console.log("hit");
} else {
//console.log("not yet");
}
for (j = balli + 1; j < balls.length; j++) {
balls2 = ballj[j];
var dx = balls2.x - ball.x;
var dy = balls2.y - ball.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < 2 * ballRadius) {
if (d === 0) {
d = 0.1;
}
var unitX = dx / d;
var unitY = dy / d;
var newForce = -1;
var forceX = unitX * newForce;
var forceY = unitY * newForce;
ball.velocity.x += forceX;
ball.velocity.y += forceY;
balls2.velocity.x -= forceX;
balls2.velocity.y -= forceY;
}
}
});
}
updateBallTimer++;
if (updateBallTimer >= setBallTimer) {
createBall();
updateBallTimer = 0;
}
}
function initializeGame() {
createBall();
createBat();
}
function createBall() {
randomPoint(50, 150);
ball = instantiateBall(canvasWidth, target.y, ballObj);
ball.velocity.x -= getRandomNumber(4, 6);
}
function instantiateBall(xPos, yPos, ball) {
ball = {
x: xPos,
y: yPos,
velocity: {x: 0, y: 0},
addGravity: function () {
return this.velocity.y += gravity;
},
onCanvas: false
};
balls.push(ball);
drawBall(ball);
return ball;
}
function drawBall(ball) {
if (ball != '') {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ballRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = "#000";
ctx.fill();
}
}
function createBat() {
// ctx.clearRect(0, 0, width, height)
dx = mouse[0] - point[0],
dy = mouse[1] - point[1];
rot = Math.atan2(dy, dx);
ctx.fillStyle = 'red';
ctx.fillRect(point[0], point[1], 10, 10);
ctx.fillStyle = 'gray';
ctx.save();
ctx.translate(point[0], point[1]);
ctx.rotate(rot);
ctx.fillRect(0, 0, batHeight, batWidth);
ctx.moveTo(point[0], point[1]);
ctx.lineTo(point[0] + (mouse[0] - point[0]) + 0.5, point[1] + (mouse[1] - point[1]) * 0.5);
//ctx.stroke();
ctx.restore();
}
function linePoint(x1, y1, x2, y2, px, py, ball) {
var d1 = dist(px, py, x1, y1);
var d2 = dist(px, py, x2, y2);
var lineLen = dist(x1, y1, x2, y2);
var buffer = 3;
var batAngle = Math.atan2(d2, d1);
var batAngleX = Math.sin(batAngle);
var batAngleY = -Math.cos(batAngle);
if (d1 + d2 >= lineLen - buffer && d1 + d2 <= lineLen + buffer) {
var d = 2 * (ball.velocity.x * batAngleX + ball.velocity.y * batAngleY);
ball.x -= d * batAngleX;
ball.x -= d * batAngleY;
ball.velocity.x -= d * batAngleX + res;
ball.velocity.y -= d * batAngleY + res;
return true;
}
return false;
}
function dist(x1, y1, x2, y2) {
var xs = x2 - x1,
ys = y2 - y1;
xs *= xs;
ys *= ys;
return Math.sqrt(xs + ys);
}
function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomPoint(min, max) {
var rndNum = Math.random() * (max - min) + min;
target = {
x: canvasWidth,
y: rndNum
};
// ctx.beginPath();
// ctx.arc(rndNum, canvasHeight, ballRadius, 0, 2 * Math.PI, false);
// ctx.fillStyle = "#ff0";
// ctx.fill();
}
function isOnCanvas(obj) {
if (obj.x <= mainCanvas.width && obj.y <= mainCanvas.height - bottomRadius) {
return true;
}
return false;
}
function hitGround(obj) {
if (obj.y >= mainCanvas.height - bottomRadius) {
//console.log("hit the ground");
return true;
}
return false;
}
function hitTop(obj) {
if (obj.y <= borderRadius) {
//console.log("hit the top");
return true;
}
return false;
}
function hitLeft(obj) {
if (obj.x <= borderRadius) {
//console.log("hit the left");
return true;
}
return false;
}
function hitRight(obj) {
if (obj.x >= mainCanvas.width - borderRadius) {
//console.log("hit the left");
return true;
}
return false;
}
function drawLineRandomPoint(moveX, moveY, lineX, lineY, strColor) {
ctx.beginPath();
ctx.moveTo(moveX, moveY);
ctx.lineTo(lineX, lineY);
ctx.strokeStyle = strColor;
ctx.stroke();
}
mainCanvas.addEventListener('mousemove', function (e) {
e.time = Date.now();
res = makeVelocityCalculator(e, previousEvent);
previousEvent = e;
mouse[0] = e.clientX;
mouse[1] = e.clientY;
});
function makeVelocityCalculator(e_init, e) {
var x = e_init.clientX, new_x, new_y, new_t,
x_dist, y_dist, interval, velocity,
y = e_init.clientY,
t;
if (e === false) {
return 0;
}
t = e.time;
new_x = e.clientX;
new_y = e.clientY;
new_t = Date.now();
x_dist = new_x - x;
y_dist = new_y - y;
interval = new_t - t;
// update values:
x = new_x;
y = new_y;
velocity = Math.sqrt(x_dist * x_dist + y_dist * y_dist) / interval;
return velocity;
}
mainCanvas.addEventListener('mouseover', function (e) {
animate = true;
executeFrame();
});
mainCanvas.addEventListener("mouseout", function (e) {
animate = false;
});
executeFrame();
window.requestAnimFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
<body>
<canvas id="mainCanvas" height="400" width="600"></canvas>
<script src="main.js" type="text/javascript"></script>
</body>

How to draw a curved spring in a HTML5 Canvas?

I would like to draw a spring in a HTML5 canvas, and show if that spring is at its rest length or not.
My spring is attached to a rectangular shape to some X-Y coordinates and defined as follows:
function Spring(restLenght, width, numRounds){
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.color = "green";
this.lineWidth = 6;
}
The parameters are explained in the picture below:
When the spring is at its rest length, the lines shall be parallel to each other, otherwise this means the spring is stretched or compressed. Then it will be immediately clear what state the spring is.
I'm stuck now with the bezierCurveTo() Method:
Here is my Fiddle: https://jsfiddle.net/df3mm8kz/1/
var cv = document.getElementById('cv'),
ctx = cv.getContext('2d'),
mouse = capture(cv),
box = new Box(120, 80, 0, 16),
spring = new Spring(160, 20, 2, 0.03, 0.9),
vx = 0,
vy = 0;
function Spring(restLenght, width, numRounds, k, f){
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.k = k;
this.f = f;
this.color = "green";
this.lineWidth = 6;
}
Spring.prototype.draw = function(ctx) {
var sPX, sPY, cP1X, cP1Y, cP2X, cP2Y, ePX, ePY;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
// length of one spring's round
var l = this.restLenght/(this.numRounds + 2);
// Initial segment, from spring anchor point to the first round
sPX = this.x1+l; sPY = this.y2;
ctx.lineTo(sPX, sPY);
// half width of spring's rounds
var hw = 0.5*this.width;
// half length of one spring's round
var hl = 0.5*l;
for(var i=0, n=this.numRounds; i<n; i++) {
cP1X = sPX + hl*i; cP1Y = sPY + hw;
cP2X = sPX + l*i; cp2Y = sPY + hw;
ePX = sPX + l*i; ePY = sPY;
ctx.bezierCurveTo(cP1X,cP1Y,cP2X,cp2Y,ePX,ePY);
cP1X = sPX + hl*i; cP1Y = sPY - hw;
cP2X = sPX + l*i; cp2Y = sPY - hw;
ePX = sPX + l*i; ePY = sPY;
ctx.bezierCurveTo(cP1X,cP1Y,cP2X,cp2Y,ePX,ePY);
}
// Final segment, from last springs round to the center of mass
ctx.lineTo(this.x2, this.y2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
};
function Box(w, h, mx, my) {
this.x = 0;
this.y = 0;
this.w = w;
this.h = h;
this.mx = mx;
this.my = my;
this.vx = 0;
this.vy = 0;
this.rotation = 0;
this.color = "red";
this.lineWidth = 1;
}
Box.prototype.draw = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = "black";
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.rect(-0.5*this.w, -0.5*this.h, this.w, this.h);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "yellow";
ctx.fillStyle = "yellow";
ctx.arc(this.mx, 0.5*this.h-this.my, 6, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.closePath();
ctx.fill();
ctx.restore();
};
window.requestAnimFrame = (
function(callback) {
return window.setTimeout(callback, 1000/30);
});
(function drawFrame() {
window.requestAnimFrame(drawFrame, cv);
ctx.clearRect(0, 0, cv.width, cv.height);
var dx = box.x - mouse.x,
dy = box.y - mouse.y,
angle = Math.atan2(dy, dx),
boxAngle = angle + 0.5*Math.PI,
targetX = mouse.x + Math.cos(angle) * spring.restLenght,
targetY = mouse.y + Math.sin(angle) * spring.restLenght;
vx += (targetX - box.x) * spring.k;
vy += (targetY - box.y) * spring.k;
vx *= spring.f;
vy *= spring.f;
box.rotation = boxAngle;
box.x += vx;
box.y += vy;
box.draw(ctx);
spring.x1 = mouse.x;
spring.y1 = mouse.y;
spring.x2 = box.x;
spring.y2 = box.y;
spring.draw(ctx);
}());
function capture(element) {
var mouse = {
x: 0,
y: 0,
event: null
},
body_scrollLeft = document.body.scrollLeft,
element_scrollLeft = document.documentElement.scrollLeft,
body_scrollTop = document.body.scrollTop,
element_scrollTop = document.documentElement.scrollTop,
offsetLeft = element.offsetLeft,
offsetTop = element.offsetTop;
element.addEventListener('mousemove', function(event) {
var x, y;
if (event.pageX || event.pageY) {
x = event.pageX;
y = event.pageY;
} else {
x = event.clientX + body_scrollLeft + element_scrollLeft;
y = event.clientY + body_scrollTop + element_scrollTop;
}
x -= offsetLeft;
y -= offsetTop;
mouse.x = x;
mouse.y = y;
mouse.event = event;
}, false);
return mouse;
}
<canvas id="cv" width="600" height="400"></canvas>
Drawing a spring
Rather than use bezier curves which do not actually fit the curve of a spring (but close) I just use a simple path and use trig functions to draw each winding. the function has a start x1,y1 and end x2, y2, windings (should be an integer), width of spring, the offset (bits at ends), Dark colour, and light colour, and the stroke width (width of the wire).
The demo draws an extra highlight to give the spring a little more depth. It can easily be removed.
The code came from this answer that has a simpler version of the same function
function drawSpring(x1, y1, x2, y2, windings, width, offset, col1, col2, lineWidth){
var x = x2 - x1;
var y = y2 - y1;
var dist = Math.sqrt(x * x + y * y);
var nx = x / dist;
var ny = y / dist;
ctx.strokeStyle = col1
ctx.lineWidth = lineWidth;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(x1,y1);
x1 += nx * offset;
y1 += ny * offset;
x2 -= nx * offset;
y2 -= ny * offset;
var x = x2 - x1;
var y = y2 - y1;
var step = 1 / (windings);
for(var i = 0; i <= 1-step; i += step){ // for each winding
for(var j = 0; j < 1; j += 0.05){
var xx = x1 + x * (i + j * step);
var yy = y1 + y * (i + j * step);
xx -= Math.sin(j * Math.PI * 2) * ny * width;
yy += Math.sin(j * Math.PI * 2) * nx * width;
ctx.lineTo(xx,yy);
}
}
ctx.lineTo(x2, y2);
ctx.lineTo(x2 + nx * offset, y2 + ny * offset)
ctx.stroke();
ctx.strokeStyle = col2
ctx.lineWidth = lineWidth - 4;
var step = 1 / (windings);
ctx.beginPath();
ctx.moveTo(x1 - nx * offset, y1 - ny * offset);
ctx.lineTo(x1, y1);
ctx.moveTo(x2, y2);
ctx.lineTo(x2 + nx * offset, y2 + ny * offset)
for(var i = 0; i <= 1-step; i += step){ // for each winding
for(var j = 0.25; j <= 0.76; j += 0.05){
var xx = x1 + x * (i + j * step);
var yy = y1 + y * (i + j * step);
xx -= Math.sin(j * Math.PI * 2) * ny * width;
yy += Math.sin(j * Math.PI * 2) * nx * width;
if(j === 0.25){
ctx.moveTo(xx,yy);
}else{
ctx.lineTo(xx,yy);
}
}
}
ctx.stroke();
}
function display() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
ctx.lineWidth = 8;
drawSpring(canvas.width / 2,10, mouse.x,mouse.y,8,100,40,"green","#0C0",15);
}
// Boiler plate code from here down and not part of the answer
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
if (m.callbacks) {
m.callbacks.forEach(c => c(e));
}
if ((m.buttonRaw & 2) && m.crashRecover !== null) {
if (typeof m.crashRecover === "function") {
setTimeout(m.crashRecover, 0);
}
}
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === undefined) {
m.callbacks = [callback];
} else {
m.callbacks.push(callback);
}
}
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
// Clean up. Used where the IDE is on the same page.
var done = function () {
window.removeEventListener("resize", resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = undefined;
}
function update(timer) { // Main update loop
if(ctx === undefined){
return;
}
globalTime = timer;
display(); // call demo code
if (!(mouse.buttonRaw & 2)) {
requestAnimationFrame(update);
} else {
done();
}
}
setTimeout(function(){
resizeCanvas();
mouse.start(canvas, true);
mouse.crashRecover = done;
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
/** SimpleFullCanvasMouse.js end **/
To make drawing easier, use .translate() and .rotate() to move into an aligned coordinate system.
ctx.translate(this.x1, this.y1);
ctx.rotate(Math.atan2(this.y2 - this.y1, this.x2 - this.x1));
You can then draw the spring along the local x-axis, and it will appear in the correct place and rotation.
Your spacing of the segments were wrong. hl*i is half the distance from the spring's starting point, not the segment's starting point.
var cv = document.getElementById('cv'),
ctx = cv.getContext('2d'),
mouse = capture(cv),
box = new Box(120, 80, 0, 16),
spring = new Spring(160, 50, 2, 0.03, 0.9),
vx = 0,
vy = 0;
function Spring(restLenght, width, numRounds, k, f) {
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.k = k;
this.f = f;
this.color = "green";
this.lineWidth = 6;
}
Spring.prototype.draw = function(ctx) {
var sPX, sPY, cP1X, cP1Y, cP2X, cP2Y, ePX, ePY;
ctx.save();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color;
ctx.fillStyle = this.color;
var vx = this.x2 - this.x1;
var vy = this.y2 - this.y1;
var vm = Math.sqrt(vx * vx + vy * vy);
ctx.translate(this.x1, this.y1);
ctx.rotate(Math.atan2(vy, vx));
ctx.beginPath();
ctx.moveTo(0, 0);
// length of one spring's round
var l = vm / (this.numRounds + 2);
// Initial segment, from spring anchor point to the first round
sPX = l;
sPY = 0;
ctx.lineTo(sPX, sPY);
// half width of spring's rounds
var hw = 0.5 * this.width;
for (var i = 0, n = this.numRounds; i < n; i++) {
cP1X = sPX + l * (i + 0.0);
cP1Y = sPY + hw;
cP2X = sPX + l * (i + 0.5);
cp2Y = sPY + hw;
ePX = sPX + l * (i + 0.5);
ePY = sPY;
ctx.bezierCurveTo(cP1X, cP1Y, cP2X, cp2Y, ePX, ePY);
cP1X = sPX + l * (i + 0.5);
cP1Y = sPY - hw;
cP2X = sPX + l * (i + 1.0);
cp2Y = sPY - hw;
ePX = sPX + l * (i + 1.0);
ePY = sPY;
ctx.bezierCurveTo(cP1X, cP1Y, cP2X, cp2Y, ePX, ePY);
}
// Final segment, from last springs round to the center of mass
ctx.lineTo(vm, 0);
//ctx.closePath();
//ctx.fill();
ctx.stroke();
ctx.restore();
};
function Box(w, h, mx, my) {
this.x = 0;
this.y = 0;
this.w = w;
this.h = h;
this.mx = mx;
this.my = my;
this.vx = 0;
this.vy = 0;
this.rotation = 0;
this.color = "red";
this.lineWidth = 1;
}
Box.prototype.draw = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = "black";
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.rect(-0.5 * this.w, -0.5 * this.h, this.w, this.h);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "yellow";
ctx.fillStyle = "yellow";
ctx.arc(this.mx, 0.5 * this.h - this.my, 6, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.closePath();
ctx.fill();
ctx.restore();
};
window.requestAnimFrame = (
function(callback) {
return window.setTimeout(callback, 1000 / 30);
});
(function drawFrame() {
window.requestAnimFrame(drawFrame, cv);
ctx.clearRect(0, 0, cv.width, cv.height);
var dx = box.x - mouse.x,
dy = box.y - mouse.y,
angle = Math.atan2(dy, dx),
boxAngle = angle + 0.5 * Math.PI,
targetX = mouse.x + Math.cos(angle) * spring.restLenght,
targetY = mouse.y + Math.sin(angle) * spring.restLenght;
vx += (targetX - box.x) * spring.k;
vy += (targetY - box.y) * spring.k;
vx *= spring.f;
vy *= spring.f;
box.rotation = boxAngle;
box.x += vx;
box.y += vy;
box.draw(ctx);
spring.x1 = mouse.x;
spring.y1 = mouse.y;
spring.x2 = box.x;
spring.y2 = box.y;
spring.draw(ctx);
}());
function capture(element) {
var mouse = {
x: 0,
y: 0,
event: null
},
body_scrollLeft = document.body.scrollLeft,
element_scrollLeft = document.documentElement.scrollLeft,
body_scrollTop = document.body.scrollTop,
element_scrollTop = document.documentElement.scrollTop,
offsetLeft = element.offsetLeft,
offsetTop = element.offsetTop;
element.addEventListener('mousemove', function(event) {
var x, y;
if (event.pageX || event.pageY) {
x = event.pageX;
y = event.pageY;
} else {
x = event.clientX + body_scrollLeft + element_scrollLeft;
y = event.clientY + body_scrollTop + element_scrollTop;
}
x -= offsetLeft;
y -= offsetTop;
mouse.x = x;
mouse.y = y;
mouse.event = event;
}, false);
return mouse;
}
<canvas id="cv" width="600" height="400"></canvas>

how can i make two objects belonging to the same array move independently of each other using javascript and the canvas tag?

I am trying to create a blackhole simulation, where all the balls that are outside of it go away from it at a given speed and those that fall on it are dragged towards the circle until they reach the center of it, where they would stop and disappear, here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>blackhole simulation escape velocity</title>
<script>
var canvas, ctx;
var blackhole;
var circle;
var circles = new Array();
var G = 6.67e-11, //gravitational constant
pixel_G = G / 1e-11,
c = 3e8, //speed of light (m/s)
M = 12e31, // masseof the blackhole in kg (60 solar masses)
pixel_M = M / 1e32
Rs = (2 * G * M) / 9e16, //Schwarzchild radius
pixel_Rs = Rs / 1e3, // scaled radius
ccolor = 128;
function update() {
var pos, i, distance, somethingMoved = false;
for (i = 0; i < circles.length; i++) {
pos = circles[i].position;
distance = Math.sqrt(((pos.x - 700) * (pos.x - 700)) + ((pos.y - 400) * (pos.y - 400)));
if (distance > pixel_Rs-5 ) {
var delta = new Vector2D(0, 0);
var forceDirection = Math.atan2(pos.y - 400, pos.x - 700);
var evelocity = Math.sqrt((2 * pixel_G * pixel_M) / (distance * 1e-2));
delta.x += Math.cos(forceDirection) * evelocity;
delta.y += Math.sin(forceDirection) * evelocity;
pos.x += delta.x;
pos.y += delta.y;
somethingMoved = true;
} else {
var delta2 = new Vector2D (0,0);
var forceDirection2 = Math.atan2(pos.y - 400, pos.x - 700);
var g = (pixel_G*pixel_M)/(distance*distance*1e2);
delta2.x += Math.cos(forceDirection2)*g;
delta2.y += Math.sin(forceDirection2)*g;
pos.x -= delta2.x;
pos.y -= delta2.y;
somethingMoved = true;
circles[i].color -= 1;
if (pos.x == 700 && pos.y == 400){
somethingMoved = false;
};
}
}
if (somethingMoved) {
drawEverything();
requestAnimationFrame(update);
};
}
function drawEverything() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
blackhole.draw(ctx);
for (var i = 0; i < circles.length; i++) {
circles[i].draw(ctx);
}
}
function init(event) {
canvas = document.getElementById("space");
ctx = canvas.getContext('2d');
blackhole = new Ball(pixel_Rs, { x: 700,
y: 400 }, 0);
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, ccolor);
circles.push(circle);
}
drawEverything();
requestAnimationFrame(update);
}
function Ball(radius, position, color) {
this.radius = radius;
this.position = position;
this.color = color;
}
Ball.prototype.draw = function(ctx) {
var c=parseInt(this.color);
ctx.fillStyle = 'rgba(' + c + ',' + c + ',' + c + ',1)';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
};
function Vector2D(x, y) {
this.x = x;
this.y = y;
}
function onClick (){
canvas = document.getElementById ('space');
ctx = canvas.getContext ('2d')
canvas.addEventListener ("mousedown", init, false)
blackhole = new Ball (5, {x: 700,
y: 400 }, 0);
blackhole.draw (ctx) ;
}
window.onload = onClick;
</script>
<style>
body {
background-color:#021c36 ;
margin: 0px;
}
</style>
</head>
<body>
<canvas id = "space", width = "1400", height = "800">
</canvas>
</body>
</html>
Now as you can see, I created a second variable called delta2, but the problem is that it can't update the position of the circles, which in term makes it impossible to move the circle, can someone tell me what is wrong. Also, how can I make the big black circle after a certain amount of time, i know i probably should create a timer, but i don't know how they work
The gravity is too weak. I put a pseudo gravity to demonstrate.
var canvas, ctx;
var blackhole;
var circle;
var circles = new Array();
var bh = {
w:500,
h:300
};
bh.cx = Math.floor(bh.w/2);
bh.cy = Math.floor(bh.h/2)
var G = 6.67e-11, //gravitational constant
pixel_G = G / 1e-11,
c = 3e8, //speed of light (m/s)
M = 12e31, // masseof the blackhole in kg (60 solar masses)
pixel_M = M / 1e32
Rs = (2 * G * M) / 9e16, //Schwarzchild radius
pixel_Rs = Rs / 1e3, // scaled radius
ccolor = 128;
function update() {
var pos, i, distance, somethingMoved = false;
for (i = 0; i < circles.length; i++) {
pos = circles[i].position;
distance = Math.sqrt(((pos.x - bh.cx) * (pos.x - bh.cx)) + ((pos.y - bh.cy) * (pos.y - bh.cy)));
if (distance > pixel_Rs - 5) {
var delta = new Vector2D(0, 0);
var forceDirection = Math.atan2(pos.y - bh.cy, pos.x - bh.cx);
var evelocity = Math.sqrt((2 * pixel_G * pixel_M) / (distance * 1e-2));
delta.x += Math.cos(forceDirection) * evelocity;
delta.y += Math.sin(forceDirection) * evelocity;
pos.x += delta.x;
pos.y += delta.y;
somethingMoved = true;
} else {
var delta2 = new Vector2D(0, 0);
var forceDirection2 = Math.atan2(pos.y - bh.cy, pos.x - bh.cx);
// FIX THIS!!!
var g = 1;//(pixel_G * pixel_M) / (distance * distance * 1e2);
delta2.x += Math.cos(forceDirection2) * g;
delta2.y += Math.sin(forceDirection2) * g;
pos.x -= delta2.x;
pos.y -= delta2.y;
somethingMoved = true;
circles[i].color -= 1;
if (pos.x == bh.cx && pos.y == bh.cy) {
somethingMoved = false;
};
}
}
if (somethingMoved) {
drawEverything();
requestAnimationFrame(update);
};
}
function drawEverything() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
blackhole.draw(ctx);
for (var i = 0; i < circles.length; i++) {
circles[i].draw(ctx);
}
}
function init(event) {
canvas = document.getElementById("space");
canvas.width = bh.w;
canvas.height = bh.h;
ctx = canvas.getContext('2d');
blackhole = new Ball(5, { //pixel_Rs, {
x: bh.cx,
y: bh.cy
}, 0);
for (var i = 0; i < 200; i++) {
var vec2D = new Vector2D(Math.floor(Math.random() * bh.w), Math.floor(Math.random() * bh.h));
circle = new Ball(5, vec2D, ccolor);
circles.push(circle);
}
drawEverything();
requestAnimationFrame(update);
}
function Ball(radius, position, color) {
this.radius = radius;
this.position = position;
this.color = color;
}
Ball.prototype.draw = function(ctx) {
var c = parseInt(this.color);
ctx.fillStyle = 'rgba(' + c + ',' + c + ',' + c + ',1)';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
};
function Vector2D(x, y) {
this.x = x;
this.y = y;
}
function onClick() {
canvas = document.getElementById('space');
ctx = canvas.getContext('2d')
canvas.addEventListener("mousedown", init, false)
blackhole = new Ball(5, {
x: bh.cx,
y: bh.cy
}, 0);
blackhole.draw(ctx);
}
window.onload = onClick;
body {
background-color: #021c36;
margin: 0px;
}
<canvas id="space" , width="700" , height="400"></canvas>

Categories