Ultimately I like to know which object was being clicked in a canvas, and I wrote the script:
dist.push(Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110));
dist.push(Math.abs(x-ball2.x-88.5) + Math.abs(y-ball2.y-110));
dist.push(Math.abs(x-ball3.x-88.5) + Math.abs(y-ball3.y-110));
function sortNumber(a,b) {
return a - b;
}
dist.sort(sortNumber);
Obviously this only give me the sort of the number but I need it to connect with ball1, ball2, and ball3. I figure I could nest an array for this but I haven't figured out the logic...
Or perhaps my approach was wrong to begin with?
P.S., obviously if I only have three balls I can do this:
var b1d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
var b2d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
var b3d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
dist.push(b1d, b2d, b3d);
function sortNumber(a,b) {
return a - b;
}
dist.sort(sortNumber);
if (dist[0] == b1d) {
alert('b1');
} else if (dist[0] == b2d) {
alert('b2');
} else if (dist[0] == d3d) {
alert('b3');
} else {
alert('####');
}
But if I have hundreds of balls this probably isn't the best way...
How you search depends on what you do with the balls, and how you place them.
Here a simple example that you can click a ball and bring it in the foreground.
We create 200 balls.
To find the correct ball we start searching in an one time sorted array, based on the z-index of the ball (from the balls on the back to the balls on the frond), as you can not click the balls on the back, we search starting from the last element of the array.
In this example, this is a good solution, but in your applications it may not be, it depends on many things, like if the balls overlap, or if the possition is not random.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var getRandomColor = function() {
// Code from : http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
var createRandomBall = function(){
// The ball object
var ball = {};
// Random radius (min 5 max 30)
ball.radius = Math.floor((Math.random() * 25) + 5);
ball.radius2 = Math.pow(ball.radius, 2);
// Random x position
ball.x = Math.floor((Math.random() * (canvas.width - ball.radius*2)) + ball.radius);
// Random y position
ball.y = Math.floor((Math.random() * (canvas.height - ball.radius*2)) + ball.radius);
// Random color
ball.color = getRandomColor();
return ball;
}
// Create many balls
var ballList = [];
var tmp_ball;
for (var i = 0; i < 200; i++) {
// Make a random ball
tmp_ball = createRandomBall();
// Add to the list
ballList.push(tmp_ball);
}
// Render the balls
var renderBalls = function(){
var ball;
// For each ball
for (var i = 0; i < ballList.length; i++) {
ball = ballList[i];
// Stroke ball
context.beginPath();
context.arc(ball.x, ball.y, ball.radius - 1, 0, 2 * Math.PI, false);
context.fillStyle = ball.color;
context.fill();
context.lineWidth = 1;
context.strokeStyle = '#000000';
context.stroke();
}
}
// Render balls
renderBalls();
// Add click event
canvas.addEventListener('click', function(event){
// Get x and y of click
var click = {
x : event.clientX - canvas.offsetLeft,
y : event.clientY - canvas.offsetTop
};
var ball = null;
// Find clicked ball
// we search the array from the back,
// because on the back balls are over the frond balls
for (var i = ballList.length - 1; i >= 0; i--) {
if( Math.pow(click.x - ballList[i].x, 2) + Math.pow(click.y - ballList[i].y, 2) <= ballList[i].radius2 ){
ball = i;
break;
}
}
// If no ball found return
if(ball == null){
console.log("No ball clicked");
return;
}
// else ball found
ball = ballList.splice(ball, 1)[0];
// Else position ball on the frond
ballList.push(ball);
// Re-render
renderBalls();
}, false);
*{
padding: 0px;
margin: 0px;
}
#myCanvas{
position: absolute;
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
}
<canvas id="myCanvas" width="600" height="200"></canvas>
A general solution is to make a map table, a grid of your canvas, and on each cell add the corresponding balls, so that you can match in which grid box the click was made and check a smaller group of balls.
So for example, lets say that you want when you click, all the balls under the click to change color. Here is an example with mapping the balls on smaller groups, we make a grid of 10 columns and 5 lines. Each ball may be in more than 1 group. We create 400 balls.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var getRandomColor = function() {
// Code from : http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
var gridSize = {x:10, y:5};
var gridList = [];
// Rows
for (var i = 0; i < gridSize.y; i++) {
gridList.push([]);
// Columns
for (var j = 0; j < gridSize.x; j++) {
gridList[i].push([]);
}
}
var createRandomBall = function(){
// The ball object
var ball = {};
// Random radius (min 5 max 30)
ball.radius = Math.floor((Math.random() * 25) + 5);
ball.radius2 = Math.pow(ball.radius, 2);
// Random x position
ball.x = Math.floor((Math.random() * (canvas.width - ball.radius*2)) + ball.radius);
// Random y position
ball.y = Math.floor((Math.random() * (canvas.height - ball.radius*2)) + ball.radius);
// Random color
ball.color = getRandomColor();
// Map ball - find cells that the circle overlap
grid = {
x : {
min : Math.floor((ball.x - ball.radius)*gridSize.x/canvas.width),
max : Math.floor((ball.x + ball.radius)*gridSize.x/canvas.width)
},
y : {
min : Math.floor((ball.y - ball.radius)*gridSize.y/canvas.height),
max : Math.floor((ball.y + ball.radius)*gridSize.y/canvas.height)
}
}
for (var y = grid.y.min; y <= grid.y.max; y++) {
for (var x = grid.x.min; x <= grid.x.max; x++) {
gridList[y][x].push(ball);
}
}
return ball;
}
// Create many balls
var ballList = [];
var tmp_ball;
for (var i = 0; i < 400; i++) {
// Make a random ball
tmp_ball = createRandomBall();
// Add to the list
ballList.push(tmp_ball);
}
// Render the balls
var renderBalls = function(){
var ball;
// For each ball
for (var i = 0; i < ballList.length; i++) {
ball = ballList[i];
// Stroke ball
context.beginPath();
context.arc(ball.x, ball.y, ball.radius - 1, 0, 2 * Math.PI, false);
context.fillStyle = ball.color;
context.fill();
context.lineWidth = 1;
context.strokeStyle = '#000000';
context.stroke();
}
for (var i = 0; i < gridSize.x + 1; i++) {
context.beginPath();
context.moveTo((canvas.width/gridSize.x)*i, 0);
context.lineTo((canvas.width/gridSize.x)*i, canvas.height);
context.stroke();
}
for (var i = 0; i < gridSize.y + 1; i++) {
context.beginPath();
context.moveTo(0, (canvas.height/gridSize.y)*i);
context.lineTo(canvas.width, (canvas.height/gridSize.y)*i);
context.stroke();
}
}
// Render balls
renderBalls();
// Add click event
canvas.addEventListener('click', function(event){
// Get x and y of click
var click = {
x : event.clientX - canvas.offsetLeft,
y : event.clientY - canvas.offsetTop
};
var grid = {
x : Math.floor(click.x*gridSize.x/canvas.width),
y : Math.floor(click.y*gridSize.y/canvas.height)
};
var ball = 0;
var smallerList = gridList[grid.y][grid.x];
// Find clicked ball
for (var i = smallerList.length - 1; i >= 0; i--) {
if( Math.pow(click.x - smallerList[i].x, 2) + Math.pow(click.y - smallerList[i].y, 2) <= smallerList[i].radius2 ){
ball++;
smallerList[i].color = getRandomColor();
}
}
console.log("Group["+grid.y+"]["+grid.x+"], " + smallerList.length + " balls in group, clicked " + ball + " balls");
// If no ball found return
if(ball == 0){
return;
}
// Re-render
renderBalls();
}, false);
*{
padding: 0px;
margin: 0px;
}
#myCanvas{
position: absolute;
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
}
<canvas id="myCanvas" width="600" height="200"></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've been trying to put together a simple maze game using HTML5 and Javascript. I can successfully load the HTML and CSS content onto the page, but no matter what I try, I can't get the JS to load. It's definitely saved as a .html file and i've only been using Sublime text to put it together (but I wouldn't have thought that would have an affect anyway). Just a bit stumped really, so I thought it must be something I've missed in my code. I wasn't sure if I've missed something?
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title> Maze Game </title>
</head>
<style>
canvas {
border: 8px double navy;
background: white;
}
img {
display: none;
}
button {
padding: 3px;
}
</style>
<body>
<canvas id="canvas"> </canvas>
<img id="sprite" src="sprite.png">
<script>
//these define the global variables for the canvas and the drawing context
var canvas;
var context;
var x = 0;
var y = 0; //positioning of the sprite
var dx = 0;
var dy = 0; //momentum of the sprite at start
window.onload = function() {
//setting up the canvas
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
//Draws the maze background
drawMaze("maze.png", 268, 5);
//On key press, run the following function
window.onkeydown = processKey;
};
var x = 0;
var y = 0;
function drawMaze(mazeFile, Xstart, Ystart) {
//This loads the maze picture in
dx = 0;
dy = 0; //if the face is already moving, stop it
var imgMaze = new Image();
imgMaze.onLoad = function() {
canvas.width = imgMaze.width;
canvas.height = imgMaze.height;
//Draws the maze onto the canvas
context.drawImage(imgMaze, 0, 0);
//draws the sprite and positions
x = Xstart;
y = Ystart;
var imgSprite = document.getElementById("sprite");
context.drawImage(imgSprite, x, y);
context.stroke();
//sets a short timer for the next frame to be drawn in (10ms)
setTimeout("drawFrame()", 10);
};
imgMaze.src = mazeFile;
}
function processKey(e) { //e needs to be used for event handling
//stop the sprite if it's already moving - enables collision
var dx = 0;
var dy = 0;
//condition for the Up arrow being pressed
if (e.keyCode == 38) {
dy = -1;
}
//condition for the Left arrow being pressed
if (e.keyCode == 37) {
dx = -1;
}
//condition for the Down arrow being pressed
if (e.keyCode == 40) {
dy = 1;
}
//condition for the Right arrow being pressed
if (e.keyCode == 39) {
dx = 1;
}
}
function drawFrame() {
if (dx != 0 || dy != 0) {
context.beginPath();
context.fillStyle = "rgb(254,244,207)";
context.rect(x, y, 15, 15);
context.fill
x += dx;
y += dy;
if (checkForCollision()) {
(dx/y = 0)
x -= dx;
y -= dy;
dx = 0;
dy = 0;
}
//Now we can finally draw the sprite!
var imgSprite = document.getElementById("sprite");
context.drawImage(imgSprite, x, y);
if (y > (canvas.height - 17)) {
alert("Congratulations! You made it!");
return;
}
}
timer = setTimeout(drawFrame, 10);
}
var imageData = context.getImageData(0, 0, 100, 50);
var pixels = imageData.data;
for (var i = 0, n = pixels.length; i < n; i += 4) {
//This will get the data/values for one pixel
var red = pixels[i];
var green = pixels [i+1];
var blue = pixels [i+2];
var alpha = pixels [i+3];
//This will invert the colours
pixels[i] = 255 - red;
pixels[i+1] = 255 - green;
pixels[i+2] = 255 - blue;
}
context.putImageData(imageData, 0, 0);
function checkForCollision() {
var imgData = context.getImageData(x-1, y-1, 15+2, 15+2);
var pixels = imgData.data;
//Then we need to perform a check, same as above
for (var i = 0; n = pixels.length, i < n; i += 4) {
var red = pixels[i];
var green = pixels[i+1];
var blue = pixels[i+2];
var alpha = pixels[i+3];
//now check for the black pixels for a wall
if (red == 0 && green == 0 && blue == 0) {
return true;
} //checks for a greyish colour - possibly the edge of a wall
if (red == 169 && green == 169 && blue == 169) {
return true;
}
}
return false; //there was no collision
}
</script>
</body>
</html>
There are lots of errors in this code. For example, in this section alone:
(commented where some issues are)
function drawFrame() {
if (dx != 0 || dy != 0) {
context.beginPath();
context.fillStyle = "rgb(254,244,207)";
context.rect(x, y, 15, 15);
context.fill // Missing parentheses and semicolon
x += dx;
y += dy;
if (checkForCollision()) {
(dx/y = 0) // Equivalent to { dx / (y = 0) }
x -= dx; // which both serves no purpose and divides by zero
y -= dy;
dx = 0;
dy = 0;
}
//Now we can finally draw the sprite!
var imgSprite = document.getElementById("sprite");
context.drawImage(imgSprite, x, y);
if (y > (canvas.height - 17)) {
alert("Congratulations! You made it!");
return;
}
}
timer = setTimeout(drawFrame, 10); // timer is not defined anywhere
// also you are calling the function within itself
// with no end condition, so it's an infinite loop
}
And on line 48:
setTimeout("drawFrame()", 10);
You should be passing the function as just the function identifier, not as a string. As such:
setTimeout(drawFrame, 10);
These are just a few. There are also some logical errors, variables that are defined but never used, and more. In its current state this will not compile.
Also, if only just for clarity's sake, try to define a script type instead of just leaving the tag empty, like:
<script type="text/javascript">
// Some JS
</script>
It can be hard when you've been staring at code for hours, but it helps to give each section a slow read-through and think about exactly what the code is doing. You can avoid lots of syntactical and logical errors this way.
I also recommend using a text editor or online interface (JS Bin, JSfiddle, etc.) that has JShint/lint or some kind of code-checking functionality. You can even use http://www.javascriptlint.com/online_lint.php and paste your whole code in there.
I am trying to create a grid-based shadow engine using JavaScript. My algorithm shades squares based on whether their position is 'behind' a block, relative to a light source.
This is my algorithm so far: https://jsfiddle.net/jexqpfLf/
var canvas = document.createElement('canvas');
canvas.width = 600;
canvas.height = 400;
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
var light_x = 90;
var light_y = 110;
var block_x = 120;
var block_y = 120;
requestAnimationFrame(render);
function render() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
var vec1_x = block_x - light_x;
var vec1_y = block_y - light_y;
var vec1_mag = Math.sqrt(vec1_x * vec1_x + vec1_y * vec1_y);
ctx.fillStyle = 'black';
for (var x = 0; x < canvas.width; x += 10)
for (var y = 0; y < canvas.width; y += 10) {
var vec2_x = x - light_x;
var vec2_y = y - light_y;
var vec2_mag = Math.sqrt(vec2_x * vec2_x + vec2_y * vec2_y);
var dotproduct = vec1_x * vec2_x + vec1_y * vec2_y;
var angle = Math.acos(dotproduct / (vec1_mag * vec2_mag));
if (vec2_mag > vec1_mag && angle < Math.PI / 8 / vec1_mag * 10)
ctx.fillRect(x, y, 10, 10);
}
ctx.fillStyle = 'green';
ctx.fillRect(light_x, light_y, 10, 10);
ctx.fillStyle = 'red';
ctx.fillRect(block_x, block_y, 10, 10);
requestAnimationFrame(render);
}
onkeydown = function (e) {
if (e.which == 65)
light_x -= 10;
if (e.which == 68)
light_x += 10;
if (e.which == 87)
light_y -= 10;
if (e.which == 83)
light_y += 10;
}
Unfortunately, as you can see in the demonstration, I'm finding some angles problematic. Some squares which should be shaded are left unshaded. This happens for some angles and distances (between the light source and the block) but not others. For example, placing the light source at (60, 90) shows these artifacts as well.
I am using the vectors LP (from light to point) and LB (from light to block), taking their dot product and dividing by the product of their magnitudes to find the shading angle, then scaling this angle depending on the distance between the block and light source.
Could these artifacts be due to rounding errors? Or is there a problem with the algorithm itself? Any help would be appreciated :-)
Great question. You're not gonna like this one.
It's a floating point math issue.
What's the value of Math.acos(1.000000000000000)?
0.
Whats the value of Math.acos(1.0000000000000003)?
NaN.
That's annoying, isn't it?
At some values, your dotproduct is 6000 and your (vec1_mag * vec2_mag) is 5999.999999999999, leading to the issue above.
Changing (vec1_mag * vec2_mag) to Math.round(vec1_mag * vec2_mag) will solve your problem.
While we're staring at this fiddle together you should know that there's another bug:
for (var x = 0; x < canvas.width; x += 10) {
for (var y = 0; y < canvas.width; y += 10) {
You use canvas.width here twice. I imagine the second one ought to be canvas.height, so make sure what you wrote there is what you want.
Working fiddle for you!