In this script I’m trying to make a coordinate plane with two dots/circles. When I add the second dot in the code, it only shows the second one.
The part with the dots is this piece of code:
point(AX, AY, false, 'red', 6)
point(BX, BY, false, 'red', 6)
Can someone please help me with this problem? Thanks a lot!
start();
function start() {
console.clear();
document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var AX = document.getElementById("AX").value;
var AY = document.getElementById("AY").value;
var BX = document.getElementById("BX").value;
var BY = document.getElementById("BY").value;
var SQUARE_SIZE = 30;
var XSCALE = 1;
var YSCALE = 1;
var centerX = 0;
var centerY = 0;
selected = [];
points = [];
lines = [];
segments = [];
history = [];
circles = [];
function point(x, y, isSelected, color, r) {
this.x = x;
this.y = y;
this.color = color;
this.r = r;
this.add = function () {
plotPoint(this.x, this.y, this.color, this.r);
};
points.push(this);
if (isSelected) {
selected.push(this);
}
this.distance = function (gx, gy) {
return Math.sqrt(Math.pow(this.x - gx, 2) + Math.pow(this.y - gy, 2));
};
}
function point1(x, y, isSelected, color, r) {
this.x = x;
this.y = y;
this.color = color;
this.r = r;
this.add = function () {
plotPoint(this.x, this.y, this.color, this.r);
};
points.push(this);
if (isSelected) {
selected.push(this);
}
this.distance = function (gx, gy) {
return Math.sqrt(Math.pow(this.x - gx, 2) + Math.pow(this.y - gy, 2));
};
}
function circle(x, y, color, r) {
this.x = x;
this.y = y;
this.color = color;
this.r = r;
this.add = function () {
ctx.beginPath();
ctx.arc(convertX(x), convertY(y), r, 0, 2 * Math.PI);
ctx.stroke();
};
}
function line(m, b, color, width) {
this.m = m;
this.b = b;
this.color = color;
this.width = width;
lines.push(this);
}
function segment(a, b, color, width) {
this.a = a;
this.b = b;
this.color = color;
this.width = width;
this.getSlope = function () {
return (b.y - a.y) / (b.x - a.x);
};
this.getIntercept = function () {
var m = this.getSlope();
return a.y - m * a.x;
};
this.getLength = function () {
return Math.sqrt(this.a.x - this.b.x + this.a.y - this.b.y);
};
this.distance = function (gx, gy) {
//var m = (b.y-a.y)/(b.x-a.x)
//var bb = a.y-m*a.x
var m = this.getSlope();
var bb = this.getIntercept();
var pim = 1 / -m;
var pib = gy - pim * gx;
if (m === 0) {
pix = gx;
piy = this.a.y;
} else if (Math.abs(m) === Infinity) {
var pix = this.a.x;
var piy = gy;
} else {
var pix = (pib - bb) / (m - pim); //((gy-(gx/m)-bb)*m)/(m*m-1)
var piy = pim * pix + pib;
}
//console.log("m:"+m+" pim:"+pim+" pib:"+pib+" pix"+pix+" piy:"+piy)
if (
((this.a.x <= pix && pix <= this.b.x) ||
(this.b.x <= pix && pix <= this.a.x)) &&
((this.a.y <= piy && piy <= this.b.y) ||
(this.b.y <= piy && piy <= this.a.y))
) {
var d = Math.sqrt(Math.pow(gx - pix, 2) + Math.pow(gy - piy, 2));
return d;
} else {
var d = Math.min(this.a.distance(gx, gy), this.b.distance(gx, gy));
return d;
}
};
this.add = function () {
if (selected.indexOf(this) > -1) {
plotLine(this.a.x, this.a.y, this.b.x, this.b.y, color, width, [5, 2]);
} else {
plotLine(this.a.x, this.a.y, this.b.x, this.b.y, color, width);
}
};
segments.push(this);
}
// var a = new point(1,1)
// var b = new point(3,4)
// new segment(a,b)
//var testline = new line(1, 2, 'red', 1)
function drawLine(x1, y1, x2, y2, color, width, dash) {
ctx.save();
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = color;
ctx.lineWidth = width;
if (dash !== undefined) {
ctx.setLineDash(dash);
}
ctx.stroke();
ctx.restore();
}
function convertX(x) {
return ((x - xmin - centerX) / (xmax - xmin)) * width;
}
function revertX(x) {
return (x * (xmax - xmin)) / width + centerX + xmin;
}
function convertY(y) {
return ((ymax - y - centerY) / (ymax - ymin)) * height;
}
function revertY(y) {
return (y * (ymin - ymax)) / height - centerY - ymin;
}
function addAxis() {
var TICK = 0;
for (
var i = Math.floor(xmin + centerX);
i <= Math.floor(xmax + centerX);
i += XSCALE
) {
drawLine(
convertX(i),
convertY(0) + TICK,
convertX(i),
convertY(0) - TICK
);
}
for (
var i = Math.floor(ymin - centerY);
i <= Math.floor(ymax - centerY);
i += YSCALE
) {
drawLine(
convertX(0) - TICK,
convertY(i),
convertX(0) + TICK,
convertY(i)
);
}
}
function addGrid() {
for (
var i = Math.floor(ymin - centerY);
i <= Math.floor(ymax - centerY);
i += YSCALE
) {
drawLine(0, convertY(i), width, convertY(i), "lightgrey", 1);
}
for (
var i = Math.floor(xmin + centerX);
i <= Math.floor(xmax + centerX);
i += XSCALE
) {
drawLine(convertX(i), height, convertX(i), 0, "lightgrey", 1);
}
}
function addPoints() {
for (const p of points) {
p.add();
}
}
function addCircles() {
for (const c of circles) {
c.add();
}
}
function addLines() {
for (const l of lines) {
plotLine(
xmin + centerX,
l.m * (xmin + centerX) + l.b,
xmax + centerX,
l.m * (xmax + centerX) + l.b,
l.color,
l.width
);
}
}
function addSegments() {
for (const s of segments) {
s.add();
}
}
function plotPoint(x, y, color, r) {
if (r === undefined) {
r = 2;
}
if (color === undefined) {
color = "black";
}
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(convertX(x), convertY(y), r, 0, 2 * Math.PI);
ctx.fill();
}
function plotCircle(x, y, color, r) {
if (r === undefined) {
r = 2;
}
if (color === undefined) {
color = "black";
}
ctx.beginPath();
ctx.arc(convertX(x), convertY(y), r, 0, 2 * Math.PI);
ctx.stroke();
}
function plotLine(x1, y1, x2, y2, color, width, dash) {
ctx.save();
ctx.beginPath();
ctx.moveTo(convertX(x1), convertY(y1));
ctx.lineTo(convertX(x2), convertY(y2));
ctx.strokeStyle = color;
ctx.lineWidth = width;
if (dash !== undefined) {
ctx.setLineDash(dash);
}
ctx.stroke();
ctx.restore();
}
function snap(x) {
if ((x - Math.round(x)) * (x - Math.round(x)) < 0.01) {
return Math.round(x);
} else {
return x;
}
}
function mouseDown(evt) {
x = evt.clientX;
y = evt.clientY;
ocx = centerX;
ocy = centerY;
if (evt.buttons === 2) {
for (const p of points) {
if (
nx * nx - 2 * convertX(p.x) * nx + convertX(p.x) * convertX(p.x) <
36 &&
ny * ny - 2 * convertY(p.y) * ny + convertY(p.y) * convertY(p.y) < 36
) {
s = new segment(p, new point(revertX(x), revertY(y), true));
selected.push(s);
return;
}
}
new point(snap(revertX(x)), snap(revertY(y)));
}
if (evt.buttons === 1) {
for (const p of points) {
if (p.distance(revertX(x), revertY(y)) < 0.2) {
selected.push(p);
}
console.log(p.distance(revertX(x), revertY(y)));
}
for (const s of segments) {
if (s.distance(revertX(x), revertY(y)) < 0.2) {
selected.push(s);
}
console.log(s.distance(revertX(x), revertY(y)));
}
}
onresize();
}
function mouseUp() {
selected = [];
}
function mouseMove(evt) {
console.clear();
nx = evt.clientX;
ny = evt.clientY;
gx = revertX(nx);
gy = revertY(ny);
if (evt.buttons === 1) {
if (selected.length > 0) {
for (const p of selected) {
p.x = snap(gx);
p.y = snap(gy);
}
} else {
centerX = (x - nx) / SQUARE_SIZE + ocx;
centerY = (y - ny) / SQUARE_SIZE + ocy;
}
}
if (evt.buttons === 2) {
for (const p of selected) {
p.x = snap(gx);
p.y = snap(gy);
}
}
console.log("coords: " + gx + ", " + gy);
console.log("points: " + points);
console.log("segments:" + segments);
console.log("selected: " + selected);
onresize();
}
function keyPress(evt) {
if ((evt.keyCode = 32)) {
//space
if (selected.length > 0) selected = [];
}
onresize();
}
point(AX, AY, false, "red", 6);
point(BX, BY, false, "red", 6);
window.onresize = function () {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
xmin = -width / SQUARE_SIZE / 2;
xmax = width / SQUARE_SIZE / 2;
ymin = -height / SQUARE_SIZE / 2;
ymax = height / SQUARE_SIZE / 2;
addGrid();
addAxis();
addPoints();
addLines();
addSegments();
addCircles();
ctx.font = "12px Arial";
ctx.fillStyle = "black";
ctx.fillText("Number of Points: " + points.length, 20, 30);
ctx.fillText("Points Slected: " + selected.length, 20, 50);
};
onresize();
}
<h2>LocusCreator v1.0 - © Niels Langerak</h2>
<p>Use the inputboxes to fill in all the info to make the locus.</p>
<p>Circle A - X:</p>
<input type="number" id="AX" value="0">
<p>Circle A - Y:</p>
<input type="number" id="AY" value="0">
<p>Circle B - X:</p>
<input type="number" id="BX" value="1">
<p>Circle B - Y:</p>
<input type="number" id="BY" value="1">
<button onclick="start()">Reload</button>
<canvas width=600px height=600px id='canvas'>
You should use the new keyword when creating your point:
new point(AX, AY, false, 'red', 6)
new point(BX, BY, false, 'red', 6)
It creates a new instance of point, but not overwrite it, as it works in your code.
Related
I want to complete polygon when starting point of polygon clicked, i think i need to hide or set opacity of last line which is linking to first point of line.
goal is to achieve exactly what is happening in this GIF.
also i want to show angles and length on each line while drawing just like exactly same as GIF.
var bw = window.innerWidth -20;
var bh = window.innerHeight -20;
var p = 10;
var cw = bw + (p*2) + 1;
var ch = bh + (p*2) + 1;
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var context = canvas.getContext("2d");
function drawBoard(){
for (var x = 0; x <= bw; x += 30) {
context.moveTo(0.5 + x + p, p);
context.lineTo(0.5 + x + p, bh + p);
}
for (var x = 0; x <= bh; x += 30) {
context.moveTo(p, 0.5 + x + p);
context.lineTo(bw + p, 0.5 + x + p);
}
context.lineWidth = 0.5;
context.strokeStyle = 'rgba(0, 122, 204,0.7)';
context.stroke();
}
drawBoard();
//---------------------------------------------
requestAnimationFrame(update)
mouse = {x : 0, y : 0, button : 0, lx : 0, ly : 0, update : true};
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown","mouseup","mousemove"].forEach(name => document.addEventListener(name,mouseEvents));
context.lineWidth = 2;
context.strokeStyle = "red";
const point = (x,y) => ({x,y});
const poly = () => ({
points : [],
addPoint(p){ this.points.push(point(p.x,p.y)) },
draw() {
context.lineWidth = 2;
context.strokeStyle = "red";
context.fillStyle = 'rgba(255, 0, 0, 0.3)';
context.beginPath();
for (const p of this.points) { context.lineTo(p.x,p.y) }
context.closePath();
context.stroke();
context.fill();
context.fillStyle = 'rgba(0, 0, 0, 0.3)';
context.strokeStyle = 'rgba(0, 0, 0, 0.8)';
for (const p of this.points) {
context.beginPath();
context.moveTo(p.x + 10,p.y);
context.arc(p.x,p.y,10,0,Math.PI *2);
context.fill();
context.stroke();
}
},
closest(pos, dist = 8) {
var i = 0, index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
function drawCircle(pos,color="black",size=8){
context.strokeStyle = color;
context.beginPath();
context.arc(pos.x,pos.y,size,0,Math.PI *2);
context.stroke();
}
const polygon = poly();
var activePoint,cursor;
var dragging= false;
function update(){
if (mouse.update) {
cursor = "crosshair";
context.clearRect(0,0,canvas.width,canvas.height);
drawBoard();
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if(activePoint) {
if (mouse.button) {
if(dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else { dragging = true }
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
<html>
<body style=" background: lightblue;">
<canvas id="canvas" style="background: #fff; magrin:20px;"></canvas>
</body>
</html>
Drawing part of code i copied from: https://stackoverflow.com/a/53437943/3877726
You can use ctx.setTransform to to align text to a line.
First normalize the vector between the end points and use that normalized vector to build the transform. See example.
To prevent text from reading back to front you need to check the x component of the normalized vector. If it is < 0 then reverse the vector.
Almost the same for the angle. See example.
Example
Snippets contain the functions drawLineText and drawAngleText (near top) that implement the additional features.
var bw = innerWidth - 20, bh = innerHeight - 20;
var cw = bw + (p * 2) + 1, ch = bh + (p * 2) + 1;
var p = 10;
var activePoint, cursor, dragging = false;
const mouse = {x: 0, y: 0, button: 0, lx: 0, ly: 0, update: true};
const TEXT_OFFSET = 5;
const TEXT_COLOR = "#000";
const TEXT_SIZE = 16;
const FONT = "arial";
const TEXT_ANGLE_OFFSET = 25;
const DEG = "°";
canvas.width = bw;
canvas.height = bh;
var ctx = canvas.getContext("2d");
function drawLineText(p1, p2, text, textOffset = TEXT_OFFSET, textColor = TEXT_COLOR, textSize = TEXT_SIZE, font = FONT) {
var x = p1.x, y = p1.y;
var nx = p2.x - x, ny = p2.y - y, len = (nx * nx + ny * ny) ** 0.5;
nx /= len;
ny /= len;
ctx.font = textSize + "px " + font;
ctx.textAlign = "center";
ctx.fillStyle = textColor;
if (nx < 0) {
ctx.textBaseline = "top";
x = p2.x;
y = p2.y;
nx = -nx;
ny = -ny;
textOffset = -textOffset;
} else { ctx.textBaseline = "bottom" }
len /= 2;
ctx.setTransform(nx, ny, -ny, nx, x, y);
ctx.fillText(text, len, -textOffset);
}
// angle between p2-p1 and p2-p3
function drawAngleText(p1, p2, p3, textAngleOffset = TEXT_ANGLE_OFFSET, textColor = TEXT_COLOR, textSize = TEXT_SIZE, font = FONT) {
var ang;
var x = p2.x, y = p2.y;
var nx1 = p1.x - x, ny1 = p1.y - y, len1 = (nx1 * nx1 + ny1 * ny1) ** 0.5;
var nx2 = p3.x - x, ny2 = p3.y - y, len2 = (nx2 * nx2 + ny2 * ny2) ** 0.5;
nx1 /= len1;
ny1 /= len1;
nx2 /= len2;
ny2 /= len2;
const cross = nx1 * ny2 - ny1 * nx2;
const dot = nx1 * nx2 + ny1 * ny2;
if (dot < 0) {
ang = cross < 0 ? -Math.PI - Math.asin(cross) : Math.PI - Math.asin(cross);
} else {
ang = Math.asin(cross);
}
const angDeg = Math.abs(ang * (180 / Math.PI)).toFixed(0) + DEG;
ctx.font = textSize + "px " + font;
ctx.fillStyle = textColor;
ctx.textBaseline = "middle";
const centerAngle = Math.atan2(ny1, nx1) + ang / 2;
const nx = Math.cos(centerAngle);
const ny = Math.sin(centerAngle);
if (nx < 0) {
ctx.textAlign = "right";
ctx.setTransform(-nx, -ny, ny, -nx, x, y);
textAngleOffset = -textAngleOffset;
} else {
ctx.textAlign = "left";
ctx.setTransform(nx, ny, -ny, nx, x, y);
}
ctx.fillText(angDeg, textAngleOffset, 0);
}
//---------------------------------------------
requestAnimationFrame(update)
function mouseEvents(e) {
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown", "mouseup", "mousemove"].forEach(name => document.addEventListener(name, mouseEvents));
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
const point = (x, y) => ({x, y});
const poly = () => ({
points: [],
closed: false,
addPoint(p) { this.points.push(point(p.x, p.y)) },
draw() {
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
ctx.beginPath();
for (const p of this.points) { ctx.lineTo(p.x, p.y) }
this.closed && ctx.closePath();
ctx.stroke();
this.closed && ctx.fill();
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
for (const p of this.points) {
ctx.beginPath();
ctx.moveTo(p.x + 10, p.y);
ctx.arc(p.x, p.y, 10, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
this.points.length > 1 && this.drawLengthText();
this.points.length > 2 && this.drawAngleText();
},
drawLengthText() {
const len = this.points.length;
var p1, i = 0;
p1 = this.points[i];
while (i < len -(this.closed ? 0 : 1)) {
const p2 = this.points[((i++) + 1) % len];
const lineLength = ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5
drawLineText(p1, p2, lineLength.toFixed(0) + "px");
if (len < 3) { break }
p1 = p2;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
},
drawAngleText() {
const len = this.points.length;
var p1, p2, i = this.closed ? 0 : 1;
p1 = this.points[(i + len - 1) % len];
p2 = this.points[i];
while (i < len -(this.closed ? 0 : 1)) {
const p3 = this.points[((i++) + 1) % len];
drawAngleText(p1, p2, p3);
p1 = p2;
p2 = p3;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
},
closest(pos, dist = 8) {
var i = 0,
index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
const polygon = poly();
function drawCircle(pos, color = "black", size = 8) {
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(pos.x, pos.y, size, 0, Math.PI * 2);
ctx.stroke();
}
function update() {
if (mouse.update) {
cursor = "crosshair";
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if (activePoint) {
if (mouse.button) {
if (dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else {
if (!polygon.closed && polygon.points.length > 2 && activePoint === polygon.points[0]) {
polygon.closed = true;
}
dragging = true
}
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
Click to add points
<canvas id="canvas" style="background: #fff; magrin:20px;"></canvas>
Note though not mandatory it is customarily considered polite to include attributions when copying code.
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).
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>
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>
Hi I try to create an animation with a circle. The function drawRandom(drawFunctions) should pic one of the three drawcircle functions and should bring it on the canvas. Now the problem is that this function become executed every second (main loop) and the circle change his colour. How can I fix that?
window.onload = window.onresize = function() {
var C = 1; // canvas width to viewport width ratio
var el = document.getElementById("myCanvas");
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var canvasWidth = viewportWidth * C;
var canvasHeight = viewportHeight;
el.style.position = "fixed";
el.setAttribute("width", canvasWidth);
el.setAttribute("height", canvasHeight);
var x = canvasWidth / 100;
var y = canvasHeight / 100;
var ballx = canvasWidth / 100;
var n;
window.ctx = el.getContext("2d");
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// draw triangles
function init() {
ballx;
return setInterval(main_loop, 1000);
}
function drawcircle1()
{
var radius = x * 5;
ctx.beginPath();
ctx.arc(ballx * 108, canvasHeight / 2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'yellow';
ctx.fill();
}
function drawcircle2()
{
var radius = x * 5;
ctx.beginPath();
ctx.arc(ballx * 108, canvasHeight / 2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'blue';
ctx.fill();
}
function drawcircle3()
{
var radius = x * 5;
ctx.beginPath();
ctx.arc(ballx * 105, canvasHeight / 2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'orange';
ctx.fill();
}
function draw() {
var counterClockwise = false;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
//first halfarc
ctx.beginPath();
ctx.arc(x * 80, y * 80, y * 10, 0 * Math.PI, 1 * Math.PI, counterClockwise);
ctx.lineWidth = y * 1;
ctx.strokeStyle = 'black';
ctx.stroke();
// draw stop button
ctx.beginPath();
ctx.moveTo(x * 87, y * 2);
ctx.lineTo(x * 87, y * 10);
ctx.lineWidth = x;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x * 95, y * 2);
ctx.lineTo(x * 95, y * 10);
ctx.lineWidth = x;
ctx.stroke();
function drawRandom(drawFunctions){
//generate a random index
var randomIndex = Math.floor(Math.random() * drawFunctions.length);
//call the function
drawFunctions[randomIndex]();
}
drawRandom([drawcircle1, drawcircle2, drawcircle3]);
}
function update() {
ballx -= 0.1;
if (ballx < 0) {
ballx = -radius;
}
}
function main_loop() {
draw();
update();
collisiondetection();
}
init();
function initi() {
console.log('init');
// Get a reference to our touch-sensitive element
var touchzone = document.getElementById("myCanvas");
// Add an event handler for the touchstart event
touchzone.addEventListener("mousedown", touchHandler, false);
}
function touchHandler(event) {
// Get a reference to our coordinates div
var can = document.getElementById("myCanvas");
// Write the coordinates of the touch to the div
if (event.pageX < x * 50 && event.pageY > y * 10) {
ballx += 1;
} else if (event.pageX > x * 50 && event.pageY > y * 10 ) {
ballx -= 1;
}
console.log(event, x, ballx);
draw();
}
initi();
draw();
}
Take a look at my code that I wrote:
var lastTime = 0;
function requestMyAnimationFrame(callback, time)
{
var t = time || 16;
var currTime = new Date().getTime();
var timeToCall = Math.max(0, t - (currTime - lastTime));
var id = window.setTimeout(function(){ callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20;
canvas.style.width = canvas.width + "px";
canvas.style.height = canvas.height + "px";
var circles = [];
var mouse =
{
x: 0,
y: 0
}
function getCoordinates(x, y)
{
return "(" + x + ", " + y + ")";
}
function getRatio(n, d)
{
// prevent division by 0
if (d === 0 || n === 0)
{
return 0;
}
else
{
return n/d;
}
}
function Circle(x,y,d,b,s,c)
{
this.x = x;
this.y = y;
this.diameter = Math.round(d);
this.radius = Math.round(d/2);
this.bounciness = b;
this.speed = s;
this.color = c;
this.deltaX = 0;
this.deltaY = 0;
this.drawnPosition = "";
this.fill = function()
{
context.beginPath();
context.arc(this.x+this.radius,this.y+this.radius,this.radius,0,Math.PI*2,false);
context.closePath();
context.fill();
}
this.clear = function()
{
context.fillStyle = "#ffffff";
this.fill();
}
this.draw = function()
{
if (this.drawnPosition !== getCoordinates(this.x, this.y))
{
context.fillStyle = this.color;
// if commented, the circle will be drawn if it is in the same position
//this.drawnPosition = getCoordinates(this.x, this.y);
this.fill();
}
}
this.keepInBounds = function()
{
if (this.x < 0)
{
this.x = 0;
this.deltaX *= -1 * this.bounciness;
}
else if (this.x + this.diameter > canvas.width)
{
this.x = canvas.width - this.diameter;
this.deltaX *= -1 * this.bounciness;
}
if (this.y < 0)
{
this.y = 0;
this.deltaY *= -1 * this.bounciness;
}
else if (this.y+this.diameter > canvas.height)
{
this.y = canvas.height - this.diameter;
this.deltaY *= -1 * this.bounciness;
}
}
this.followMouse = function()
{
// deltaX/deltaY will currently cause the circles to "orbit" around the cursor forever unless it hits a wall
var centerX = Math.round(this.x + this.radius);
var centerY = Math.round(this.y + this.radius);
if (centerX < mouse.x)
{
// circle is to the left of the mouse, so move the circle to the right
this.deltaX += this.speed;
}
else if (centerX > mouse.x)
{
// circle is to the right of the mouse, so move the circle to the left
this.deltaX -= this.speed;
}
else
{
//this.deltaX = 0;
}
if (centerY < mouse.y)
{
// circle is above the mouse, so move the circle downwards
this.deltaY += this.speed;
}
else if (centerY > mouse.y)
{
// circle is under the mouse, so move the circle upwards
this.deltaY -= this.speed;
}
else
{
//this.deltaY = 0;
}
this.x += this.deltaX;
this.y += this.deltaY;
this.x = Math.round(this.x);
this.y = Math.round(this.y);
}
}
function getRandomDecimal(min, max)
{
return Math.random() * (max-min) + min;
}
function getRoundedNum(min, max)
{
return Math.round(getRandomDecimal(min, max));
}
function getRandomColor()
{
// array of three colors
var colors = [];
// go through loop and add three integers between 0 and 255 (min and max color values)
for (var i = 0; i < 3; i++)
{
colors[i] = getRoundedNum(0, 255);
}
// return rgb value (RED, GREEN, BLUE)
return "rgb(" + colors[0] + "," + colors[1] + ", " + colors[2] + ")";
}
function createCircle(i)
{
// diameter of circle
var minDiameter = 25;
var maxDiameter = 50;
// bounciness of circle (changes speed if it hits a wall)
var minBounciness = 0.2;
var maxBounciness = 0.65;
// speed of circle (how fast it moves)
var minSpeed = 0.3;
var maxSpeed = 0.45;
// getRoundedNum returns a random integer and getRandomDecimal returns a random decimal
var x = getRoundedNum(0, canvas.width);
var y = getRoundedNum(0, canvas.height);
var d = getRoundedNum(minDiameter, maxDiameter);
var c = getRandomColor();
var b = getRandomDecimal(minBounciness, maxBounciness);
var s = getRandomDecimal(minSpeed, maxSpeed);
// create the circle with x, y, diameter, bounciness, speed, and color
circles[i] = new Circle(x,y,d,b,s,c);
}
function makeCircles()
{
var maxCircles = getRoundedNum(2, 5);
for (var i = 0; i < maxCircles; i++)
{
createCircle(i);
}
}
function drawCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].draw();
ii++;
}
}
}
function clearCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].clear();
ii++;
}
}
}
function updateCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].keepInBounds();
circles[i].followMouse();
ii++;
}
}
}
function update()
{
requestMyAnimationFrame(update,10);
updateCircles();
}
function draw()
{
requestMyAnimationFrame(draw,1000/60);
context.clearRect(0,0,canvas.width,canvas.height);
drawCircles();
}
window.addEventListener("load", function()
{
window.addEventListener("mousemove", function(e)
{
mouse.x = e.layerX || e.offsetX;
mouse.y = e.layerY || e.offsetY;
});
makeCircles();
update();
draw();
});