Javascript Ball Class? - EDIT - javascript

I have been trying to play around with the idea of a Ball class within Javascript that is within an HTML file. Check out my code to see what my hang up is. I have seen people use both a function and a class to accomplish this but I can't seem to get this to work. I feel like I am doing more of a C++ style rather than Javascript. Any suggestions?
class Ball {
constructor(x, y, radius, dx, dy) {
this.x = x;
this.y = y;
this.radius = radius;
this.dx = dx;
this.dy = dy;
}
get X() {
return this.x;
}
get Y() {
return this.y;
}
get Radius() {
return this.radius;
}
get Dx() {
return this.dx;
}
get Dy() {
return this.dy;
}
drawBall() {
ctx.beginPath();
ctx.arc(get X(), get Y(), get Radius(), 0, Math.PI*2);
ctx.fillStyle() = "black";
ctx.fill();
ctx.closePath();
}
};
var Ball1 = new Ball(canvas.width/2, canvas.height-30, 10, 2, -2);
function drawBall() {
ctx.beginPath();
ctx.arc(get X(), get Y(), get Radius(), 0, Math.PI*2);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
drawBall2();
if((Ball1.get X() + Ball1.get Dx()) > canvas.width-Ball1.get Radius() || (Ball1.get X() + Ball1.get Dx()) < Ball1.get Radius()) {
Ball1.set Dx(-(Ball1.get Dx()));
}
if(Ball1.get Y() + Ball1.get Dy() > canvas.height-Ball1.get Radius() || y + dy < ballRadius) {
dy = -dy;
}
if(x2 + dx2 > canvas.width-ballRadius || x2 + dx2 < ballRadius) {
dx2 = -dx2;
}
if(y2 + dy2 > canvas.height-ballRadius || y2 + dy2 < ballRadius) {
dy2 = -dy2;
}
x += dx;
y += dy;
x2 += dx2;
y2 += dy2;
}
I know there are discrepancies in my code like things not matching but I don't think I am on the right track. Thanks ahead of time!
EDIT!
Here is the code that I started with that works so far:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>My Game</title>
<style>
* { padding: 0; margin: 0; }
canvas { background: #eee; display: block; margin: 0 auto; }
</style>
</head>
<body>
<canvas id="myCanvas" width="600" height="580"></canvas>
<script>
//Javascript goes here
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var ballRadius = 10;
//ball 1
var x = canvas.width/2;
var y = canvas.height-30;
var dx = 2;
var dy = -2;
//ball 2
var x2 = canvas.width/3;
var y2 = canvas.height-30;
var dy2 = -4;
var dx2 = 4;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
function drawBall2() {
ctx.beginPath();
ctx.arc(x2, y2, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
drawBall2();
if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if(y + dy > canvas.width-ballRadius || y + dy < ballRadius) {
dy = -dy;
}
if(x2 + dx2 > canvas.width-ballRadius || x2 + dx2 < ballRadius) {
dx2 = -dx2;
}
if(y2 + dy2 > canvas.height-ballRadius || y2 + dy2 < ballRadius) {
dy2 = -dy2;
}
x += dx;
y += dy;
x2 += dx2;
y2 += dy2;
}
setInterval(draw, 10);
</script>
</body>
</html>

I fixed your code a little bit just to give you an Idea. This is still not a very good code design. The example will only work in modern Browsers because you use es6 features like class. I think you should start with some javascript fundamentals first.
"use strict";
class Ball {
constructor(x, y, radius, dx, dy) {
this.x = x;
this.y = y;
this.radius = radius;
this.dx = dx;
this.dy = dy;
}
get X() {
return this.x;
}
get Y() {
return this.y;
}
get Radius() {
return this.radius;
}
get Dx() {
return this.dx;
}
get Dy() {
return this.dy;
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.X, this.Y, this.Radius, 0, Math.PI * 2);
ctx.fillStyle = "black";
ctx.stroke();
}
};
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var Ball1 = new Ball(canvas.width / 2, canvas.height - 30, 10, 2, -2);
Ball1.draw(ctx);
<canvas id="myCanvas" width="200" height="200"></canvas>
Simple Javascript
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
function drawBall(ctx, x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fillStyle = "black";
ctx.stroke();
}
drawBall(ctx, canvas.width / 2, canvas.height - 30, 10, 2, -2);

Related

hmtl5 canvas collision with multiple objects?

So i am doing a flappy bird clone as coding exercise... and now i got to the part where i have to detect the collision bettwen the bird and the pipes.
I would to know how would you suggest be the best way of detecting the collision of the bird with the pipes, taking into account that i am pushing the pipes into an array
this are the main parts of the code
//Pipes proto that the bird most avoid
function Pipes(x1, y1, height1, width1, dx1, dy1, x2, y2, height2,
width2, dx2, dy2) {
this.x1 = x1;
this.y1 = y1;
this.height1 = height1;
this.width1 = width1;
this.dx1 = dx1;
this.dy1 = dy1;
this.x2 = x2;
this.y2 = y2;
this.height2 = height2;
this.width2 = width2;
this.dx2 = dx2;
this.dy2 = dy2;
this.draw = function () {
c.fillStyle = "green";
c.fillRect(x1, y1, height1, width1)
c.fillStyle = "green";
c.fillRect(x2, y2, height2, width2)
}
this.update = function () {
x1 += -dx1;
x2 += -dx2;
this.draw();
}
}
function Bird(x, y, dx, dy, radius, color, flap) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.color = color;
this.draw = function () {
c.beginPath();
c.arc(x, y, radius, 0, 2 * Math.PI, false);
c.fillStyle = color;
c.fill();
};
this.update = function (){
flap = false;
x += dx
//gravity manager
var gravity = 0.4;
y += dy
dy += gravity;
//Screen collision manager
if (y + dy > innerHeight) {
y = innerHeight - radius;
}
if(radius < crashObj.x1 || radius < crashObj.x2 || radius <
crashObj.x1){
}
this.draw()
}
};
//Main GameLoop
function animate() {
c.clearRect(0, 0, innerWidth, innerHeight);
//canvas Color
c.fillStyle = "#C5D3E2";
c.fillRect(0, 0, window.innerWidth, window.innerHeight);
//Updates pipes
for (var i = 0; i < pipesArr.length; i++) {
pipesArr[i].update();
}
//draw and update bird into the screen
bird.update();
requestAnimationFrame(animate);
}
animate();
thanks in advance fo the answer!
What you are looking for is a circle-rectangle collision.
User markE has posted an in-depth answer on how to achieve it, along with a JS Fiddle demonstrating it.
Then on the update function, run through all the Pipes and check each of them for collision. For example:
for(var i = 0; i < pipesArr.length; i++){
...
if(RectCircleColliding(bird, pipe)){
die()
}
}

getting an error "p5practice.js:97 Uncaught TypeError: Cannot read property 'y' of undefined"

I was working on the paddle part of my code and was fixing other errors and after it was fixed, it gave me this error "p5practice.js:97 Uncaught TypeError: Cannot read property 'y' of undefined" Also, if that error can be fixed, how do I properly get my paddle to work? I've looked at several other pong like examples and have had no luck with mine working.
//variables
var canvas;
var ctx;
var w;
var h;
var ball;
var paddle;
var score1;
//ball object
var BALL = function(x, y) {
this.x = x;
this.y = y;
this.color = "white";
this.radius = 12;
this.vx = 3;
this.vy = -3;
};
//paddle 1
var PADDLE = function(x, y) {
this.x = 10;
this.y = h/2 - 50;
this.color = "white";
this.width = 5;
this.height = 100;
};
window.addEventListener("keydown", movePaddle);
//keydown event
//down
function movePaddle(e) {
switch (event.keyCode){
case 38: //up
console.log(paddle.y)
if (paddle.y - 30 >= -10) {
paddle.y -= 30;
}
break;
case 40: // down
console.log(paddle.y)
if (paddle.y + 30 < 305) {
paddle.y += 30;
}
break;
}
}
//DOM load
window.onload = function() {
canvas = document.getElementById('canvas');
w = canvas.width;
h = canvas.height;
ctx = canvas.getContext('2d');
ball = new BALL (w/2, h/2);
paddle = new PADDLE(w-100, h/2);
drawScore();
startGame();
movePaddle();
};
function startGame() {
requestAnimationFrame(startGame);
ctx.clearRect(0, 0, w, h);
ball.x += ball.vx;
ball.y += ball.vy;
ctx.fillStyle = ball.color;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, 2*Math.PI, true);
ctx.closePath();
ctx.save();
ctx.fill();
ctx.restore();
ctx.fillStyle = paddle.color;
ctx.beginPath();
ctx.save();
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.shadowColor = "red";
ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);
ctx.closePath();
ctx.restore();
}
//collision with walls
//https://www.khanacademy.org/computer-programming/paddle-ball/830543654
//bottom
if(ball.y >= 390) {
ball.vy = -ball.vy;
console.log("bottom hit");
}
//top
if(ball.y <= 10) {
ball.vy = -ball.vy;
console.log("top hit");
}
//right
if (ball.x >= 590){
ball.vx = -ball.vx;
console.log("right hit");
}
//left
if(ball.x <= 10){
score++;
ball.x = w/2;
ball.y = h/2;
console.log("left hit");
}
//collision with paddle
if(ball.x<15){
if(ball.y>paddle.y && ball.y<paddle.y + paddle.height){
ball.vy = -ball.vy;
console.log("paddle");
}
}
//reset
//https://bl.ocks.org/ruffrey/1e242222aebbcd102a53
//not finished
function reset(){
ball.x;
ball.y;
}
//score
var score = 0;
function drawScore(){
ctx.font ="16px Arial";
ctx.fillStyle ="#0095DD";
ctx.fillText("Score: "+score,w/2, 20);
}
var leftScore = this.x > 10;
if (leftScore){
playerCount.score++;
}
this.reset();
canvas {
border: 3px solid yellow;
background-color: black;
}
<!doctype html>
<html>
<head>
<title>Pong</title>
<script src="p5practice.js"></script>
<link rel= 'stylesheet' href="p5practice.css">
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
</script>
</body>
</html>
You need to move your if statements at the bottom into the onload or startGame function. They are running before the variable ball is getting initialized. So you are getting the error that is saying ball is undefined
You will also need to move this or it will complain about x:
var leftScore = this.x > 10;
//variables
var canvas;
var ctx;
var w;
var h;
var ball;
var paddle;
var score1;
//ball object
var BALL = function(x, y) {
this.x = x;
this.y = y;
this.color = "white";
this.radius = 12;
this.vx = 3;
this.vy = -3;
};
//paddle 1
var PADDLE = function(x, y) {
this.x = 10;
this.y = h / 2 - 50;
this.color = "white";
this.width = 5;
this.height = 100;
};
window.addEventListener("keydown", movePaddle);
//keydown event
//down
function movePaddle(e) {
switch (event.keyCode) {
case 38: //up
console.log(paddle.y)
if (paddle.y - 30 >= -10) {
paddle.y -= 30;
}
break;
case 40: // down
console.log(paddle.y)
if (paddle.y + 30 < 305) {
paddle.y += 30;
}
break;
}
}
//DOM load
window.onload = function() {
canvas = document.getElementById('canvas');
w = canvas.width;
h = canvas.height;
ctx = canvas.getContext('2d');
ball = new BALL(w / 2, h / 2);
paddle = new PADDLE(w - 100, h / 2);
drawScore();
startGame();
movePaddle();
//bottom
if (ball.y >= 390) {
ball.vy = -ball.vy;
console.log("bottom hit");
}
//top
if (ball.y <= 10) {
ball.vy = -ball.vy;
console.log("top hit");
}
//right
if (ball.x >= 590) {
ball.vx = -ball.vx;
console.log("right hit");
}
//left
if (ball.x <= 10) {
score++;
ball.x = w / 2;
ball.y = h / 2;
console.log("left hit");
}
//collision with paddle
if (ball.x < 15) {
if (ball.y > paddle.y && ball.y < paddle.y + paddle.height) {
ball.vy = -ball.vy;
console.log("paddle");
}
}
};
function startGame() {
requestAnimationFrame(startGame);
ctx.clearRect(0, 0, w, h);
ball.x += ball.vx;
ball.y += ball.vy;
ctx.fillStyle = ball.color;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.save();
ctx.fill();
ctx.restore();
ctx.fillStyle = paddle.color;
ctx.beginPath();
ctx.save();
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.shadowColor = "red";
ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);
ctx.closePath();
ctx.restore();
}
//collision with walls
//https://www.khanacademy.org/computer-programming/paddle-ball/830543654
//reset
//https://bl.ocks.org/ruffrey/1e242222aebbcd102a53
//not finished
function reset() {
ball.x;
ball.y;
}
//score
var score = 0;
function drawScore() {
ctx.font = "16px Arial";
ctx.fillStyle = "#0095DD";
ctx.fillText("Score: " + score, w / 2, 20);
var leftScore = this.x > 10;
if (leftScore) {
playerCount.score++;
}
this.reset();
}
canvas {
border: 3px solid yellow;
background-color: black;
}
<!doctype html>
<html>
<head>
<title>Pong</title>
<script src="p5practice.js"></script>
<link rel='stylesheet' href="p5practice.css">
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
</script>
</body>
</html>

html5 canvas rotate function inside of object not working

I am trying to get the spin function to work with an instance of a ball i have created below.
i cant access the spin function from ball1 for some reason.
If ive tested logging to the console from the spin function and it is working but i cant figure out how to get get the ball rotating.
i had it working in earlier versions but not not fully.
Any help would be greatly appreciated.
Please see below for my code.
<!DOCTYPE html>
<hmtl>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<style type="text/css">
canvas{
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="canvasOne" ></canvas>
<script type="text/javascript">
// Gets a handle to the element with id canvasOne.
var canvas = document.getElementById("canvasOne");
// Set the canvas up for drawing in 2D.
var ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
function Ball(xpos, ypos, r) {
this.xpos = xpos;
this.ypos = ypos;
this.r = r;
this.move = function(addx, addy) {
this.xpos = this.xpos + addx;
this.ypos = this.ypos + addy;
};
this.resize = function(setr) {
this.r = setr;
};
this.draw = function() {
for (var i = 0; i < 7; i++) {
ctx.beginPath();
ctx.moveTo(this.xpos, this.ypos);
ctx.arc(this.xpos, this.ypos, this.r, i * (2 * Math.PI / 7), (i + 1) * (2 * Math.PI / 7));
ctx.lineWidth = 2;
ctx.stroke();
}
ctx.beginPath();
ctx.moveTo(this.xpos, this.ypos);
ctx.arc(this.xpos, this.ypos, this.r - 10, 0, 2 * Math.PI);
ctx.lineWidth = 2;
ctx.stroke();
};
this.spin = function() {
var angle = 0;
this.angle = (this.angle + 1) % 360;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(this.xpos,this.ypos);
ctx.rotate( this.angle*Math.PI/180 );
ctx.translate(-this.xpos,-this.ypos);
ctx.beginPath();
this.draw();
ctx.restore();
};
this.contains = function(x, y) {
this.x = this.x;
this.y = this.y;
if (Math.sqrt((x - this.xpos) * (x - this.xpos) + (y - this.ypos) * (y - this.ypos)) <= this.r) {
return true;
} else {
return false;
}
};
//put "ball" as a paremeter
//ball will be the foreign Ball to test intersection against
this.intersect = function(ball) {
var productX = this.xpos - ball.xpos;
var productY = this.ypos - ball.ypos;
var distance = Math.sqrt(productX * productX + productY * productY);
if (distance <= (this.r + ball.r)) {
return true;
} else {
return false;
}
};
}
var ball1 = new Ball(100, 100, 100);
ball1.draw();
ball1.spin();
setInterval(ball1.spin,100);
</script>
</body>
</html>

store a value from a random generator

I'm still new to javascript.
I'm working on an javascript tutorial from here https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls
One of the more challenging exercise is to make the ball change color when it bounce off from a wall. So far i manage to do this:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var x = canvas.width/2;
var y = canvas.height-30;
var dx = 2;
var dy = -2;
var ballRadius = 10;
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function ballColor(){
var newColor = getRandomColor();
return newColor;
}
function drawBall(){
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle=ballColor();
ctx.fill();
ctx.closePath();}
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawBall();
x += dx;
y += dy;
if(y+dy>canvas.height-ballRadius||y+dy<ballRadius){
dy = -dy;
}
if(x+dx>canvas.width-ballRadius||x+dx<ballRadius){
dx = -dx;}
};
setInterval(draw,30);
If i run the function draw(), the ball will change color for every frame, making it very "flashing".
How do i get a fix color from getRandomColor() and store it in a variable (let say fixedColorOnly), so that the ball will only display fixedColorOnly until it hit another wall? By then the fixedColorOnly will store another color from getRandomNumber() and bounce and so on.
Thanks in advance.
Just create another global variable for the color. Here's a quick refactor:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="myCanvas" style="border: 1px solid black"></canvas>
<script>
var canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d"),
x = canvas.width / 2,
y = canvas.height - 30,
dx = 2,
dy = -2,
ballRadius = 10,
color = getRandomColor();
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
x += dx;
y += dy;
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
dy = -dy;
color = getRandomColor();
}
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
color = getRandomColor();
}
};
setInterval(draw, 30);
</script>
</body>
</html>

How to draw a curved spring in a HTML5 Canvas?

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

Categories