I am trying to detect if two balls are intersecting on a HTML5 canvas.
I need a function called intersect as a part of a constructor object called Ball.
This function will take one Ball object as an argument and return true if the two balls on the canvas are touching/ intersecting. and false otherwise.
I cant figure out how to pass in a new instance of a ball to the intersect function and then compare it to another ball on the canvas.
The function I'm working on the is the final function intersect at the end of the Ball Object.
Please see below for the code i have so far.
Any help would be greatly appreciated.
<!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(ball.xpos, ball.ypos);
ctx.arc(ball.xpos, ball.ypos, ball.r, i*(2 * Math.PI / 7), (i+1)*(2 * Math.PI / 7));
ctx.lineWidth = 2;
ctx.strokeStyle = '#444';
ctx.stroke();
}
ctx.beginPath();
ctx.moveTo(ball.xpos, ball.ypos);
ctx.arc(ball.xpos,ball.ypos,ball.r-10,0,2*Math.PI);
ctx.lineWidth = 2;
ctx.strokeStyle = '#444';
ctx.stroke();
};
this.rotate = function(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Move registration point to the center of the canvas
ctx.translate(ball.xpos, ball.ypos);
// Rotate 1 degree
ctx.rotate(Math.PI / 180);
// Move registration point back to the top left corner of canvas
ctx.translate(-ball.xpos, -ball.ypos);
ball.draw();
ctx.restore();
};
this.contains = function(x, y){
this.x = this.x;
this.y = this.y;
if(Math.sqrt((x-ball.xpos)*(x-ball.xpos) + (y-ball.ypos)*(y-ball.ypos)) <= ball.r)
{
return true;
}else{
return false;
}
};
this.intersect = function(){
this.ball1 = this.ball1;
var distance = (ball.xpos * ball.xpos) + (ball.ypos *ball.ypos);
if(distance <= (ball.r + ball.r)*(ball.r + ball.r)){
return true;
}else{
return false;
}
};
}
var ball = new Ball(100,100,100);
ball.draw();
</script>
</body>
</html>
First off, if you aren't going to use the this keyword in your class, then why make it a class?
You can setup your intersect to take a Ball as a parameter. From here you can calculate the collision between this and the parameter Ball.
You distance function was off, as it only looked on the this object, and i fixed the this problem in your code:
var canvas = document.body.appendChild(document.createElement("canvas"));
// 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.rotate = function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Move registration point to the center of the canvas
ctx.translate(this.xpos, this.ypos);
// Rotate 1 degree
ctx.rotate(Math.PI / 180);
// Move registration point back to the top left corner of canvas
ctx.translate(-this.xpos, -this.ypos);
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);
var ball2 = new Ball(240, 140, 40);
function update(evt) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (evt !== void 0) {
ball2.xpos = evt.offsetX;
ball2.ypos = evt.offsetY;
}
//Pass the ball as an argument to the method
ctx.strokeStyle = ball1.intersect(ball2) ? "red" : '#444';
ball1.draw();
ball2.draw();
}
update();
canvas.onmousemove = update;
I cant figure out how to pass in a new instance of a ball to the
intersect function
Well to pass anything really it should have an argument.
this.intersect = function(otherball){
// then compare the two ball objects
Then...
var ball1 = new Ball(100,100,100);
var ball2 = new Ball(100,100,100);
ball1.draw();
ball2.draw();
console.log(ball1.intersect(ball2));
Related
Currently attempting to make a physics simulation for elastic collisions of circles. I am having an issue where I do not know how to run the simulation with two circles interacting at the same time. I am not yet looking to create the interaction between the circles just to have them both running simultaneously. Any help is much appreciated. This is my first post so I apologize if I formatted something incorrectly.
var width = 400;
var height = 400;
var canvas = ctx = false;
var frameRate = 1 / 60; // Seconds
var frameDelay = frameRate * 1000; // ms
var loopTimer = false;
var ball = {
position: {
x: width / 2,
y: height / 2
},
velocity: {
x: 0,
y: 0
},
radius: 15, // 1px = 1cm
restitution: -1
};
var mouse = {
x: 0,
y: 0,
isDown: false
};
function getMousePosition(event) {
mouse.x = event.pageX - canvas.offsetLeft;
mouse.y = event.pageY - canvas.offsetTop;
}
var mouseDown = function(event) {
if (event.which == 1) {
getMousePosition(event);
mouse.isDown = true;
ball.position.x = mouse.x;
ball.position.y = mouse.y;
}
}
var mouseUp = function(event) {
if (event.which == 1) {
mouse.isDown = false;
ball.velocity.y = (ball.position.y - mouse.y) / 10;
ball.velocity.x = (ball.position.x - mouse.x) / 10;
}
}
var setup = function() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
canvas.onmousemove = getMousePosition;
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
ctx.fillStyle = 'blue';
ctx.strokeStyle = '#000000';
loopTimer = setInterval(loop, frameDelay);
}
var loop = function() {
if (!mouse.isDown) {
ball.position.x += ball.velocity.x * frameRate * 100;
ball.position.y += ball.velocity.y * frameRate * 100;
}
if (ball.position.y > height - ball.radius) {
ball.velocity.y *= ball.restitution;
ball.position.y = height - ball.radius;
}
if (ball.position.x > width - ball.radius) {
ball.velocity.x *= ball.restitution;
ball.position.x = width - ball.radius;
}
if (ball.position.x < ball.radius) {
ball.velocity.x *= ball.restitution;
ball.position.x = ball.radius;
}
if (ball.position.y < ball.radius) {
ball.velocity.y *= ball.restitution;
ball.position.y = ball.radius;
}
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.translate(ball.position.x, ball.position.y);
ctx.beginPath();
ctx.arc(0, 0, ball.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.restore();
if (mouse.isDown) {
ctx.beginPath();
ctx.moveTo(ball.position.x, ball.position.y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
ctx.closePath();
}
}
setup();
#canvas {
border: solid 1px #ccc;
}
<canvas id="canvas"></canvas>
Here is how I would do it:
Instead of making the ball a kind of static object I made a constructor function (More about that here).
Then I made a ball array to store all the balls.
To make the dragging possible I store a seperate ball, which is not being moved by "physics" in the newBall variable. This ball is either invisible or is the ball currently being dragged.
In mouseDown() the newBall gets positioned under the cursor.
In mouseUp() it gets it's velocity and gets added to the array of animated balls. Also a new newBall gets created.
In loop() I loop two times through the array of animated balls. Once for the physics, once for the painting.
(Normally you would use two different methods with different tickRates to make animation more smooth, because physics calculation doesn't need to happen 60 times per second.
var width = 400;
var height = 400;
var canvas = ctx = false;
var frameRate = 1 / 60; // Seconds
var frameDelay = frameRate * 1000; // ms
var loopTimer = false;
function ball() {
this.position = {
x: width / 2,
y: height / 2
};
this.velocity = {
x: 0,
y: 0
};
this.radius = 15; // 1px = 1cm
this.restitution = -1
};
var balls = [];
var newBall = new ball();
var mouse = {
x: 0,
y: 0,
isDown: false
};
function getMousePosition(event) {
mouse.x = event.pageX - canvas.offsetLeft;
mouse.y = event.pageY - canvas.offsetTop;
}
var mouseDown = function(event) {
if (event.which == 1) {
getMousePosition(event);
mouse.isDown = true;
newBall.position.x = mouse.x;
newBall.position.y = mouse.y;
}
}
var mouseUp = function(event) {
if (event.which == 1) {
mouse.isDown = false;
newBall.velocity.y = (newBall.position.y - mouse.y) / 10;
newBall.velocity.x = (newBall.position.x - mouse.x) / 10;
balls.push(newBall);
newBall = new ball();
}
}
var setup = function() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
canvas.onmousemove = getMousePosition;
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
ctx.fillStyle = 'blue';
ctx.strokeStyle = '#000000';
loopTimer = setInterval(loop, frameDelay);
}
var loop = function() {
for (var ball of balls) {
ball.position.x += ball.velocity.x * frameRate * 100;
ball.position.y += ball.velocity.y * frameRate * 100;
if (ball.position.y > height - ball.radius) {
ball.velocity.y *= ball.restitution;
ball.position.y = height - ball.radius;
}
if (ball.position.x > width - ball.radius) {
ball.velocity.x *= ball.restitution;
ball.position.x = width - ball.radius;
}
if (ball.position.x < ball.radius) {
ball.velocity.x *= ball.restitution;
ball.position.x = ball.radius;
}
if (ball.position.y < ball.radius) {
ball.velocity.y *= ball.restitution;
ball.position.y = ball.radius;
}
}
ctx.clearRect(0, 0, width, height);
for (var ball of balls) {
ctx.save();
ctx.translate(ball.position.x, ball.position.y);
ctx.beginPath();
ctx.arc(0, 0, ball.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.restore();
}
ctx.save();
ctx.translate(newBall.position.x, newBall.position.y);
ctx.beginPath();
ctx.arc(0, 0, newBall.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.restore();
if (mouse.isDown) {
ctx.beginPath();
ctx.moveTo(newBall.position.x, newBall.position.y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
ctx.closePath();
}
}
setup();
#canvas {
border: solid 1px #ccc;
}
<canvas id="canvas"></canvas>
Now to get a bit more complex:
I added tickDelay and tickTimer to use them in a tickLoop
The ball constructor now has two methods:
show() draws the ball on the canvas
tick() does the pysics stuff (dt= deltaTime: time since last tick)
newBall is now null if the mouse isn't pressed
setup() initializes the width and height according to the <canvas> elements real size
tick() loops through the balls and calls .tick() tickDelay is in milliseconds so it gets divided by 1000
drawFrame() is your former loop() and does the drawing stuff
var width = 400;
var height = 400;
var canvas = ctx = false;
var frameRate = 1 / 60; // Seconds
var frameDelay = frameRate * 1000; // ms
var tickDelay = frameDelay * 2; //ticks 2 times slower than frames
var frameTimer;
var tickTimer;
function ball() {
this.position = {
x: width / 2,
y: height / 2
};
this.velocity = {
x: 0,
y: 0
};
this.radius = 15; // 1px = 1cm
this.restitution = -.99;
this.show = function() {
ctx.save();
ctx.translate(this.position.x, this.position.y);
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.restore();
};
this.tick = function(dt) {
this.position.x += this.velocity.x * dt;
this.position.y += this.velocity.y * dt;
if (this.position.y > height - this.radius) {
this.velocity.y *= this.restitution;
this.position.y = height - this.radius;
}
if (this.position.x > width - this.radius) {
this.velocity.x *= this.restitution;
this.position.x = width - this.radius;
}
if (this.position.x < this.radius) {
this.velocity.x *= this.restitution;
this.position.x = this.radius;
}
if (this.position.y < this.radius) {
this.velocity.y *= this.restitution;
this.position.y = this.radius;
}
}
};
var balls = [];
var newBall;
var mouse = {
x: 0,
y: 0,
isDown: false
};
function getMousePosition(event) {
mouse.x = event.pageX - canvas.offsetLeft;
mouse.y = event.pageY - canvas.offsetTop;
}
function mouseDown(event) {
if (event.which == 1) {
getMousePosition(event);
mouse.isDown = true;
if (!newBall) newBall = new ball();
newBall.position.x = mouse.x;
newBall.position.y = mouse.y;
}
}
function mouseUp(event) {
if (event.which == 1) {
mouse.isDown = false;
newBall.velocity.y = (newBall.position.y - mouse.y);
newBall.velocity.x = (newBall.position.x - mouse.x);
balls.push(newBall);
newBall = null;
}
}
function setup() {
canvas = document.getElementById("canvas");
width = canvas.getBoundingClientRect().width;
height = canvas.getBoundingClientRect().height;
ctx = canvas.getContext("2d");
canvas.onmousemove = getMousePosition;
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
ctx.fillStyle = 'blue';
ctx.strokeStyle = '#000000';
requestAnimationFrame(drawFrame);
frameTimer = setInterval(drawFrame, frameDelay);
tickTimer = setInterval(tick, tickDelay);
}
function tick() {
for (var ball of balls) ball.tick(tickDelay * .001);
}
function drawFrame() {
ctx.clearRect(0, 0, width, height);
for (var ball of balls) ball.show();
if (newBall) newBall.show(ctx);
if (mouse.isDown && newBall) {
ctx.beginPath();
ctx.moveTo(newBall.position.x, newBall.position.y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
ctx.closePath();
}
}
setup();
#canvas {
border: solid 1px #ccc;
}
<canvas id="canvas"></canvas>
A really simple way would to do exactly the same as you do now, but not initiate all functions as a variable. Change all the variables that are functions to just functions, and where you call them. At least the variable called ball. Then after that you could make two variables like this
ball1 = new ball();
ball2 = new ball();
Your script is kind of messy so hard for me to say if this will go through without any errors, but if it does, I am more than happy to help. This is not the very best solution if you only go for the way i presented now so please do not use this as you solution, but more as a way to get started. Also you will not really learn anything of it if we just gave you the answer
Edit:
Another thing to mark is that using setInterval for games and graphical projects may be a bad idea since JavaScript is single threaded. A better solution is using requestAnimationFrame()
It would look something like this
function mainLoop() {
update();
draw();
requestAnimationFrame(mainLoop);
}
// Start things off
requestAnimationFrame(mainLoop);
I am making a game. I want the player to not go outside the circular game region. the player should not cross the red circular line. It should remain inside and could move along the boundary.
I have written a simple function for collision detection between circles. I have found a bug in it too. I am getting a console.log() message of outside even if I am inside the game area.
It's happening when the player is at [x < 0]. Help me out please.
var Game = (function(window) {
var canvas = document.getElementById("game"),
ctx = canvas.getContext("2d");
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
var ROCK = "rock",
PAPER = "paper",
SCISSOR = "scissor";
var BG_IMAGE = document.getElementById("bg");
// this is the game area Radius
var GAME_R = 500;
var offsetX = 0,
offsetY = 0;
var player;
// circle collision detection
function checkCollision(x1, y1, r1, x2, y2, r2) {
var x = x1-x2;
var y = y1-y2;
var d = Math.hypot(x, y);
return d < r1 + r2;
}
function start() {
player = new Entity();
addEventListener("mousemove", function(e) {
var angle = Math.atan2(e.clientY - SCREEN_HEIGHT/2, e.clientX - SCREEN_WIDTH/2);
player.setAngle(angle);
}, true);
animLoop();
}
function update() {
offsetX = player.x - SCREEN_WIDTH/2;
offsetY = player.y - SCREEN_HEIGHT/2;
player.update();
}
function draw() {
ctx.save();
ctx.translate(-offsetX, -offsetY);
// bg
ctx.fillStyle = ctx.createPattern(BG_IMAGE, "repeat");
ctx.fillRect(offsetX, offsetY, SCREEN_WIDTH, SCREEN_HEIGHT);
// game area border
ctx.beginPath();
ctx.arc(0, 0, GAME_R, 0, Math.PI * 2);
ctx.closePath();
ctx.lineWidth = 5;
ctx.strokeStyle = "red";
ctx.stroke();
// player
player.draw();
ctx.restore();
}
function gameLoop() {
update();
// here
if(checkCollision(player.x, player.y, player.x, 0, 0, GAME_R)) {
console.log("inside");
} else {
console.log("outside");
}
draw();
}
function animLoop() {
window.requestAnimationFrame(animLoop);
gameLoop();
}
// player
function Entity() {
var self = {
x: 0,
y: 0,
r: 50,
entityType: PAPER,
angle: 0,
speed: 5
}
self.setSpeed = function(speed) {
this.speed = speed;
}
self.setAngle = function(angle) {
this.angle = angle;
}
self.update = function() {
this.x += this.speed * Math.cos(this.angle);
this.y += this.speed * Math.sin(this.angle);
}
self.draw = function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = "grey";
ctx.fill();
ctx.fillStyle = "#fff";
ctx.font = "30px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(this.entityType, this.x, this.y);
}
return self;
}
start();
})(window);
<canvas id="game"></canvas>
<div style="display: none;">
<img id="bg" src="https://i.imgur.com/9qjEwiz.png">
</div>
This will check if the inner Circle inside the outer
function checkCollision(cxInner, cyInner, rInner, cxOuter, cyOuter, rOuter) {
return Math.sqrt(Math.pow(cxInner-cxOuter, 2) + Math.pow(cyInner-cyOuter, 2)) < rOuter - rInner;
}
complete code:
var Game = (function(window) {
var canvas = document.getElementById("game"),
ctx = canvas.getContext("2d");
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
var ROCK = "rock",
PAPER = "paper",
SCISSOR = "scissor";
var BG_IMAGE = document.getElementById("bg");
// this is the game area Radius
var GAME_R = 500;
var offsetX = 0,
offsetY = 0;
var player;
// circle collision detection
function checkCollision(cxInner, cyInner, rInner, cxOuter, cyOuter, rOuter) {
return Math.sqrt(Math.pow(cxInner-cxOuter, 2) + Math.pow(cyInner-cyOuter, 2)) < rOuter - rInner;
}
function start() {
player = new Entity();
addEventListener("mousemove", function(e) {
var angle = Math.atan2(e.clientY - SCREEN_HEIGHT/2, e.clientX - SCREEN_WIDTH/2);
player.setAngle(angle);
}, true);
animLoop();
}
function update() {
offsetX = player.x - SCREEN_WIDTH/2;
offsetY = player.y - SCREEN_HEIGHT/2;
player.update();
}
function draw() {
ctx.save();
ctx.translate(-offsetX, -offsetY);
// bg
ctx.fillStyle = ctx.createPattern(BG_IMAGE, "repeat");
ctx.fillRect(offsetX, offsetY, SCREEN_WIDTH, SCREEN_HEIGHT);
// game area border
ctx.beginPath();
ctx.arc(0, 0, GAME_R, 0, Math.PI * 2);
ctx.closePath();
ctx.lineWidth = 5;
ctx.strokeStyle = "red";
ctx.stroke();
// player
player.draw();
ctx.restore();
}
function gameLoop() {
update();
// here
if(!checkCollision(player.x, player.y, player.r, 0, 0, GAME_R)) {
player.back();
}
draw();
}
function animLoop() {
window.requestAnimationFrame(animLoop);
gameLoop();
}
// player
function Entity() {
var self = {
x: 0,
y: 0,
r: 50,
entityType: PAPER,
angle: 0,
speed: 5
};
self.setSpeed = function(speed) {
this.speed = speed;
};
self.setAngle = function(angle) {
this.angle = angle;
};
self.update = function() {
this.x += this.speed * Math.cos(this.angle);
this.y += this.speed * Math.sin(this.angle);
};
self.back = function() {
this.x -= this.speed * Math.cos(this.angle);
this.y -= this.speed * Math.sin(this.angle);
};
self.draw = function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = "grey";
ctx.fill();
ctx.fillStyle = "#fff";
ctx.font = "30px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(this.entityType, this.x, this.y);
};
return self;
}
start();
})(window);
<canvas id="game"></canvas>
<div style="display: none;">
<img id="bg" src="https://i.imgur.com/9qjEwiz.png">
</div>
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>
I am trying to design an animated word web similar to the one found here. The goal is to have a center word bubble that is fixed in one location and to have other word bubbles around it that are moving around.
I was hoping to use the bubble chart from chartjs as a base and remove the legend, gridlines, axies, etc. to get the bubbles. However except for the tooltips included in the documentation, I have not been able to find a way to add text to one of the bubbles. Is there a way to add the text inside the bubble so that it is always visible? Or can anyone point me in the direction of a better library to accomplish this?
I am crazy but i coded that in Canvas. Was bored haha. Test it by pasting to a new html file :D You can play with parametrs, colors, shapes and new bubbles with new texts. Have fun!
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
var r = 0;
var bubbles = [];
bubbles.push(new Bubble(canvas.width/2, canvas.height/2, "Apple", "rgb(0,0,120)", 30, 20));
bubbles.push(new Bubble(canvas.width/2 + 100, canvas.height/2, "Store", "rgb(0,0,200)", 30, 20));
bubbles.push(new Bubble(canvas.width/2 - 100, canvas.height/2, "Ipad", "rgb(0,0,200)", 30, 20));
bubbles.push(new Bubble(canvas.width/2, canvas.height/2 + 100, "iMac", "rgb(120,0,0)", 30, 20));
setInterval(function(){update();}, 20);
function update()
{
ctx.clearRect(0,0,canvas.width, canvas.height);
for (var i=bubbles.length-1; i>=0; i--)
{
bubbles[i].update();
}
}
function Vector(x,y)
{
this.x = x;
this.y = y;
this.startX = x;
this.startY = y;
this.add = function (v)
{
this.x += v.x;
this.y += v.y;
}
this.mult = function(p)
{
this.x *= p;
this.y *= p;
}
this.rotate = function(ang)
{
this.x = (this.startX - bubbles[0].position.x) * Math.cos(ang)
- (this.startY - bubbles[0].position.y) * Math.sin(ang)
+ bubbles[0].position.x;
this.y = (this.startX - bubbles[0].position.x) * Math.sin(ang)
+ (this.startY - bubbles[0].position.y) * Math.cos(ang)
+ bubbles[0].position.y;
}
}
function Bubble(x,y,text, color, width, height)
{
this.position = new Vector(x,y);
this.velocity = new Vector(0,0);
this.acceleration = new Vector(0,0);
this.text = text;
this.color = color;
this.width = width;
this.height = height;
this.draw = function()
{ ctx.beginPath();
if(this.text != "Apple")
{
ctx.lineTo(bubbles[0].position.x, bubbles[0].position.y);
}
ctx.font = "bold 13pt Arial";
ctx.fillStyle = this.color;
ctx.strokeStyle = this.color;
ctx.arc(this.position.x,this.position.y, this.width, 0, 2 * Math.PI, false);
ctx.fill();
ctx.fillStyle = "black";
ctx.stroke();
ctx.fillText(this.text, this.position.x - this.width / 2 - 6, this.position.y + this.height / 4);
}
this.update = function()
{
if(this.text != "Apple")
{
r--;
if (r < -1440)
r = 1;
this.position.rotate( Math.PI / 720 * r);
}
this.draw();
}
}
</script>
</body>
</html>
I have a ball that drops from cursor location, and redrops when the cursor is moved to another location. I am trying get a new ball to drop every time I click the mouse. I tried:
canvas.addEventListener('click', function(event) {
ball.draw();
});
But it doesn't seem to do anything. Is there some way to draw a NEW ball on click instead of just redrawing the same ball over and over again?
Here's the rest of the code:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
var W = window.innerWidth,
H = window.innerHeight;
var running = false;
canvas.height = H; canvas.width = W;
var ball = {},
gravity = .5,
bounceFactor = .7;
ball = {
x: W,
y: H,
radius: 15,
color: "BLUE",
vx: 0,
vy: 1,
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
};
function clearCanvas() {
ctx.clearRect(0, 0, W, H);
}
function update() {
clearCanvas();
ball.draw();
ball.y += ball.vy;
ball.vy += gravity;
if(ball.y + ball.radius > H) {
ball.y = H - ball.radius;
ball.vy *= -bounceFactor;
}
}
canvas.addEventListener("mousemove", function(e){
ball.x = e.clientX;
ball.y = e.clientY;
ball.draw();
});
setInterval(update, 1000/60);
ball.draw();
Just rewrite the ball object so it becomes instantiate-able:
function Ball(W, H) {
this.x = W;
this.y = H;
this.radius = 15;
this.color = "blue";
this.vx = 0;
this.vy = 1;
}
Move the methods to prototypes (this will make them shareable across instances). In addition, add an update method so you can localize updates:
Ball.prototype = {
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
},
update: function() {
this.y += this.vy;
this.vy += gravity;
if(this.y + this.radius > H) {
this.y = H - this.radius;
this.vy *= -bounceFactor;
}
}
};
In the click event (consider renaming the array to plural form - it's easier to distinguish that way. In your code you're overriding the "array" (which is defined as an object) with a single ball object later):
var balls = []; // define an array to hold the balls
For the click event to use the x and y position of the mouse as start point for the ball, we first need to adjust it as it is relative to client window and not the canvas. To do this we get the absolute position of canvas and subtract it from the client coordinates:
canvas.addEventListener('click', function(event) {
var rect = this.getBoundingClientRect(), // adjust mouse position
x = event.clientX - rect.left,
y = event.clientY - rect.top;
balls.push(new Ball(x, y)); // add a new instance
});
Now in the main animation loop just iterate over the array. Every time there is a new ball it will be considered and updated - we just let the loop run until some condition is met (not shown):
function update() {
clearCanvas();
for(var i = 0, ball; ball = balls[i]; i++) {
ball.draw(); // this will draw current ball
ball.update(); // this will update its position
}
requestAnimationFrame();
}
Live example
If you put these together you will get:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
W = canvas.width, // simplified for demo
H = canvas.height,
gravity = .5,
bounceFactor = .7;
function Ball(x, y) {
this.x = x;
this.y = y;
this.radius = 15;
this.color = "blue";
this.vx = 0;
this.vy = 1
}
Ball.prototype = {
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
},
update: function() {
this.y += this.vy;
this.vy += gravity; // todo: limit bounce at some point or this value will be added
if (this.y + this.radius > H) {
this.y = H - this.radius;
this.vy *= -bounceFactor;
}
}
};
function clearCanvas() {
ctx.clearRect(0, 0, W, H);
}
var balls = []; // define an array to hold the balls
canvas.addEventListener('click', function(event) {
var rect = this.getBoundingClientRect(), // adjust mouse position
x = event.clientX - rect.left,
y = event.clientY - rect.top;
balls.push(new Ball(x, y)); // add a new instance
});
(function update() {
clearCanvas();
for (var i = 0, ball; ball = balls[i]; i++) {
ball.draw(); // this will draw current ball
ball.update(); // this will update its position
}
requestAnimationFrame(update);
})();
canvas {background:#aaa}
<canvas id="canvas" width=600 height=400></canvas>