//Editable Vars
let cols = 35;
let rows = 35;
let fps = 5;
//Declarations
let canvas;
let ctx;
let background;
let grid = new Array(cols);
let w;
let h;
let pathfinder;
let target;
let timer;
let renderQueue = [];
let canPathfind = true;
//Space Class
class Space{
constructor(x,y,c='lightgrey'){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
}
draw(){
ctx.fillStyle = this.c;
ctx.strokeStyle = 'black';
ctx.fillRect(this.x * w, this.y * h, this.w, this.h);
ctx.rect(this.x * w, this.y * h, this.w, this.h);
ctx.stroke();
}
move(x,y){
if(x < 0 || x > cols-1 || y < 0|| y > rows-1){
return;
}
grid[this.x][this.y] = new Space(this.x,this.y);
renderQueue.push(grid[this.x][this.y]);
this.x = x;
this.y = y;
grid[this.x][this.y] = this;
renderQueue.push(grid[this.x][this.y]);
}
}
//Game-Run code
function gameStart(){
canvas = document.getElementById('gameCanvas');
ctx = canvas.getContext('2d');
w = canvas.width / cols;
h = canvas.height / rows;
createGrid();
pathfinder = new Space(randomInt(cols-1),randomInt(rows-1),'green');
grid[pathfinder.x][pathfinder.y] = pathfinder;
target = new Space(randomInt(cols-1),randomInt(rows-1),'red');
grid[target.x][target.y] = target;
drawGrid();
timer = setInterval(updateScreen, 1000/fps);
}
function restartGame(){
clearInterval(timer);
gameStart();
}
//Starts loading process on windows load
window.onload = gameStart();
//Checks all 8 possible move directions and calls pathfinder.move for best move
function pathfind(pathfinder, target){
if(!canPathfind) return;
let p = {x: pathfinder.x, y: pathfinder.y};
let t = {x: target.x, y: target.y};
let move = {x : 0, y : 0};
// 0,0 9,9
//if(p.x == t.x && p.y == t.y){
//restartGame();
//}
if(t.x - p.x >= 1){
move.x = 1;
}else if(t.x - p.x <= -1){
move.x = -1;
}else{
move.x = 0;
}
if(t.y - p.y >= 1){
move.y = 1;
}else if(t.y - p.y <= -1){
move.y = -1;
}else{
move.y = 0;
}
pathfinder.move(pathfinder.x + move.x, pathfinder.y + move.y);
}
function updateScreen(){
pathfind(pathfinder,target);
drawUpdatedSpaces();
}
function drawUpdatedSpaces(){
for(let i = 0; i < renderQueue.length; i++){
renderQueue[i].draw();
}
renderQueue = [];
}
function drawGrid(){
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j].draw();
}
}
}
//Creates grid and instantiates Space in every cell
function createGrid(){
for(let i = 0; i < grid.length; i++){
grid[i] = new Array(rows);
}
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j] = new Space(i,j);
}
}
}
// Returns distance to target from specified coords
function distanceFromTarget(x, y) {
return (Math.sqrt(Math.pow(Math.abs(x - target.x), 2) + (Math.pow(Math.abs(y - target.y), 2))));
}
// Returns random Integer between 0 and Max
function randomInt(max) {
return Math.floor(Math.random() * max);
}
It runs as expected which is great, but performance is super slow. That may be because I'm using jsfiddle to work on this while away from my personal PC setup, but is there a way to make this perform better? As of right now I can't move the grid size to >50 without it running extremely slow. I would love to eventually move to a 4k x 4k grid and create an auto-generating maze for the 'pathfinder' to pathfind through.
Thoughts/things I'm considering for performance:
Using HTML grid and updating via CSS instead of HTML5 Canvas
Only re-drawing cells that have changed (Implemented in the Space.move() function with array renderQueue)
literally re-doing everything in python :D
I have a block of code that i want to try and run on a Web worker but i can't get it to work because "document." is not defined in a worker. So i was wondering if it is even possible to run code like this on a worker. Can it get click events and so on.
The code creates a grid on a canvas and sets a clearRect where you click.
//Declare varibles
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
var cellSize = 12;
var gridColor = "#1f1f1f";
var largeGridColor = "#2e2e2e";
//set canvas resolution to screen resolution
ctx.canvas.width = 1920;
ctx.canvas.height = 932;
var cellsInRow = parseInt(ctx.canvas.height/cellSize);
var cellsInCollum = parseInt(ctx.canvas.width/cellSize);
//create array
let arry = [];
for(var i = 0; i < cellsInRow; i++){
arry[i] = [];
for(var j = 0; j < cellsInCollum; j++){
arry[i][j] = 0;
}
}
let arryOld = arry.map(row => [...row]);
//draw on canvas
setInterval(drawCells, 1);
canvasBackground();
canvasGrid();
randomStartPattern()
function getCursorPointer(canvas, event){
const rect = canvas.getBoundingClientRect();
let scaleX = canvas.width / rect.width;
let scaleY = canvas.height / rect.height;
const x = (event.clientX - rect.left)*scaleX;
const y = (event.clientY - rect.top)*scaleY;
console.log("x: " + x + " y: " + y);
let xP = (x);
let yP = (y);
setCell(xP, yP);
}
canvas.addEventListener('mousedown', function(e){
getCursorPointer(canvas, e);
});
function setCell(x, y){
for(var i = 0; i < cellsInRow; i++){
for(var j = 0; j < cellsInCollum; j++){
if((y >= (i*cellSize) && y <= ((i*cellSize)+cellSize-1)) &&(x >= (j*cellSize) && x <= ((j*cellSize)+cellSize-1))){
if(arry[i][j] == 0){
arry[i][j] = 1;
}else{
arry[i][j] = 0;
}
}
}
}
}
function drawCells(){
for(var i = 0; i < cellsInRow; i++){
for(var j = 0; j < cellsInCollum; j++){
if(arry[i][j] == 1){
ctx.clearRect(j * cellSize+1, i * cellSize+1, cellSize-2, cellSize-2);
}else{
ctx.fillStyle ="black";
ctx.fillRect(j * cellSize+1, i * cellSize+1, cellSize-2, cellSize-2);
}
}
}
}
function canvasBackground(){
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function canvasGrid(){
for(var i = 0; i < cellsInRow; i++){
if(i%10 == 0){
ctx.strokeStyle = largeGridColor;
}else{
ctx.strokeStyle = gridColor;
}
ctx.beginPath();
ctx.moveTo(0, i*cellSize);
ctx.lineTo(canvas.width, i*cellSize);
ctx.lineWidth = 1;
ctx.stroke();
for(var j = 0; j < cellsInCollum; j++){
if(j%10 == 0){
ctx.strokeStyle = largeGridColor;
}else{
ctx.strokeStyle = gridColor;
}
ctx.beginPath();
ctx.moveTo(j*cellSize, 0);
ctx.lineTo(j*cellSize, canvas.height);
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
function randomStartPattern(){
for(var i = 0; i < cellsInRow; i++){
for(var j = 0; j < cellsInCollum; j++){
let rand = Math.random();
if(rand < 0.5){
arry[i][j] = 1;
}else{
arry[i][j] = 0;
}
}
}
}
It should works smth like this:
/// ================= MAIN SCRIPT ====================
const worker = new Worker('./your_worker.js');
function getCursorPointer(canvas, event){
const rect = canvas.getBoundingClientRect();
let scaleX = canvas.width / rect.width;
let scaleY = canvas.height / rect.height;
const x = (event.clientX - rect.left)*scaleX;
const y = (event.clientY - rect.top)*scaleY;
console.log("x: " + x + " y: " + y);
worker.postMessage({x, y});
}
canvas.addEventListener('mousedown', function(e){
getCursorPointer(canvas, e);
});
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({offscreen}, [offscreen]);
Here you works with DOM, and pass events to worker using postMessage
// ==================== WORKER CODE ===========================
let ctx;
const arry = [];
onmessage = function({data}) {
if(data.offscreen) {
ctx = data.offscreen.getContext("2d");
canvasBackground();
canvasGrid();
randomStartPattern()
setInterval(drawCells, 1);
} else {
setCell(data.x, data.y);
}
});
function drawCells(){
for(var i = 0; i < cellsInRow; i++){
for(var j = 0; j < cellsInCollum; j++){
if(arry[i][j] == 1){
ctx.clearRect(j * cellSize+1, i * cellSize+1, cellSize-2, cellSize-2);
}else{
ctx.fillStyle ="black";
ctx.fillRect(j * cellSize+1, i * cellSize+1, cellSize-2, cellSize-2);
}
}
}
}
function randomStartPattern(){
for(var i = 0; i < cellsInRow; i++){
for(var j = 0; j < cellsInCollum; j++){
let rand = Math.random();
if(rand < 0.5){
arry[i][j] = 1;
}else{
arry[i][j] = 0;
}
}
}
}
function setCell(x, y){
for(var i = 0; i < cellsInRow; i++){
for(var j = 0; j < cellsInCollum; j++){
if((y >= (i*cellSize) && y <= ((i*cellSize)+cellSize-1)) &&(x >= (j*cellSize) && x <= ((j*cellSize)+cellSize-1))){
if(arry[i][j] == 0){
arry[i][j] = 1;
}else{
arry[i][j] = 0;
}
}
}
}
}
function canvasBackground(){
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function canvasGrid(){
for(var i = 0; i < cellsInRow; i++){
if(i%10 == 0){
ctx.strokeStyle = largeGridColor;
}else{
ctx.strokeStyle = gridColor;
}
ctx.beginPath();
ctx.moveTo(0, i*cellSize);
ctx.lineTo(canvas.width, i*cellSize);
ctx.lineWidth = 1;
ctx.stroke();
for(var j = 0; j < cellsInCollum; j++){
if(j%10 == 0){
ctx.strokeStyle = largeGridColor;
}else{
ctx.strokeStyle = gridColor;
}
ctx.beginPath();
ctx.moveTo(j*cellSize, 0);
ctx.lineTo(j*cellSize, canvas.height);
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
Here you works with canvas context, store your arry and catch events from main page by onmessage
I'm trying to get my head around using javascript in the html < canvas >, and I started with the MDN Breakout game tutorial. Here is how the complete game looks like. I'm stuck with one of the exercises and I really could not find any solution to my issue after an hour of googling!! :((. The following code generates a ball on the canvas.
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = '#FFFFF';
ctx.fill();
ctx.closePath();
I needed the ball to change its colour after it collides with one of the bricks. In order to achieve that, I created a variable to store the colour value: let colour = '#FFFFF';, and later in a function which detects collisions, changed the value of this variable. It worked fine, however, whenever the ball changed its colour, so did the bricks and the paddle. As I tried to fix this, I found out that whenever I manually change the colour of either a ball, a brick or a paddle (all of which are set in different functions), all of the objects change the colour as well.
This is very strange, because if in an emply .js file I make just two shapes and colour them differently it works fine:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.rect(0, 0, 20, 40);
ctx.fillStyle = 'cyan';
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(50, 50, 20, 0, Math.PI*2);
ctx.fillStyle = 'black';
ctx.fill();
ctx.closePath();
But with all the game code I have right now, I can not assign different colour to different objects, they all change colour instead! I have no idea how to fix this and change just the colour of the ball! Anyone knows what might be causing the issue? Please help, thank you so much in advance 🙏
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
//Ball variables
const radius = 10;
let colour = '#FFFFF';
let x = canvas.width / 2;
let y = canvas.height - 30;
let dx = 2;
let dy = -2;
//Paddle
const paddleHeight = 10;
let paddleWidth = 100;
let paddleX = (canvas.width - paddleWidth) / 2;
//Paddle movement
var rightPressed = false;
var leftPressed = false;
//Bricks
var brickRowCount = 3;
var brickColumnCount = 5;
var brickWidth = 75;
var brickHeight = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = {
x: 0,
y: 0,
status: 1
};;
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
drawPaddle();
drawBricks();
collisionDetection();
x += dx;
y += dy;
if (rightPressed && paddleX < canvas.width - paddleWidth) {
paddleX += 7;
} else if (leftPressed && paddleX > 0) {
paddleX -= 7;
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = colour;
ctx.fill();
ctx.closePath();
//Bounce off the walls
if (x + dx > canvas.width - radius || x + dx < radius) {
dx = -dx;
}
if (y + dy < radius) {
dy = -dy;
} else if (y + dy > canvas.height - radius) {
//Collision detection (ball + paddle)
if (x > paddleX && x < paddleX + paddleWidth) {
dy = -dy;
} else {
//alert("GAME OVER");
document.location.reload();
}
}
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = '#FFFFF';
ctx.fill();
ctx.closePath();
}
function drawBricks() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status == 1) {
var brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
var brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = "#FFFFF";
ctx.fill();
ctx.closePath();
}
}
}
}
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight") {
rightPressed = true;
} else if (e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = true;
}
}
function keyUpHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight") {
rightPressed = false;
} else if (e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = false;
}
}
function collisionDetection() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status == 1) {
if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
dy = -dy;
b.status = 0;
colour = '#ff9ecb';
}
}
}
}
}
var interval = setInterval(draw, 10);
<!DOCTYPE html>
<html>
<head>
<title>Breakout Game</title>
</head>
<body>
<canvas id='canvas' height='320' width='480'></canvas>
<script src="app.js"></script>
</body>
</html>
Looks like a minor error with the colour hex codes you're using for your fillStyle, which are invalid. See the corrections in the snippet below from:
let colour = '#FFFFF'; // Six characters invalid
To:
let colour = '#FF0000'; // Seven characters valid
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
//Ball variables
const radius = 10;
// Fix colour
let colour = '#FF0000';
let x = canvas.width / 2;
let y = canvas.height - 30;
let dx = 2;
let dy = -2;
//Paddle
const paddleHeight = 10;
let paddleWidth = 100;
let paddleX = (canvas.width - paddleWidth) / 2;
//Paddle movement
var rightPressed = false;
var leftPressed = false;
//Bricks
var brickRowCount = 3;
var brickColumnCount = 5;
var brickWidth = 75;
var brickHeight = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = {
x: 0,
y: 0,
status: 1
};;
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
drawPaddle();
drawBricks();
collisionDetection();
x += dx;
y += dy;
if (rightPressed && paddleX < canvas.width - paddleWidth) {
paddleX += 7;
} else if (leftPressed && paddleX > 0) {
paddleX -= 7;
}
}
function drawBall() {
// Update to valid colour
ctx.fillStyle = colour;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
//Bounce off the walls
if (x + dx > canvas.width - radius || x + dx < radius) {
dx = -dx;
}
if (y + dy < radius) {
dy = -dy;
} else if (y + dy > canvas.height - radius) {
//Collision detection (ball + paddle)
if (x > paddleX && x < paddleX + paddleWidth) {
dy = -dy;
} else {
//alert("GAME OVER");
document.location.reload();
}
}
}
function drawPaddle() {
// Update to valid colour
ctx.fillStyle = '#00FF00';
// Consider using fillRect
ctx.fillRect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
}
function drawBricks() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status == 1) {
var brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
var brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
// Update to valid colour
ctx.fillStyle = "#FFFFaa";
// Consider using fillRect
ctx.fillRect(brickX, brickY, brickWidth, brickHeight);
}
}
}
}
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight") {
rightPressed = true;
} else if (e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = true;
}
}
function keyUpHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight") {
rightPressed = false;
} else if (e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = false;
}
}
function collisionDetection() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status == 1) {
if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
dy = -dy;
b.status = 0;
colour = '#ff9ecb';
}
}
}
}
}
var interval = setInterval(draw, 10);
<!DOCTYPE html>
<html>
<head>
<title>Breakout Game</title>
</head>
<body>
<canvas id='canvas' height='320' width='480'></canvas>
<script src="app.js"></script>
</body>
</html>
Also, consider using fillRect() as shown above, for a slightly simpler implementation. Hope that helps!
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 would like to know how the "moving effect" it's made in this site:
http://www.perturbator.com/
I mean the pink lines, not the logo ()wichi is a simple parallax).
EDIT (I can't answer myself in 8 hours):
The code it's in the base.js file. It drawns the dots and then the lines.
$(function(){
var header = $('.site-header'),
canvas = $('<canvas></canvas>').appendTo(header)[0],
ctx = canvas.getContext('2d'),
color = '#fc335c',
idle = null, mousePosition;
canvas.width = window.innerWidth;
canvas.height = header.outerHeight();
canvas.style.display = 'block';
ctx.fillStyle = color;
ctx.lineWidth = .1;
ctx.strokeStyle = color;
var mousePosition = {
x: 30 * canvas.width,
y: 30 * canvas.height
},
dots = {
nb: 150,
distance: 90,
d_radius: 900,
array: []
};
function Dot(){
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = -.5 + Math.random();
this.vy = -.5 + Math.random();
this.radius = Math.random();
}
Dot.prototype = {
create: function(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.fill();
},
animate: function(){
for(var i = 0, dot=false; i < dots.nb; i++){
dot = dots.array[i];
if(dot.y < 0 || dot.y > canvas.height){
dot.vx = dot.vx;
dot.vy = - dot.vy;
}else if(dot.x < 0 || dot.x > canvas.width){
dot.vx = - dot.vx;
dot.vy = dot.vy;
}
dot.x += dot.vx;
dot.y += dot.vy;
}
},
line: function(){
for(var i = 0; i < dots.nb; i++){
for(var j = 0; j < dots.nb; j++){
i_dot = dots.array[i];
j_dot = dots.array[j];
if((i_dot.x - j_dot.x) < dots.distance && (i_dot.y - j_dot.y) < dots.distance && (i_dot.x - j_dot.x) > - dots.distance && (i_dot.y - j_dot.y) > - dots.distance){
if((i_dot.x - mousePosition.x) < dots.d_radius && (i_dot.y - mousePosition.y) < dots.d_radius && (i_dot.x - mousePosition.x) > - dots.d_radius && (i_dot.y - mousePosition.y) > - dots.d_radius){
ctx.beginPath();
ctx.moveTo(i_dot.x, i_dot.y);
ctx.lineTo(j_dot.x, j_dot.y);
ctx.stroke();
ctx.closePath();
}
}
}
}
}
};
function createDots(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < dots.nb; i++){
dots.array.push(new Dot());
dot = dots.array[i];
dot.create();
}
dot.line();
dot.animate();
}
idle = setInterval(createDots, 1000/30);
$(canvas).on('mousemove mouseleave', function(e){
if(e.type == 'mousemove'){
mousePosition.x = canvas.width / 2;
mousePosition.y = canvas.height / 2;
}
if(e.type == 'mouseleave'){
mousePosition.x = canvas.width / 2;
mousePosition.y = canvas.height / 2;
}
});
});
If you're talking about the pinkish background getting bigger and smaller, I believe it is done with a CSS3 animation http://www.w3schools.com/css/css3_animations.asp
You can see the details following the DOM chain:
<div id="home">
<div>...
<div>...
<div class="bg-layer">
If you were talking about the 3D "Perturbator", then I'd guess something related to 3Dtransforms in a JS script, but I can't tell much more...