Related
I have multiple elipses on the canvas in javascript and I want all of them to bounce off each other. I tried using the distance formula and then changing the x and y direction of the ball when the distance is less than the ball radius *2.
This worked well for one ball, but it doesn't work so well for many balls as it often leads to the dreaded 'bounce loop' depicted Here
To remedy this issue, I resolved to change the way the balls bounce depending on where they collide with each other to avoid the bounce loop and to make the game closer to real life physics.
If there's a side to side collision, I want to reverse the x direction of both balls and if there's a top to bottom collision, I want to reverse the y direction of both balls.
So, I calculated all the points, for example, between 45 degrees and 135 degrees that correlate with a degree (that's 90 points) and compared them to all 90 point between 225 degrees and 315 degrees and vice versa.
If the distance between any of the points on the edge of the circle and all the other balls center point is less than the radius, I want the Y direction of both balls to be reversed.
I repeated the same process for 135 degrees and 225 degress to 315 degrees and 405 degrees (equivalent to 45) and reversed the X direction of both balls.
As of right now, I think the balls should bounce off each other how I want them to, but they just don't. They bounce off each other's sides and tops, bottoms, and occasionally at angles, but they tend to dip inside of each other and then change direction. Here is a video of the output.
Below is the code comparing top to bottom:
// radius is the same for all the balls and is at 25.
let ballToBallDistance = (x1, y1, x2, y2) => {
return Math.sqrt((Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)));
}
const ballCollisionY = (start, end) => {
for (let i = start; i <= end; i++) {
return ballObjects[0].ballRadius * Math.sin((i * Math.PI / 180));
}
}
const ballCollisionX = (start, end) => {
for (let i = start; i <= end; i++) {
return ballObjects[0].ballRadius * Math.cos((i * Math.PI / 180));
}
}
const upperYBall = {
bounceTopBottom() {
let n = 0;
for (let i = 0; i < ballObjects.length; i++) {
if (ballObjects.length == 1) {
return;
}
if (n == i) {
continue;
}
let yUpXPoint = ballObjects[n].ballXPos - ballCollisionX(45, 135);
let yUpYPoint = ballObjects[n].ballYPos - ballCollisionY(45, 135);
let centerBallX = ballObjects[i].ballXPos;
let centerBallY = ballObjects[i].ballYPos;
let pointDistance = ballToBallDistance(yUpXPoint, yUpYPoint, centerBallX, centerBallY);
if (pointDistance <= 25) {
ballObjects[n].ballMotionY = ballObjects[n].ballMotionY * -1;
}
if (i == ballObjects.length - 1) {
++n;
i = -1;
continue;
}
}
}
}
const lowerYBall = {
bounceBottomTop() {
let n = 0;
for (let i = 0; i < ballObjects.length; i++) {
if (ballObjects.length == 1) {
return;
}
if (n == i) {
continue;
}
let yDownXPoint = ballObjects[n].ballXPos - ballCollisionX(225, 315);
let yDownYPoint = ballObjects[n].ballYPos - ballCollisionY(225, 315);
let centerBallX = ballObjects[i].ballXPos;
let centerBallY = ballObjects[i].ballYPos;
let pointDistance = ballToBallDistance(yDownXPoint, yDownYPoint, centerBallX, centerBallY);
if (pointDistance <= 25) {
ballObjects[n].ballMotionY = ballObjects[n].ballMotionY * -1;
}
if (i == ballObjects.length - 1) {
++n;
i = -1;
continue;
}
}
}
}
I've been stuck on this feature for two weeks. If anyone has any insight as to what I am doing wrong and perhaps a solution to achieve the desired result, that would be very much appreciated.
I propose you switch from the special-case coding to a more general approach.
When two balls collide:
Calculate the collision normal (angle)
Calculate the new velocity based on the previous velocity and the normal
Reposition the balls so that they do not overlap anymore, preventing the 'bounce loop'.
You will need:
A method to calculate the angle between two balls:
function ballToBallAngle(ball1,ball2) {
return Math.atan2(ball2.y-ball1.y,ball2.x-ball1.x)
}
A method to derive the normal vector from a angle:
function calcNormalFromAngle(angle){
return [
Math.cos(angle),
Math.sin(angle)
]
}
A method to calculate the dot product of two vectors:
function dotproduct (a, b){
return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n)
}
Finally a way to calculate the bounce angle. Read this, it describes it perfectly.
So to put it together, see the snippet below:
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
let balls = [
{x:40,y:40,radius:25,vx:4,vy:3},
{x:300,y:300,radius:50,vx:-2,vy:-3},
{x:100,y:220,radius:25,vx:4,vy:-3},
{x:400,y:400,radius:50,vx:-1,vy:-3},
{x:200,y:400,radius:32,vx:2,vy:-3}
]
function tick() {
balls.forEach((ball, index) => {
ball.x += ball.vx
ball.y += ball.vy
//check for x bounds collision
if (ball.x - ball.radius < 0) {
bounceBall(ball, Math.PI)
ball.x = ball.radius
} else if (ball.x + ball.radius > 500) {
bounceBall(ball, 0)
ball.x = 500 - ball.radius
}
//check for y bounds collision
if (ball.y - ball.radius < 0) {
bounceBall(ball, Math.PI / 2)
ball.y = ball.radius
} else if (ball.y + ball.radius > 500) {
bounceBall(ball, -Math.PI / 2)
ball.y = 500 - ball.radius
}
balls.forEach((other_ball, other_index) => {
if (index == other_index)
return
// how many px the balls intersect
let intersection = ball.radius + other_ball.radius - ballToBallDistance(ball, other_ball)
// if its greater than 0, they must be colliding
if (intersection > 0) {
let angle = ballToBallAngle(ball, other_ball)
let normal = calcNormalFromAngle(angle)
bounceBall(ball, angle)
bounceBall(other_ball, angle + Math.PI)
// set positions so that they are not overlapping anymore
ball.x -= normal[0] * intersection / 2
ball.y -= normal[1] * intersection / 2
other_ball.x += normal[0] * intersection / 2
other_ball.y += normal[1] * intersection / 2
}
})
})
render()
requestAnimationFrame(tick)
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
balls.forEach(ball => {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);
ctx.stroke();
})
}
function bounceBall(ball, angle) {
let normal = calcNormalFromAngle(angle)
let velocity = [ball.vx, ball.vy]
let ul = dotproduct(velocity, normal) / dotproduct(normal, normal)
let u = [
normal[0] * ul,
normal[1] * ul
]
let w = [
velocity[0] - u[0],
velocity[1] - u[1]
]
let new_velocity = [
w[0] - u[0],
w[1] - u[1]
]
ball.vx = new_velocity[0]
ball.vy = new_velocity[1]
}
function dotproduct(a, b) {
return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n)
}
function ballToBallDistance(ball1, ball2) {
return Math.sqrt((Math.pow(ball2.x - ball1.x, 2) + Math.pow(ball2.y - ball1.y, 2)));
}
function ballToBallAngle(ball1, ball2) {
return Math.atan2(ball2.y - ball1.y, ball2.x - ball1.x)
}
function calcNormalFromAngle(angle) {
return [
Math.cos(angle),
Math.sin(angle)
]
}
tick();
body{
background-color: #eee;
}
canvas{
background-color: white;
}
<canvas width="500" height="500"></canvas>
I am looking for a "random-dot kinematograms" implementation in javascript/HTML (preferably in ReactJS) that I can use in web-based experiments.
Basically, dots moving (arrows indicate motion direction) within a visual field (the circle canvas). Signal dots (shown as filled circles) all move in the same direction, while the noise dots can move in random directions. In the actual display, the correlated and uncorrelated dots in a frame appear the same; the filled and open dots are used in the figure only to explain the principle.
Where can I find an implementation for something like this, where the user can specify the direction using a mouse or slider? Or how would one approach implementing this task in ReactJS? (new to javascript, so tips would be highly appreciated).
I've build you simple canvas based kinematogram that you should extend to your needs. What I've done so far is:
added bucket of balls (total:100) = 50 black, 50 white
blacks are going in strait direction
whites are going randomly and they also bounce from walls
whites are going in random speed
blacks are going in constant speed
To adapt blacks directions you could start by looking in this answer to determine the mouse direction and patch my balls.push loop for that.
To be able to vary balls ratio, I'd add input field somewhere on page and replace my hardcoded noise variable.. something like:
<input type="text" id="t" />
and in javascript pick it like:
var t = document.getElementById("t");
t.addEventListener('keyup', function(ev){ /* update value */ }, false);
hope this helps you in your research and I do encourage you to take a look into the code that I'm posting so try to learn it and extend it :)
;(function() {
'use strict';
var c = document.getElementById('c');
var t = document.getElementById('t');
var ctx = c.getContext('2d');
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;
// current dots
var balls=[];
var total = 100;
var noise = 50; // here we could pick the value from user input
var bounce = -1;
for(var i=0 ; i<total ; i++){
balls.push({
x : Math.random() * w,
y : Math.random() * h,
vx : ( i < noise ) ? (Math.random() * (2.5 - 1 + 1) + 1) : 2,
vy : ( i < noise ) ? (Math.random() * (2.5 - 1 + 1) + 1) : 2,
})
}
// draw all balls each frame
function draw() {
ctx.clearRect(0, 0, w, h);
var j, dot;
for(j = 0; j < total; j++) {
dot = balls[j];
ctx.beginPath();
ctx.arc(dot.x, dot.y, 4, 0, Math.PI * 2, false);
ctx.fillStyle = (j > noise) ? "rgb(0,0,0)" : "#fff";
ctx.fill();
ctx.strokeStyle = 'black';
(j < noise) ? ctx.stroke() : '';
}
}
// movement function
function update(){
var i,dot;
for( i=0 ; i< total ; i++){
dot = balls[i];
dot.x += dot.vx;
dot.y += dot.vy;
// if ball is white, bounce it
if( i < noise){
if(dot.x > w){
dot.x = w;
dot.vx *= bounce;
}else if(dot.x <0){
dot.x = 0;
dot.vx *= bounce;
}
if(dot.y > h){
dot.y = h;
dot.vy *= bounce;
}else if(dot.y<0){
dot.y = 0;
dot.vy *= bounce;
}
// if ball is black do not bounce
} else {
if(dot.x > w){
dot.x = 0;
}else if(dot.x <0){
dot.x = w;
}
if(dot.y > h){
dot.y = 0;
}else if(dot.y<0){
dot.y = h;
}
}
}
}
// loop the animation
requestAnimationFrame(function loop() {
requestAnimationFrame(loop);
update();
draw();
});
})();
html,
body {
padding: 0;
margin: 0;
}
canvas {
display: block;
background: white;
}
<canvas id="c"></canvas>
I'm trying to learn HTML5 and found a very simple particle system wich i modded a bit.
I would like to create a line, between particles, if the distance between the particles is within the range 0-20.
What I currently have draws a line between every particle, no matter the distance.
This is where I try to check the distance, but I can't figure out how to do this. Would appreciate any help and explanations. Thanks in advance.
// This particle
var p = particles[t];
// Check position distance to other particles
for (var q = 0; q < particles.length; q++) {
if (particles[q].x - p.x < line_distance || p.x - particles[q].x < line_distance) {
ctx.beginPath();
ctx.lineWidth = .1;
ctx.strokeStyle = '#fff';
ctx.moveTo(p.x, p.y);
ctx.lineTo(particles[q].x, particles[q].y);
ctx.stroke();
}
}
// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Set fullscreen
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
// Options
var num =30; // Number of particles to draw
var size = 3; // Particle size
var color = '#fff'; // Particle color
var min_speed = 1; // Particle min speed
var max_speed = 3; // Particle max speed
var line_distance = 20; // This is the max distance between two particles
// if we want to draw a line between them
// Particles array
var particles = [];
for (var i = 0; i < num; i++) {
particles.push(
new create_particle()
);
}
// Lets animate the particle
function draw() {
// Background
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Lets draw particles from the array now
for (var t = 0; t < particles.length; t++) {
// This particle
var p = particles[t];
for (var q = 0; q < particles.length; q++) {
// Check position distance
if (particles[q].x - p.x < line_distance || p.x - particles[q].x < line_distance) {
ctx.beginPath();
ctx.lineWidth = .1;
ctx.strokeStyle = '#fff';
ctx.moveTo(p.x, p.y);
ctx.lineTo(particles[q].x, particles[q].y);
ctx.stroke();
}
}
// Color
ctx.fillStyle = color;
// Circle path
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, Math.PI * 2, false);
ctx.fill();
// Lets use the velocity now
p.x += p.vx;
p.y += p.vy;
// If there is only 1 particle
// show X, Y, and velocity
if (num === 1) {
ctx.fillText('Y:'+ p.y, 20, 20);
ctx.fillText('X:'+ p.x, 20, 40);
ctx.fillText('YV:'+ p.vy, 20, 60);
ctx.fillText('XV:'+ p.vx, 20, 80);
}
// To prevent the balls from moving out of the canvas
if (p.x < size) p.vx*= (p.vx / -p.vx);
if (p.y < size) p.vy*= (p.vy / -p.vy);
if (p.x > canvas.width - size) p.vx*= (-p.vx / p.vx);
if (p.y > canvas.height - size) p.vy*= (-p.vy / p.vy);
}
// Loop
requestAnimationFrame(draw);
}
// Function for particle creation
function create_particle() {
// Random position
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
// Velocity
this.vx = random_int_between(min_speed, max_speed);
this.vy = random_int_between(min_speed, max_speed);
// Color & Size
this.color = color;
this.radius = size;
}
// Random number between (used for speed)
function random_int_between(min, max) {
return Math.floor(Math.random() * max) + min;
}
draw();
<canvas id="canvas"></canvas>
N body Particle systems
As this is an N body case and no one said anything about CPU load.
CPU Load
Particle systems can quickly bog down a CPU in an overload of processing. This is particularly true when you are testing each particle against the other. As particle systems are almost always for realtime graphics ineffective coding can destroy the whole animation.
Do nothing not needed
First as you are only looking for a threshold distance you can optimise the calculations by not continuing to calculate as soon as you know that there is a fail in the test.
So set up the threshold distance
var dist = 20;
var distSq = dist * dist; // No need to square this inside loops
Then in the loop as you calculate test and continue. Assuming p1 and p2 are particles
x = p2.x-p1.x; // do x first
if((x *= x) < distSq){ // does it pass?? if not you have saved calculating y
y = p2.y-p1.y; // now do y as you know x is within distance
if(x + (y * y) < distSq){ // now you know you are within 20
// draw the line
Assuming only 1/6 will pass and 1/3 come close you save over half the CPU load. You will also notice that I don't use the CPU heavy sqrt of the distance. There is no need as there is a one to one match between a number and the square of a number. If the square root of a number is less than the distance so will the square of the number be less than the square of the distance.
N body Squared
Never do a N body sim with two for loops like this.
for(i = 0; i < particles.length; i ++){
for(j = 0; j < particles.length; j ++){
// you will test all i for j and all j for i but half of them are identical
// and the square root of the number are self to self
This hurts me just to look at as the solution is so so simple.
Assuming you have 100 particles at 60 frames a second you are doing 60 * 100 * 100 comparisons a second (600,000) for 100 particles. Thats is a total waste of CPU time.
Never do something twice, or that you know the answer to.
To improve the for loops and avoid testing distances you already know and testing how far each particle is from itself
var len = particles.length; // move the length out as it can be expensive
// and pointless as the value does not change;
for(i = 0; i < len; i ++){
for(j = i + 1; j < len; j ++){
// Now you only test each particle against each other once rather than twice
Thus with just a few simple characters (for(j = 0 becomes for(j = i + 1) you more than half the CPU load, from 600,000 comparisons down to less than 300,000
The human eye is easy to fool
Fooling the eye is the best way to get extra performance from your animations.
This is a visual effect and the human eye does not see pixels nor does it it see individual frames at 1/60th a second, but it does see a drop in frame rate. Creating a complex particle system can an excellent FX but if it drops the frame rate the benefit is lost. Take advantage of the fact that pixels are to small and 1/20th of a second is way beyond the human ability to find error is the best way to optimise FXs and add more bang per CPU tick.
The demo below has two particle sims. 100 points each. Any points that come within 49 pixels have a line drawn between them. One does all the stuff I demonstrated above the other sacrifices a little memory and a lot off acuracy and only calculates the distances between 1/3rd of the points every frame. As the max speed can be close to half the line length a frame, skipping 2 frames can make a line twice as long or two points be too close without a line. There is a massive CPU saving in doing this, but you can not pick which is which.
Click on which sim you think is skipping points to find out which is which.
var canvas = document.createElement("canvas");
canvas.width= 540;
canvas.height = 270;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
mouseX = 0;
mouseB = false;
function clickedFun(event){
mouseX = event.clientX
mouseB = true;
}
canvas.addEventListener("click",clickedFun);
var w = 250;
var h = 250;
var wh = w/2;
var hh = h/2;
var speedMax = 5;
var partSize = 2;
var count = 100
var grav = 1;
var pA1 = []; // particle arrays
var pA2 = [];
var PI2 = Math.PI * 2;
// populate particle arrays
for(var i = 0; i < count; i += 1){
// dumb list
pA1.push({
x : Math.random() * w,
y : Math.random() * h,
dx : (Math.random() -0.5)*speedMax,
dy : (Math.random() -0.5)*speedMax,
})
// smart list
pA2.push({
x : Math.random() * w,
y : Math.random() * h,
dx : (Math.random() -0.5)*speedMax,
dy : (Math.random() -0.5)*speedMax,
links : [], // add some memory
})
for(var j = 0; j < count; j += 1){
pA2[i].links[i] = false; // set memory to no links
}
}
// move and draw the dots. Just a simple gravity sim
function drawAll(parts){
var x,y,d;
var i = 0;
var len = parts.length;
var p;
ctx.beginPath();
for(;i < len; i++){
p = parts[i];
x = wh-p.x;
y = hh-p.y;
d = x*x + y*y;
x *= grav / d;
y *= grav / d;
p.dx += x;
p.dy += y;
p.x += p.dx;
p.y += p.dy;
if(p.x <= 0){
p.dx -= p.dx/2;
p.x = 1;
}else
if(p.x >= w){
p.dx -= p.dx/2;
p.x = w-1;
}
if(p.y <= 0){
p.dy -= p.dy/2;
p.y = 1;
}else
if(p.y >= h){
p.dy -= p.dy/2;
p.y = w-1;
}
ctx.moveTo(p.x+partSize,p.y)
ctx.arc(p.x,p.y,partSize,0,PI2)
}
ctx.fill();
}
//Old style line test. If two particles are less than dist apart
// draw a line between them
function linesBetween(parts,dist){
var distSq = dist*dist;
var x,y,d,j;
var i = 0;
var len = parts.length;
var p,p1;
ctx.beginPath();
for(; i < len; i ++){
p = parts[i];
for(j = i + 1; j < len; j ++){
p1 = parts[j];
x = p1.x-p.x;
if((x *= x) < distSq){
y = p1.y-p.y;
if(x + (y*y) < distSq){
ctx.moveTo(p.x,p.y);
ctx.lineTo(p1.x,p1.y)
}
}
}
}
ctx.stroke();
}
var counter = 0;// counter for multyplexing
// Fast version. As the eye can not posible see the differance of
// of 4 pixels over 1/30th of a second only caculate evey third
// particls
function linesBetweenFast(parts,dist){
var distSq = dist*dist;
var x,y,d,j,l;
var i = 0;
counter += 1;
var cc = counter % 3;
var wr,re;
var len = parts.length;
var p,p1;
var lineSet
ctx.beginPath();
for(; i < len; i ++){
p = parts[i];
l = p.links;
for(j = i + 1; j < len; j += 1){
p1 = parts[j];
if((j + cc)%3 === 0){ // only every third particle
lineSet = false; // test for diferance default to fail
x = p1.x-p.x;
if((x *= x) < distSq){
y = p1.y-p.y;
if(x + (y*y) < distSq){
lineSet = true; // yes this needs a line
}
}
l[j] = lineSet; // flag it as needing a line
}
if(l[j]){ // draw the line if needed
ctx.moveTo(p.x,p.y);
ctx.lineTo(p1.x,p1.y);
}
}
}
ctx.stroke();
}
var drawLines; // to hold the function that draws lines
// set where the screens are drawn
var left = 10;
var right = 10 * 2 + w;
// Now to not cheat swap half the time
if(Math.random() < 0.5){
right = 10;
left = 10 * 2 + w;
}
// draws a screem
var doScreen = function(parts){
ctx.fillStyle = "red"
drawAll(parts);
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
drawLines(parts,49);
}
var guess = ""
var guessPos;
var gueesCol;
ctx.font = "40px Arial Black";
ctx.textAlign = "center";
ctx.textBasline = "middle"
var timer = 0;
function update(){
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.setTransform(1,0,0,1,left,10);
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.strokeRect(0,0,w,h);
drawLines = linesBetween;
doScreen(pA1)
ctx.setTransform(1,0,0,1,right,10);
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.strokeRect(0,0,w,h);
drawLines = linesBetweenFast
doScreen(pA2)
if(mouseB){
if((mouseX > 270 && right >250) ||
(mouseX < 250 && right < 250)){
guess = "CORRECT!"
guessPos = right;
guessCol = "Green";
}else{
guess = "WRONG"
guessPos = left
guessCol = "Red";
}
timer = 120;
mouseB = false;
}else
if(timer > 0){
timer -= 1;
if(timer > 30){
ctx.setTransform(1,0,0,1,guessPos,10);
ctx.font = "40px Arial Black";
ctx.fillStyle = guessCol;
ctx.fillText(guess,w/2,h/2);
}else{
if(Math.random() < 0.5){
right = 10;
left = 10 * 2 + w;
}else{
left = 10;
right = 10 * 2 + w;
}
}
}else{
ctx.setTransform(1,0,0,1,0,0);
ctx.font = "16px Arial Black";
var tw = ctx.measureText("Click which sim skips 2/3rd of").width +30;
ctx.beginPath();
ctx.fillStyle = "#DDD";
ctx.strokeStyle = "Red";
ctx.rect(270-tw/2,-5,tw,40);
ctx.stroke();
ctx.fill();
ctx.fillStyle = "blue";
ctx.fillText("Click which sim skips 2/3rd of",270,15) ;
ctx.fillText("particle tests every frame",270,30) ;
}
requestAnimationFrame(update);
}
update();
This is just your test which is wrong.
a-b < c || b-a < c is always true (except if a-b == c)
replace by abs(a-b) < c if you want to test "x" distance, or by using the above formula if you want an euclidian distance
// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Set fullscreen
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
// Options
var num =30; // Number of particles to draw
var size = 3; // Particle size
var color = '#fff'; // Particle color
var min_speed = 1; // Particle min speed
var max_speed = 3; // Particle max speed
var line_distance = 20; // This is the max distance between two particles
// if we want to draw a line between them
// Particles array
var particles = [];
for (var i = 0; i < num; i++) {
particles.push(
new create_particle()
);
}
// Lets animate the particle
function draw() {
// Background
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Lets draw particles from the array now
for (var t = 0; t < particles.length; t++) {
// This particle
var p = particles[t];
for (var q = 0; q < particles.length; q++) {
// Check position distance
if (Math.abs(particles[q].x - p.x) < line_distance) {
ctx.beginPath();
ctx.lineWidth = .1;
ctx.strokeStyle = '#fff';
ctx.moveTo(p.x, p.y);
ctx.lineTo(particles[q].x, particles[q].y);
ctx.stroke();
}
}
// Color
ctx.fillStyle = color;
// Circle path
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, Math.PI * 2, false);
ctx.fill();
// Lets use the velocity now
p.x += p.vx;
p.y += p.vy;
// If there is only 1 particle
// show X, Y, and velocity
if (num === 1) {
ctx.fillText('Y:'+ p.y, 20, 20);
ctx.fillText('X:'+ p.x, 20, 40);
ctx.fillText('YV:'+ p.vy, 20, 60);
ctx.fillText('XV:'+ p.vx, 20, 80);
}
// To prevent the balls from moving out of the canvas
if (p.x < size) p.vx*= (p.vx / -p.vx);
if (p.y < size) p.vy*= (p.vy / -p.vy);
if (p.x > canvas.width - size) p.vx*= (-p.vx / p.vx);
if (p.y > canvas.height - size) p.vy*= (-p.vy / p.vy);
}
// Loop
requestAnimationFrame(draw);
}
// Function for particle creation
function create_particle() {
// Random position
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
// Velocity
this.vx = random_int_between(min_speed, max_speed);
this.vy = random_int_between(min_speed, max_speed);
// Color & Size
this.color = color;
this.radius = size;
}
// Random number between (used for speed)
function random_int_between(min, max) {
return Math.floor(Math.random() * (max-min)) + min;
}
draw();
<canvas id="canvas" width="300" height="300"></canvas>
// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Set fullscreen
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
// Options
var num =30; // Number of particles to draw
var size = 3; // Particle size
var color = '#fff'; // Particle color
var min_speed = 1; // Particle min speed
var max_speed = 3; // Particle max speed
var line_distance = 20; // This is the max distance between two particles
// if we want to draw a line between them
// Particles array
var particles = [];
for (var i = 0; i < num; i++) {
particles.push(
new create_particle()
);
}
// Lets animate the particle
function draw() {
// Background
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Lets draw particles from the array now
for (var t = 0; t < particles.length; t++) {
// This particle
var p = particles[t];
for (var q = 0; q < particles.length; q++) {
// Check position distance
if (particles[q].x - p.x < line_distance || p.x - particles[q].x < line_distance) {
ctx.beginPath();
ctx.lineWidth = .1;
ctx.strokeStyle = '#fff';
ctx.moveTo(p.x, p.y);
ctx.lineTo(particles[q].x, particles[q].y);
ctx.stroke();
}
}
// Color
ctx.fillStyle = color;
// Circle path
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, Math.PI * 2, false);
ctx.fill();
// Lets use the velocity now
p.x += p.vx;
p.y += p.vy;
// If there is only 1 particle
// show X, Y, and velocity
if (num === 1) {
ctx.fillText('Y:'+ p.y, 20, 20);
ctx.fillText('X:'+ p.x, 20, 40);
ctx.fillText('YV:'+ p.vy, 20, 60);
ctx.fillText('XV:'+ p.vx, 20, 80);
}
// To prevent the balls from moving out of the canvas
if (p.x < size) p.vx*= (p.vx / -p.vx);
if (p.y < size) p.vy*= (p.vy / -p.vy);
if (p.x > canvas.width - size) p.vx*= (-p.vx / p.vx);
if (p.y > canvas.height - size) p.vy*= (-p.vy / p.vy);
}
// Loop
requestAnimationFrame(draw);
}
// Function for particle creation
function create_particle() {
// Random position
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
// Velocity
this.vx = random_int_between(min_speed, max_speed);
this.vy = random_int_between(min_speed, max_speed);
// Color & Size
this.color = color;
this.radius = size;
}
// Random number between (used for speed)
function random_int_between(min, max) {
return Math.floor(Math.random() * max) + min;
}
draw();
<canvas id="canvas"></canvas>
To calculate the distance between two points, you should use pythagoras theorem:
length = sqrt(a² + b²)
Where a is the length of one side, and b is the length of the other side.
var a = (x2 - x1);
var b = (y2 - y1);
var sum = (a * a) + (b * b);
var length = Math.sqrt(sum);
This can be turned into a function, since you know you'll have particles that have an x and y.
function calcLength(particle1, particle2) {
var xDiff = particle2.x - particle1.x;
var yDiff = particle2.y - particle1.y;
var sum = (xDiff * xDiff) + (yDiff * yDiff);
return Math.sqrt(sum);
}
Then you can use that function in your code:
for (var t = 0; t < particles.length; t++) {
var p = particles[t];
for (var q = 0; q < particles.length; q++) {
var p2 = particles[q];
if (calcLength(p, p2) < 20) {
// draw a line between the particles
}
}
}
To calculate the distance between two points you use the pythagorean theorem. http://www.purplemath.com/modules/distform.htm
// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Set fullscreen
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
// Options
var num =30; // Number of particles to draw
var size = 3; // Particle size
var color = '#fff'; // Particle color
var min_speed = 1; // Particle min speed
var max_speed = 3; // Particle max speed
var line_distance = 20; // This is the max distance between two particles
// if we want to draw a line between them
// Particles array
var particles = [];
for (var i = 0; i < num; i++) {
particles.push(
new create_particle()
);
}
// Lets animate the particle
function draw() {
// Background
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Lets draw particles from the array now
for (var t = 0; t < particles.length; t++) {
// This particle
var p = particles[t];
for (var q = 0; q < particles.length; q++) {
// Check position distance
if (distance(particles[q], p) < line_distance) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = '#fff';
ctx.moveTo(p.x, p.y);
ctx.lineTo(particles[q].x, particles[q].y);
ctx.stroke();
}
}
// Color
ctx.fillStyle = color;
// Circle path
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, Math.PI * 2, false);
ctx.fill();
// Lets use the velocity now
p.x += p.vx;
p.y += p.vy;
// If there is only 1 particle
// show X, Y, and velocity
if (num === 1) {
ctx.fillText('Y:'+ p.y, 20, 20);
ctx.fillText('X:'+ p.x, 20, 40);
ctx.fillText('YV:'+ p.vy, 20, 60);
ctx.fillText('XV:'+ p.vx, 20, 80);
}
// To prevent the balls from moving out of the canvas
if (p.x < size) p.vx*= (p.vx / -p.vx);
if (p.y < size) p.vy*= (p.vy / -p.vy);
if (p.x > canvas.width - size) p.vx*= (-p.vx / p.vx);
if (p.y > canvas.height - size) p.vy*= (-p.vy / p.vy);
}
// Loop
requestAnimationFrame(draw);
}
// Function for particle creation
function create_particle() {
// Random position
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
// Velocity
this.vx = random_int_between(min_speed, max_speed);
this.vy = random_int_between(min_speed, max_speed);
// Color & Size
this.color = color;
this.radius = size;
}
// Random number between (used for speed)
function random_int_between(min, max) {
return Math.floor(Math.random() * max) + min;
}
draw();
function distance(pointA, pointB){
var dx = pointB.x - pointA.x;
var dy = pointB.y - pointA.y;
return Math.sqrt(dx*dx + dy*dy);
}
<canvas id="canvas"></canvas>
Please note I increased the lineWidth to 1, so you could see better the result
You have a coordinate system - use the Pythagorean theorem.
I'm creating a Canvas animation, and have managed to position x number of circles in a circular path. Here's a simplified version of the code I'm using:
var total = circles.length,
i = 0,
radius = 500,
angle = 0,
step = (2*Math.PI) / total;
for( i; i < total; i++ ) {
var circle = circles[i].children[0],
cRadius = circle[i].r,
x = Math.round(winWidth/2 + radius * Math.cos( angle ) - cRadius),
y = Math.round(winHeight/2 + radius * Math.sin( angle ) - cRadius);
circle.x = x;
circle.y = y;
angle += step;
}
Which results in this:
What I am trying to achieve is for all the circles to be next to each other with no gap between them (except the first and last). The circles sizes (radius) are generated randomly and shouldn't adjust to fit the circular path:
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
I expect there to be a gap between the first and last circle.
I'm struggling to get my head around this so any help would be much appreciated :)
Cheers!
Main creation loop :
• take a current radius
• compute the angles it cover ( = atan2(smallRadius/bigRadius) )
• move base angle by this latest angle.
http://jsfiddle.net/dj2v7mbw/9/
function CircledCircle(x, y, radius, margin, subCircleCount, subRadiusMin, subRadiusMax) {
this.x = x;
this.y = y;
this.radius = radius;
this.subCircleCount = subCircleCount;
var circles = this.circles = [];
// build top sub-circles
var halfCount = Math.floor(subCircleCount / 2);
var totalAngle = addCircles(halfCount);
// re-center top circles
var correction = totalAngle / 2 + Math.PI / 2;
for (var i = 0; i < halfCount; i++) this.circles[i].angle -= correction;
// build bottom sub-circles
totalAngle = addCircles(subCircleCount - halfCount);
// re-center bottom circles
var correction = totalAngle / 2 - Math.PI / 2;
for (var i = halfCount; i < subCircleCount; i++) this.circles[i].angle -= correction;
// -- draw this C
this.draw = function (angleShift) {
for (var i = 0; i < this.circles.length; i++) drawDistantCircle(this.circles[i], angleShift);
}
//
function drawDistantCircle(c, angleShift) {
angleShift = angleShift || 0;
var thisX = x + radius * Math.cos(c.angle + angleShift);
var thisY = y + radius * Math.sin(c.angle + angleShift);
ctx.beginPath();
ctx.arc(thisX, thisY, c.r, 0, 2 * Math.PI);
ctx.fillStyle = 'hsl(' + (c.index * 15) + ',75%, 75%)';
ctx.fill();
ctx.stroke();
}
//
function addCircles(cnt) {
var currAngle = 0;
for (var i = 0; i < cnt; i++) {
var thisRadius = getRandomInt(subRadiusMin, subRadiusMax);
var thisAngle = Math.atan2(2 * thisRadius + margin, radius);
var thisCircle = new subCircle(thisRadius, currAngle + thisAngle / 2, i);
currAngle += thisAngle;
circles.push(thisCircle);
}
return currAngle;
}
}
with
function subCircle(radius, angle, index) {
this.r = radius;
this.angle = angle;
this.index = index;
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
use with
var myCircles = new CircledCircle(winWidth / 2, winHeight / 2, 350, 2, 24, 5, 50);
myCircles.draw();
animate with :
var angleShift = 0;
function draw() {
requestAnimationFrame(draw);
ctx.clearRect(0, 0, winWidth, winHeight);
myCircles.draw(angleShift);
angleShift += 0.010;
}
draw();
It's something like this, but you're gonna have to figure out the last circle's size:
http://jsfiddle.net/rudiedirkx/ufvf62yf/2/
The main logic:
var firstStep = 0, rad = 0, step = 0;
firstStep = step = stepSize();
for ( var i=0; i<30; i++ ) {
draw.radCircle(rad, step);
rad += step;
step = stepSize();
rad += step;
}
stepSize() creates a random rad between Math.PI/48 and Math.PI/48 + Math.PI/36 (no special reason, but it looked good). You can fix that to be the right sizes.
draw.radCircle(rad, step) creates a circle at position rad of size step (also in rad).
step is added twice per iteration: once to step from current circle's center to its edge and once to find the next circle's center
firstStep is important because you have to know where to stop drawing (because the first circle crosses into negative angle)
I haven't figured out how to make the last circle the perfect size yet =)
There's also a bit of magic in draw.radCircle():
var r = rRad * Math.PI/3 * 200 * .95;
The 200 is obviously the big circle's radius
The 95% is because the circle's edge length is slightly longer than the (straight) radius of every circle
I have no idea why Math.PI/3 is that... I figured it had to be Math.PI/2, which is 1 rad, but that didn't work at all. 1/3 for some reason does..... Explain that!
If you want to animate these circle sizes and keep them aligned, you'll have a hard time. They're all random now. In an animation, only the very first iteration can be random, and the rest is a big mess of cache and math =)
I'm trying to learn some canvas in html5 and javascript and I want to create those typical Illustrator sun rays:
But my problem is that I want to automate it and make it full screen.
To calculate the coordinates of the points in the middle isn't hard, it's the outer points that I cant seem to get a grip on.
K, so this is what I got.
The problem lies in the for-loop for creating an array for the outer coordinates.
So it starts calculating from the center of the screen.
If it's the first point (we ignore the inner points for now) it takes the x_coordinate variable (which is the horizontal center of the screen) and adds the width_between_rays divided by two (because I want to mimic the picture above with some space between the two upper rays).
The rest of the points are checked if they are divided by two to see if I should add the width_between_rays (should probably be offset or something) or the width_of_rays to the last points cordinates.
Well this seems pretty straight forward but since the window size isn't a fixed size I need some way of calculating where the point should be if, for example; the position of a point is outside the width/height of the screen.
So my way of calculating this doesn't work (I think).
Anyways, can someone (who's obviously smarter than me) point me in the right direction?
function sun_rays(z_index, element, color, number_of_rays, width_of_rays, width_between_rays) {
// Start the canvas stuff
var canvas = document.getElementById(element);
var ctx = canvas.getContext("2d");
console.log();
ctx.canvas.width = $(window).width();
ctx.canvas.height = $(window).width();
ctx.fillStyle = color;
// calculate the window size and center position
var window_width = $(window).width();
var window_hight = $(window).height();
var x_coordinate = window_width / 2;
var y_coordinate = window_hight / 2;
// create an array for the center coordinates
var center_coordinate_array = new Array();
for(i=0; i < number_of_rays; i++){
center_coordinate_array[i] = new Array(x_coordinate, y_coordinate);
}
// create an array for the outer coordinates
var outer_coordinate_array = new Array();
for(i=1; i == number_of_rays*2; i++){
if(i == 1) {
// X
var last_outer_x_coordinate = x_coordinate + (width_between_rays/2);
// Y
if(last_outer_x_coordinate < window_width) {
last_outer_y_coordinate = last_outer_y_coordinate;
} else {
$x_coordinate_difference = last_outer_x_coordinate - window_width;
last_outer_y_coordinate = x_coordinate_difference;
}
center_coordinate_array[i] = new Array(last_outer_x_coordinate, last_outer_y_coordinate);
} else {
if(i % 2 == 0) {
// X
last_outer_x_coordinate = last_outer_x_coordinate + width_of_rays;
// Y
//calculate the y position
center_coordinate_array[i] = new Array(last_outer_x_coordinate);
} else {
// X
last_outer_x_coordinate = last_outer_x_coordinate + width_between_rays;
// Y
//calculate the y position
center_coordinate_array[i] = new Array(last_outer_x_coordinate);
}
}
}
}
It seems like you should use the trig functions to do something like this.
var coordinate_array = [];
var xCoord = 0;
var yCoord = 0;
var angleIncrement = 15;
var i = 0;
//iterate over angles (in degrees) from 0 to 360
for (var theta = 0; theta < 360; theta += angleIncrement) {
//angle is in sector from bottom right to top right corner
if (theta >= 315 || theta <= 45)
{
xCoord = $(window).width();//point on right side of canvas
yCoord = abs($(window).width()/2 * tan(theta));
yCoord = tranformY(theta,yCoord);
}
//angle is in sector from top right to top left corner
else if (theta > 45 && theta <= 135)
{
yCoord = 0; //top is zero
xCoord = abs($(window).height()/2 * tan(theta));
xCoord = transformX(theta, xCoord);
}
//angle is in sector from top left to bottom left corner
else if (theta > 135 && theta <= 225)
{
xCoord = 0; //left edge on a canvas is zero
yCoord = abs($(window).width()/2 * tan(theta);
yCoord = transformY(theta, yCoord);
}
//angle is in sector from bottom left to bottom right corner
else // theta > 225 && theta < 315
{
yCoord = $(window).height();
xCoord = abs($(window).height()/2 * tan(theta));
xCoord = transformX(theta, xCoord);
}
coordinate_array[i++] = new Array(xCoord, yCoord);
}
//Transform from cartesian coordinates to top left is 0,0
function tranformY(theta, y)
{
var centerYCoord = $(window).height()/2;
//if angle falls in top half (Quadrant 1 or 2)
if(theta > 0 && theta < 180)
{
return centerYCoord - y;
}
elseif(theta > 180 && theta < 360)
{
return centerYCoord + y;
}
//coord falls on 0/360 or 180 (vert. median)
return centerYCoord;
}
//Transform from cartesian coordinates to top left is 0,0
function transformX(theta, x)
{
var centerXCoord = $(window).width()/2;
//if angle falls in right half (Quadrant 1 or 4)
if(theta > 270 || theta < 90)
{
return centerXCoord + x;
}
elseif(theta > 90 && theta < 270)
{
return centerXCoord - x;
}
//coordinate falls on 270 or 90 (center)
return centerXCoord;
}
//now draw your rays from the center coordinates to the points in coordinate_array
//NOTE: This code will need to be cleaned up - I just wrote it in the textbox.
The previous code puts the coordinates for the red points into an array.
This problem is by its very nature related to the incremental change of an angle. Your solution is going to need to deal with the angles using trig functions.