I have been trying to write a simulator using the HTML5 Canvas. I have the following code to generate equipotential lines for electric fields.
function drawEqLines(charges) {
// don't bother if there aren't any charges
if (charges.length == 0) return;
// the Charge class contains the charge of the charge as well as its x and y coordinates
var fieldFilled = [];
for (var i = 0; i < 10; i++) {
fieldFilled.push([]);
for (var j = 0; j < 10; j++) {
fieldFilled[i].push(false);
}
}
var calculatedFields = [];
var maxForce = 0;
for (var i = 0; i < fieldFilled.length; i++) {
var direction = 1;
for (var jj=0; jj < fieldFilled[i].length; jj++) {
if (!fieldFilled[i][jj]) {
//create a path here
//Iterate at most 2 times in case the surface gets out of the area
for (var circleTimes = 0; circleTimes < 3; circleTimes+=2) {
//Define the center of the current block as a starting point of the surface
/*
horizontalBlock and verticalBlock are the width and height of the canvas divided into 10 different sections.
_BlockHalf is half of one of the blocks
*/
var curX = i * this.horizontalBlock + this.horizontalBlockHalf;
var curY = jj * this.verticalBlock + this.verticalBlockHalf;
// Point is a class that contains an x and y value
var curPt = new Point(curX, curY);
var direction = 1 - circleTimes;
var dots = [];
dots.push(curPt);
//Superposition the fields from all charges, and get the resulting force vector
var dirX = 0;
var dirY = 0;
var totalForce = 0;
for (var j = 0; j < charges.length; j++) {
var distX = curPt.x - charges[j].x;
var distY = curPt.y - charges[j].y;
var distanceSq = distX * distX + distY * distY;
var force = charges[j].charge / distanceSq;
var distanceFactor = force / Math.sqrt(distanceSq);
//Measure the initial force in order to match the equipotential surface points
totalForce += force;
dirX += distX * distanceFactor;
dirY += distY * distanceFactor;
}
//Maximum 2000 dots per surface line
var times = 2000;
while (times-- > 0) {
var dirTotal = Math.sqrt(dirX * dirX + dirY * dirY);
var stepX = dirX / dirTotal;
var stepY = dirY / dirTotal;
//The equipotential surface moves normal to the force vector
curPt.x = curPt.x + direction * 6 * stepY;
curPt.y = curPt.y - direction * 6 * stepX;
/*
*
* ***********************LOG LINE HERE***********************************
*
*/
// this prints out unique values
console.log(curPt.x + ", " + curPt.y);
//Correct the exact point a bit to match the initial force as near it can
var minForceIndex = -1;
var minForceDiff = 0;
var minDirX = 0;
var minDirY = 0;
var minCurX = 0;
var minCurY = 0;
curPt.x -= 3 * stepX;
curPt.y -= 3 * stepY;
for (var pointIndex = 0; pointIndex < 7; pointIndex++, curPt.x += stepX, curPt.y += stepY) {
dirX = 0;
dirY = 0;
var forceSum = 0;
for (var j = 0; j < charges.length; j++) {
var distX = curPt.x - charges[j].x;
var distY = curPt.y - charges[j].y;
var distanceSq = distX ** 2 + distY ** 2;
var force = charges[j].charge / distanceSq;
var distanceFactor = force / Math.sqrt(distanceSq);
//Measure the initial force in order to match the equipotential surface points
forceSum += force;
dirX += distX * distanceFactor;
dirY += distY * distanceFactor;
}
var forceDiff = Math.abs(forceSum - totalForce);
if (minForceIndex == -1 || forceDiff < minForceDiff) {
minForceIndex = pointIndex;
minForceDiff = forceDiff;
minDirX = dirX;
minDirY = dirY;
minCurX = curPt.x;
minCurY = curPt.y;
} else {
break;
}
}
//Set the corrected equipotential point
curPt.x = minCurX;
curPt.y = minCurY;
dirX = minDirX;
dirY = minDirY;
//Mark the containing block as filled with a surface line.
var indI = parseInt(curPt.x / this.horizontalBlock);
var indJ = parseInt(curPt.y / this.verticalBlock);
if (indI >= 0 && indI < fieldFilled.length) {
if (indJ >= 0 && indJ < fieldFilled[indI].length) {
fieldFilled[indI][indJ] = true;
}
}
//Add the dot to the line (was commented out when I added the other log)
dots.push(curPt);
if (dots.length > 5) {
//If got to the begining, a full circle has been made, terminate further iterations
if (indI == i && indJ == jj) {
distX = dots[0].x - curPt.x;
distY = dots[0].y - curPt.y;
if (distX * distX + distY * distY <= 49) {
dots.push(new Point(dots[0].x, dots[0].y));
times = 0;
circleTimes = 3;
}
}
//If got out of the area, terminate further iterations for this turn.
if (curPt.x < 0 || curPt.x > this.canvas.width || curPt.y < 0 || curPt.y > this.canvas.height) {
times = 0;
}
}
}
calculatedFields.push([totalForce, dots]);
maxForce = Math.max(maxForce, Math.abs(totalForce));
}
}
}
}
console.log(calculatedFields);
// draw the lines from the arrays of dots here...
}
The result of this code is an array that looks like this:
[
[
0.000007927962725256215,
[
// as you can see, these points are all the same
{x: 114.16365557137308, y: 402.6147544331975},
{x: 114.16365557137308, y: 402.6147544331975},
{x: 114.16365557137308, y: 402.6147544331975},
{x: 114.16365557137308, y: 402.6147544331975},
// etc...
]
],
[
0.000006140401131528559,
[
{x: -1.0201768243546043, y: 323.28955989370445},
{x: -1.0201768243546043, y: 323.28955989370445},
{x: -1.0201768243546043, y: 323.28955989370445},
{x: -1.0201768243546043, y: 323.28955989370445},
// etc...
]
]
]
These points should be unique, however, it seems to just generate the same point over and over again. For some reason, it does decide to stop after a reasonable number of iterations, as if it is doing the calculations correctly but just putting the same point into the array anyway.
Why is this happening? How can I fix it?
Update: Here are the classes that I am using:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Charge {
constructor(charge, x, y) {
this.charge = charge;
this.x = x;
this.y = y;
}
}
Also, the charges array is fine, because I use it in a similar method which does work correctly. An example for this would be:
[
{charge: 6, x: 50, y: 50},
{charge: -4, x: 70, y: 90.5},
// etc...
]
Update 2:
I have tried adding the log line indicated in the above code, which prints out only unique values. However, even if I try pushing the point to the array here it still results in the same issue.
Update 3:
Added this runnable snippet:
const height = 400; // for example
const width = 600; // also for example
const horizontalBlock = width / 10;
const verticalBlock = height / 10;
const horizontalBlockHalf = horizontalBlock / 2;
const verticalBlockHalf = verticalBlock / 2;
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
function drawEqLines(charges) {
// don't bother if there aren't any charges
if (charges.length == 0) return;
// the Charge class contains the charge of the charge as well as its x and y coordinates
var fieldFilled = [];
for (var i = 0; i < 10; i++) {
fieldFilled.push([]);
for (var j = 0; j < 10; j++) {
fieldFilled[i].push(false);
}
}
var calculatedFields = [];
var maxForce = 0;
for (var i = 0; i < fieldFilled.length; i++) {
var direction = 1;
for (var jj=0; jj < fieldFilled[i].length; jj++) {
if (!fieldFilled[i][jj]) {
//create a path here
//Iterate at most 2 times in case the surface gets out of the area
for (var circleTimes = 0; circleTimes < 3; circleTimes+=2) {
//Define the center of the current block as a starting point of the surface
/*
horizontalBlock and verticalBlock are the width and height of the canvas divided into 10 different sections.
_BlockHalf is half of one of the blocks
*/
var curX = i * horizontalBlock + horizontalBlockHalf;
var curY = jj * verticalBlock + verticalBlockHalf;
// Point is a class that contains an x and y value
var curPt = new Point(curX, curY);
var direction = 1 - circleTimes;
var dots = [];
dots.push(curPt);
//Superposition the fields from all charges, and get the resulting force vector
var dirX = 0;
var dirY = 0;
var totalForce = 0;
for (var j = 0; j < charges.length; j++) {
var distX = curPt.x - charges[j].x;
var distY = curPt.y - charges[j].y;
var distanceSq = distX * distX + distY * distY;
var force = charges[j].charge / distanceSq;
var distanceFactor = force / Math.sqrt(distanceSq);
//Measure the initial force in order to match the equipotential surface points
totalForce += force;
dirX += distX * distanceFactor;
dirY += distY * distanceFactor;
}
//Maximum 2000 dots per surface line
var times = 2000;
while (times-- > 0) {
var dirTotal = Math.sqrt(dirX * dirX + dirY * dirY);
var stepX = dirX / dirTotal;
var stepY = dirY / dirTotal;
//The equipotential surface moves normal to the force vector
curPt.x = curPt.x + direction * 6 * stepY;
curPt.y = curPt.y - direction * 6 * stepX;
/*
*
* ***********************LOG LINE HERE***********************************
*
*/
// this prints out unique values
console.log(curPt.x + ", " + curPt.y);
//Correct the exact point a bit to match the initial force as near it can
var minForceIndex = -1;
var minForceDiff = 0;
var minDirX = 0;
var minDirY = 0;
var minCurX = 0;
var minCurY = 0;
curPt.x -= 3 * stepX;
curPt.y -= 3 * stepY;
for (var pointIndex = 0; pointIndex < 7; pointIndex++, curPt.x += stepX, curPt.y += stepY) {
dirX = 0;
dirY = 0;
var forceSum = 0;
for (var j = 0; j < charges.length; j++) {
var distX = curPt.x - charges[j].x;
var distY = curPt.y - charges[j].y;
var distanceSq = distX ** 2 + distY ** 2;
var force = charges[j].charge / distanceSq;
var distanceFactor = force / Math.sqrt(distanceSq);
//Measure the initial force in order to match the equipotential surface points
forceSum += force;
dirX += distX * distanceFactor;
dirY += distY * distanceFactor;
}
var forceDiff = Math.abs(forceSum - totalForce);
if (minForceIndex == -1 || forceDiff < minForceDiff) {
minForceIndex = pointIndex;
minForceDiff = forceDiff;
minDirX = dirX;
minDirY = dirY;
minCurX = curPt.x;
minCurY = curPt.y;
} else {
break;
}
}
//Set the corrected equipotential point
curPt.x = minCurX;
curPt.y = minCurY;
dirX = minDirX;
dirY = minDirY;
//Mark the containing block as filled with a surface line.
var indI = parseInt(curPt.x / this.horizontalBlock);
var indJ = parseInt(curPt.y / this.verticalBlock);
if (indI >= 0 && indI < fieldFilled.length) {
if (indJ >= 0 && indJ < fieldFilled[indI].length) {
fieldFilled[indI][indJ] = true;
}
}
//Add the dot to the line (was commented out when I added the other log)
dots.push(curPt);
if (dots.length > 5) {
//If got to the begining, a full circle has been made, terminate further iterations
if (indI == i && indJ == jj) {
distX = dots[0].x - curPt.x;
distY = dots[0].y - curPt.y;
if (distX * distX + distY * distY <= 49) {
dots.push(new Point(dots[0].x, dots[0].y));
times = 0;
circleTimes = 3;
}
}
//If got out of the area, terminate further iterations for this turn.
if (curPt.x < 0 || curPt.x > width || curPt.y < 0 || curPt.y > height) {
times = 0;
}
}
}
calculatedFields.push([totalForce, dots]);
maxForce = Math.max(maxForce, Math.abs(totalForce));
}
}
}
}
console.log(calculatedFields);
// draw the lines from the arrays of dots here...
}
var charges = [
{charge: 6, x: 50, y: 75},
{charge: -5, x: 60, y: 254.5}
]
drawEqLines(charges);
You have a loop with code that says: dots.push(curPt)
In that loop, you reassign the x and y properties of the curPt object, but it's the same object that you keep pushing on each iteration.
You will have to create a new Point object inside the loop if you want to push different points into the array.
Related
Im making a small game where I move a ball onDeviceOrientation into holes. I generate holes(circles) in canvas and push them into an array called holes. When I'm trying to check for collision, the declared value let currentHole = this.holes[i]; shows this error. Can you tell me why the property of 0 is undefined? I couldn't understand checking other people's problems.
this is my JS code:
const canvas = document.querySelector('.canvas');
const hole = canvas.getContext('2d');
let scoreCount = 0;
let holes = [];
var hx = Math.random() * canvas.width;
var hy = Math.random() * canvas.height;
var radius = 25;
var sAngle = 0;
var eAngle = 2 * Math.PI;
let speedX = 0;
let speedY = 0;
let x = 200;
let y = 200;
window.addEventListener('deviceorientation', onDeviceOrientationChange);
const restart = document.querySelector('.restart');
document.querySelector('.start').addEventListener('click', onStartClick);
function onStartClick() {
const btn = document.querySelector('.start');
btn.classList.add('remove');
restart.classList.remove('remove');
const score = document.createElement('span');
score.classList.add('score');
score.innerHTML = 'SCORE: ' + scoreCount;
document.body.appendChild(score);
let ball = document.createElement('div');
ball.classList.add('ball');
document.body.appendChild(ball);
makeHoles();
onDeviceOrientationChange(event);
checkForCollision();
}
function onDeviceOrientationChange(event) {
let ball = document.querySelector('.ball');
speedX = event.alpha / 60; //alpha nie
speedY = event.beta / 60;
if ((innerWidth > speedX + x > 0)) {
x += speedX;
ball.style.left = x + 'px';
}
if (window.innerHeight > speedY + y > 0) {
y += speedY;
ball.style.top = y + 'px';
}
}
function makeHoles() {
for (let i = 1; i < canvas.width / 100; i++) {
hole.beginPath();
hole.arc(
hx,
hy,
radius,
sAngle,
eAngle,
);
hole.fillStyle = 'rgb(84, 93, 139)';
hole.fill();
hole.stroke();
hole.closePath();
holes.push(hole);
}
console.log(holes);
}
function checkForCollision() {
for (let i = 0; i < holes.length; i++) {
let currentHole = this.holes[i];
let ball = document.querySelector('.ball');
currentHole = {
radius: radius,
x: hx,
y: hy
};
ball = {
radius: 15,
x: x,
y: y
};
var dx = currentHole.x - ball.x;
var dy = currentHole.y - ball.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < currentHole.radius + ball.radius) {
scoreCount++;
}
}
}
You should use 'holes[i]', not 'this.holes[i]'.
I've been working on a simulation of a magnetic pendulum (Magnetic Pendulum for reference). Now, I have a question concerning how you calculate the forces/acceleration:
In examples you find online of the magnetic pendulum (such as the one provided above), and other physics force and acceleration calculations, the forces are calculated at a starting position, the position updated according to time-step dt and then new forces at time t+dt calculated to get a new position and so on. Basic numerical integration, makes sense.
However, I noticed, that if I calculate the forces this way, things don't go as one would expect. For example: when the pendulum is attracted by a magnet it just stops at the particluar magnet even tough you would expect it to "overswing" a bit. Therefore, I introduced three variables for each force which is all previous forces added together, and now it somehow seems to work correctly.
I'm wondering if
the way of calculating the forces I used makes sense in a physics simulation and
if not, then what am I doing wrong? Because it seems to work for other people.
Here's my code:
p5.disableFriendlyErrors = true;
let magnete = [];
let particles = [];
var maganzahl = 3; //number of magnets
var magradius = 100; //radius of magnets from mid-point
var G = 0.002; //coefficient of force towards middle (gravity)
var R = 0.2; //friction coefficient
var h = 50; //height of bob over magnets
var M = 150000; //strength of magnets
var m = 1; //mass (might not work)
var dt = 0.25; //timestep
var d = 2; //pixel density
var counter2 = 0;
var Reset;
var end = false;
//--------------------------------------------
function setup() {
createCanvas(600, 600);
pixelDensity(d);
background(60);
Reset = createButton('Reset');
Reset.position(width + 19, 49);
Reset.mousePressed(resetcanvas);
//construction of magnets
for (var i = 0; i < maganzahl; i++) {
magnete.push(new Magnet((width / 2) + magradius * cos(TWO_PI * (i / maganzahl)), (height / 2) + magradius * sin(TWO_PI * (i / maganzahl)), i));
}
}
//construction of a new "starting position" by mouse-click
function mousePressed() {
if (mouseX < width && mouseY < height) {
particles.push(new Particle(mouseX, mouseY));
}
}
function draw() {
for (var i = 0; i < particles.length; i++) {
if (particles[i].counter < 1000) {
//5 updates per frame(to speed it up)
for (var k = 0; k < 5; k++) {
for (var j = 0; j < magnete.length; j++) {
particles[i].attracted(magnete[j]);
}
particles[i].update();
particles[i].show2();
}
} else if (particles[i].counter < 1001) {
particles[i].counter += 1;
var nearest = [];
for (var j = 0; j < magnete.length; j++) {
nearest.push(particles[i].near(magnete[j]));
}
if (nearest.indexOf(min(nearest)) == 0) {
var c = color("green");
}
if (nearest.indexOf(min(nearest)) == 1) {
var c = color("purple");
}
if (nearest.indexOf(min(nearest)) == 2) {
var c = color("orange");
}
if (nearest.indexOf(min(nearest)) == 3) {
var c = color("blue");
}
if (nearest.indexOf(min(nearest)) == 4) {
var c = color("red");
}
if (nearest.indexOf(min(nearest)) == 5) {
var c = color("yellow");
}
//show particle trace according to nearest magnet
particles[i].show(c);
}
}
//displaying magnets
for (var i = 0; i < magnete.length; i++) {
magnete[i].show();
}
//displaying mid-point
stroke(255);
circle(width / 2, height / 2, 3);
}
function resetcanvas() {
background(60);
}
function Particle(x, y) {
this.orgpos = createVector(x, y);
this.pos = createVector(x, y);
this.prev = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.accpre = createVector();
this.accprepre = createVector();
this.velprediction = this.vel;
this.magnetf = createVector();
this.gravity = createVector();
this.friction = createVector();
this.shape = new Array();
this.counter = 0;
//calculating new positions
this.update = function() {
//predictor for velocity -> Beeman's algorithm
this.velprediction.add(this.accpre.mult(3 / 2 * dt).add(this.accprepre.mult(-1 / 2 * dt)));
var momgrav = createVector(width / 2 - this.pos.x, height / 2 - this.pos.y);
var momfric = createVector(this.velprediction.x, this.velprediction.y);
momgrav.mult(G * m); //force due to gravity
momfric.mult(-R); //force due to friction
this.gravity.add(momgrav);
this.friction.add(momfric);
//a = F/m
this.acc = createVector((this.magnetf.x + this.gravity.x + this.friction.x) / m, (this.magnetf.y + this.gravity.y + this.friction.y) / m);
//-=Beeman's Algorithm=-
this.vel.add(this.acc.mult(dt * 1 / 3).add(this.accpre.mult(dt * 5 / 6)).add(this.accprepre.mult(-1 / 6 * dt)));
this.pos.add(this.vel.mult(dt).add(this.accpre.mult(dt * dt * 2 / 3)).add(this.accprepre.mult(-1 / 6 * dt * dt)));
this.accprepre = createVector(this.accpre.x, this.accpre.y);
this.accpre = createVector(this.acc.x, this.acc.y);
this.velprediction = createVector(this.vel.x, this.vel.y);
this.counter += 1;
this.shape.push(new p5.Vector(this.pos.x, this.pos.y));
}
//calculating force due to magnets -> attracted called earlier than update in sketch.js
this.attracted = function(target) {
var magn = createVector(target.pos.x - this.pos.x, target.pos.y - this.pos.y);
var dist = sqrt(sq(h) + sq(magn.x) + sq(magn.y)); //distance bob - magnet
strength = M / (Math.pow(dist, 3));
magn.mult(strength);
this.magnetf.add(magn);
}
//calculating distance to target
this.near = function(target) {
var dist = sqrt(sq(h) + sq(this.pos.x - target.pos.x) + sq(this.pos.y - target.pos.y));
return (dist);
}
//trace
this.show = function(col) {
beginShape();
stroke(col);
for (var i = 0; i < this.shape.length - 1; i++) {
line(this.shape[i].x, this.shape[i].y, this.shape[i + 1].x, this.shape[i + 1].y);
strokeWeight(2);
noFill();
}
endShape();
}
//dots
this.show2 = function() {
strokeWeight(1)
point(this.pos.x, this.pos.y);
}
}
function Magnet(x, y, n) {
this.pos = createVector(x, y);
this.n = n;
this.show = function() {
noStroke();
//color for each magnet
if (n == 0) {
fill("green");
}
if (n == 1) {
fill("purple");
}
if (n == 2) {
fill("orange");
}
if (n == 3) {
fill("blue");
}
if (n == 4) {
fill("red");
}
if (n == 5) {
fill("yellow");
}
strokeWeight(4);
circle(this.pos.x, this.pos.y, 10);
}
}
Any help would be greatly appreciated!
I found the issue! So apparently in p5.js you have to be careful with your vector calculations. If you for example do something like:
[some variable] = [Vector].add([anotherVector].mult(2));
both [Vector] and [anotherVector] change their value. Makes sense now that I think about it...
The fact that it still seemed to work somewhat "realistically" and that I managed to generate some beautiful pictures using it (Such as this one 1 and this one 2) is still quite mysterious to me but I guess sometimes numbers just do their magic ;)
Run it:
Code Preview
If you want to change some variables/edit:
p5.js Web Editor
I have a little bit of javascript,
which is calling from the processing/p5.js library a number of tools.
// Spring drawing constants for top bar
let springHeight = 19,
left,
right,
maxHeight = 300,
minHeight = 0,
over = false,
move = false;
// Spring simulation constants
let M = 0.8, // Mass
K = 0.2, // Spring constant
D = 0.92, // Damping
R = 180; // Rest position
// Spring simulation variables
let ps = R, // Position
vs = 0.0, // Velocity
as = 0, // Acceleration
f = 0; // Force
function setup() {
createCanvas(710, 400);
rectMode(CORNERS);
// rectMode(CORNER);
c = color('rgb(255,233,234)');
fill(c);
noStroke();
left = width / 2 - 400;
right = width / 2 + 400;
}
function draw() {
background(102);
updateSpring();
drawWire();
}
function drawWire() {
rect(left, ps + 10, right, ps + springHeight);
// rect(left, ps + 10, right, ps + springHeight);
}
function updateSpring() {
// Update the spring position
if ( !move ) {
f = -K * ( ps - R ); // f=-ky
as = f / M; // Set the acceleration, f=ma == a=f/m
vs = D * (vs + as); // Set the velocity
ps = ps + vs; // Updated position
}
if (abs(vs) < 0.1) {
vs = 0.0;
}
// Test if mouse if over the top bar
if (mouseX > left && mouseX < right && mouseY > ps && mouseY < ps + springHeight) {
over = true;
} else {
over = false;
}
// Set and constrain the position of top bar
if (move) {
ps = mouseY - springHeight / 2;
ps = constrain(ps, minHeight, maxHeight);
}
}
function mousePressed() {
if (over) {
move = true;
}
}
function mouseReleased() {
move = false;
}
the javascript file, when run in my html, will create a single, pleasantly springy string:
what would be the best way to change this code so that multiple strings are produced,
perhaps spaced vertically by n pixels?
I have tried simply replicating the code block, but can not seem to be getting this right!
If you just want to draw multiple strings then use a for loop. Compute the y coordinate of the top of the string dependent on the control variable (i) of the loop. e.g. y = ps + springDist * i;:
e.g. 3 strings:
let springDist = 40.0;
function drawWire() {
for (let i = 0; i < 3; ++i) {
let y = ps + springDist * i;
rect(left, y, right, y+springHeight);
}
}
Of course you have to check if the mouse is over any of the strings and to state (move_i) which string is "touched":
let move_i = 0;
function updateSpring() {
// [...]
// Test if mouse if over the top bar
over = false;
for (let i = 0; i < 3; ++i) {
let y = ps + springDist * i;
if (mouseX > left && mouseX < right && mouseY > y && mouseY < y + springHeight) {
over = true;
move_i = i;
}
}
// Set and constrain the position of top bar
if (move) {
ps = mouseY - springHeight / 2 - move_i * springDist;
ps = constrain(ps, minHeight, maxHeight);
}
}
If you want to move each string individually, then you have to create a list of objects:
let strings = [];
function setup() {
// [...]
for (let i = 0; i < 3; ++i) {
let ps = R + springDist * i;
strings.push({ps : ps, vs : 0.0, as : 0, f : 0, R : ps})
}
}
Draw the objects:
function drawWire() {
for (let i = 0; i < strings.length; ++i) {
let y = strings[i].ps;
rect(left, y, right, y+springHeight);
}
}
Update the objects in a loop and move the individual object which is dragged (move_i):
function updateSpring() {
// Update the spring position
for (let i = 0; i < strings.length; ++i) {
let st = strings[i];
if ( i != move_i || !move ) {
st.f = -K * ( st.ps - st.R ); // f=-ky
st.as = st.f / M; // Set the acceleration, f=ma == a=f/m
st.vs = D * (st.vs + st.as); // Set the velocity
st.ps = st.ps + st.vs; // Updated position
}
if (abs(st.vs) < 0.1) {
st.vs = 0.0;
}
}
// Test if mouse if over the top bar
over = false;
for (let i = 0; i < strings.length; ++i) {
let y = strings[i].ps
if (mouseX > left && mouseX < right && mouseY > y && mouseY < y + springHeight) {
over = true;
move_i = i;
}
}
// Set and constrain the position of top bar
if (move) {
strings[move_i].ps = mouseY - springHeight / 2;
strings[move_i].ps = constrain(strings[move_i].ps, minHeight, maxHeight);
}
}
See the example:
// Spring drawing constants for top bar
let springHeight = 19,
springDist = 40,
left,
right,
maxHeight = 300,
minHeight = 0,
over = false,
move = false;
move_i = 0;
// Spring simulation constants
let M = 0.8, // Mass
K = 0.2, // Spring constant
D = 0.92, // Damping
R = 180; // Rest position
// Spring simulation variables
let strings = [];
function setup() {
createCanvas(710, 400);
rectMode(CORNERS);
// rectMode(CORNER);
c = color('rgb(255,233,234)');
fill(c);
noStroke();
left = width / 2 - 400;
right = width / 2 + 400;
for (let i = 0; i < 3; ++i) {
let ps = R + springDist * i;
strings.push({ps : ps, vs : 0.0, as : 0, f : 0, R : ps})
}
}
function draw() {
background(102);
updateSpring();
drawWire();
}
function drawWire() {
for (let i = 0; i < strings.length; ++i) {
let y = strings[i].ps;
rect(left, y, right, y+springHeight);
}
}
function updateSpring() {
// Update the spring position
for (let i = 0; i < strings.length; ++i) {
let st = strings[i];
if ( i != move_i || !move ) {
st.f = -K * ( st.ps - st.R ); // f=-ky
st.as = st.f / M; // Set the acceleration, f=ma == a=f/m
st.vs = D * (st.vs + st.as); // Set the velocity
st.ps = st.ps + st.vs; // Updated position
}
if (abs(st.vs) < 0.1) {
st.vs = 0.0;
}
}
// Test if mouse if over the top bar
over = false;
for (let i = 0; i < strings.length; ++i) {
let y = strings[i].ps
if (mouseX > left && mouseX < right && mouseY > y && mouseY < y + springHeight) {
over = true;
move_i = i;
}
}
// Set and constrain the position of top bar
if (move) {
strings[move_i].ps = mouseY - springHeight / 2;
strings[move_i].ps = constrain(strings[move_i].ps, minHeight, maxHeight);
}
}
function mousePressed() {
if (over) {
move = true;
}
}
function mouseReleased() {
move = false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
I am working with a javascript animation that shows ripples in water in html canvas.
This is the javascript code and the jfiddle link
(function(){
var canvas = document.getElementById('c'),
/** #type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = 400,
height = 400,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
with (ctx) {
fillStyle = '#a2ddf8';
fillRect(0, 0, width, height);
fillStyle = '#07b';
save();
rotate(-0.785);
for (var i = 0; i < count; i++) {
fillRect(-width, i * step, width * 3, line_width);
}
restore();
}
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 128;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var a, b, data, cur_pixel, new_pixel, old_data;
var t = oldind; oldind = newind; newind = t;
var i = 0;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
var _newind = newind + i, _mapind = oldind + i;
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind];
data -= data >> 5;
_ripplemap[_newind] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++i;
}
}
}
canvas.onmousemove = function(/* Event */ evt) {
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
setInterval(function() {
disturb(rnd() * width, rnd() * height);
}, 700);
})();
The issue is the ripple spills over from one side of the canvas and animates on the opposite side, I want to keep them from moving into the opposite sides.
Thanks!
Stopping waves at edges
To stop the propagation of the waves across the edges you need to limit the function that adds the disturbance so that it does not write past the edges.
So change...
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var y = dy - riprad; y < dy + riprad; y++) {
for (var x = dx - riprad; x < dx + riprad; x++) {
ripplemap[oldind + (y * width) + x] += 128;
}
}
}
to...
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var y = dy - riprad; y < dy + riprad; y++) {
for (var x = dx - riprad; x < dx + riprad; x++) {
// don't go past the edges.
if(y >= 0 && y < height && x >= 0 && x < width){
ripplemap[oldind + (y * width) + x] += 128;
}
}
}
}
And you also need to change the wave propagation that is in the function newFrame. The propagation is controlled by the value in data. A value of zero means no wave and no propagation. So test if the pixel is on the edge. If the pixel is an edge pixel then just stop the wave by setting data to zero. To save CPU cycles I have added to vars h, w that are height - 1 and width - 1 so you don't have to perform the subtraction twice for every pixel.
Change the code in the function newFrame from...
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
var _newind = newind + i, _mapind = oldind + i;
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
}
To...
var w = _width - 1; // to save having to subtract 1 for each pixel
var h = _height - 1; // dito
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
var _newind = newind + i, _mapind = oldind + i;
// is the pixel on the edge
if(y === 0 || x === 0 || y === h || x === w){
data = 0; // yes edge pixel so stop propagation.
}else{
// not on the edge so just do as befor.
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
}
This is not perfect as it will dampen the waves bouncing from the sides but you don't have much CPU time so it is a good solution.
Some notes.
Use requestAnimationFrame rather than setInterval(run, delay) to time the animation as you will cause some devices to crash the page with the code you currently have, if the device can not handle the CPU load. requestAnimationFrame will stop this happening.
Change the run function to
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
requestAnimationFrame(run);
}
At bottom of code remove setInterval(run,delay); and add
requestAnimationFrame(run);
And change the second setInterval to
var drops = function() {
disturb(rnd() * width, rnd() * height);
setTimeout(drops,700)
};
drops();
NEVER use setInterval, especially for this type of CPU intensive code. You will cause many machines to crash the page. Us setTimeout or requestAnimationFrame instead.
Also remove the with statement where you create the texture.
function randomXToY(minVal,maxVal,floatVal)
{
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
Ball = (function() {
// constructor
function Ball(x,y,radius,color){
this.center = {x:x, y:y};
this.radius = radius;
this.color = color;
this.dx = 2;
this.dy = 2;
this.boundaryHeight = $('#ground').height();
this.boundaryWidth = $('#ground').width();
this.dom = $('<p class="circle"></p>').appendTo('#ground');
// the rectange div a circle
this.dom.width(radius*2);
this.dom.height(radius*2);
this.dom.css({'border-radius':radius,background:color});
this.placeAtCenter(x,y);
}
// Place the ball at center x, y
Ball.prototype.placeAtCenter = function(x,y){
this.dom.css({top: Math.round(y- this.radius), left: Math.round(x - this.radius)});
this.center.x = Math.round(x);
this.center.y = Math.round(y);
};
Ball.prototype.setColor = function(color) {
if(color) {
this.dom.css('background',color);
} else {
this.dom.css('background',this.color);
}
};
// move and bounce the ball
Ball.prototype.move = function(){
var diameter = this.radius * 2;
var radius = this.radius;
if (this.center.x - radius < 0 || this.center.x + radius > this.boundaryWidth ) {
this.dx = -this.dx;
}
if (this.center.y - radius < 0 || this.center.y + radius > this.boundaryHeight ) {
this.dy = -this.dy;
}
this.placeAtCenter(this.center.x + this.dx ,this.center.y +this.dy);
};
return Ball;
})();
var number_of_balls = 5;
var balls = [];
$('document').ready(function(){
for (i = 0; i < number_of_balls; i++) {
var boundaryHeight = $('#ground').height();
var boundaryWidth = $('#ground').width();
var y = randomXToY(30,boundaryHeight - 50);
var x = randomXToY(30,boundaryWidth - 50);
var radius = randomXToY(15,30);
balls.push(new Ball(x,y,radius, '#'+Math.floor(Math.random()*16777215).toString(16)));
}
loop();
});
loop = function(){
for (var i = 0; i < balls.length; i++){
balls[i].move();
}
setTimeout(loop, 8);
};
I have never used in oops concepts in javascript. How do I change the ball color when the balls touches each other?
This is the link : http://jsbin.com/imofat/1/edit
You currently don't have any interaction with the balls. What you can do is checking whether two balls are "inside" each other, and change colors in that case: http://jsbin.com/imofat/1491/.
// calculates distance between two balls
var d = function(a, b) {
var dx = b.center.x - a.center.x;
var dy = b.center.y - a.center.y;
return Math.sqrt(dx*dx + dy*dy);
};
and:
// for each ball
for(var i = 0; i < balls.length; i++) {
// check against rest of balls
for(var j = i + 1; j < balls.length; j++) {
var a = balls[i];
var b = balls[j];
// distance is smaller than their radii, so they are inside each other
if(d(a, b) < a.radius + b.radius) {
// set to some other color using your random color code
a.setColor('#'+Math.floor(Math.random()*16777215).toString(16));
b.setColor('#'+Math.floor(Math.random()*16777215).toString(16));
}
}
}
Still, there are things for improvement:
Balls are changing colors as long as they are inside each other, not just once.
If you want them to "touch", you might want to implement some kind of bouncing effect to make it more realistic.