I'm making a tower defense game using JavaScript and p5.js library. My enemy follows a path and their location is always stored in a list. I have a base and a gun, the gun rotates around the base(as 1 unit) and is supposed to point towards the nearest enemy. I have a function that will allow me to make the gun point towards the enemy pointEnemy however, I'm not able to get the correct condition to make it point towards the nearest enemy in it's range. I need the correct argument for enemyx & enemyy. I'm currently spawning 100 enemies and they keep moving, their location is stored in globalenemy1position. Any help is appreciated, thank you.
Required Code
Some important variables
var numberOfEnemy1 = 100
let classenemy1 = new Enemy1(numberOfEnemy1);
var globalenemy1position = [];
var isFireTowerPressed = false;
var FireTowerPos = []; // Position of all FireTowers => [x,y]
var FireTowerRange = 300;
var FireTowerAngle = 0;
My Enemy Class
class Enemy1
{
constructor(number_of_enemies)
{
this.number_of_enemies = number_of_enemies;
this.enemy_position = [];
this.enemy1speed = 4;
}
enemy1_spawn()
{
let randomx = random(-300, -100);
for(var i=0; i<this.number_of_enemies; i++)
{
var positionx = randomx;
var positiony = 100;
this.enemy_position.push([positionx + (-i*50), positiony]);
globalenemy1position.push([positionx + (-i*50), positiony]);
image(enemy1, this.enemy_position[i][0], this.enemy_position[i][1]);
}
}
enemy1_move()
{
for(var i = 0; i < this.enemy_position.length; i++)
{
image(enemy1, this.enemy_position[i][0], this.enemy_position[i][1]);
if (this.enemy_position[i][0] >= 200 && this.enemy_position[i][1] <= 450 && this.enemy_position[i][0] < 599)
{
this.enemy_position[i][1] += this.enemy1speed;
globalenemy1position[i][1] += this.enemy1speed;
}
else if (this.enemy_position[i][1] >= 100 && this.enemy_position[i][0] >= 600)
{
this.enemy_position[i][1] -= this.enemy1speed;
globalenemy1position[i][1] -= this.enemy1speed;
}
else if (this.enemy_position[i][0] >= 750)
{
this.enemy_position[i][0] = 750;
lives --;
this.enemy_position.shift();
globalenemy1position.shift();
}
else
{
this.enemy_position[i][0] += this.enemy1speed;
globalenemy1position[i][0] += this.enemy1speed;
}
}
}
}
Draw Function - Redraws Every Frame
function draw()
{
background(60, 238, 161);
[...]
classenemy1.enemy1_move();
rect(750, 70, 50, 100);
ShowLives();
if (isFireTowerPressed == true)
{
image(firetowerbaseImg, mouseX - 28, mouseY - 28);
noFill();
stroke(0,0,0);
strokeWeight(1);
circle(mouseX, mouseY, 300);
}
for (var i = 0; i < FireTowerPos.length; i++)
{
image(firetowerbaseImg, FireTowerPos[i][0], FireTowerPos[i][1]);
if (globalenemy1position.length >= 1)
{
var gunx = FireTowerPos[i][0] +28;
var guny = FireTowerPos[i][1]+25;
var gunrange = FireTowerPos[i][3];
for (j=0; j<globalenemy1position.length; j++)
{
// Need help with this statement here
pointEnemy(globalenemy1position[j][0], globalenemy1position[j][1], gunx, guny, FireTowerPos[i][2], FireTowerPos[i][3]);
}
}
else
{
image(firetowerturretImg, FireTowerPos[i][0], FireTowerPos[i][1]-20);
}
}
}
Function to make the gun point towards Enemy - I need the proper value for enemyx & enemyy
function pointEnemy(enemyx, enemyy, gunx, guny, gunangle, gunrange)
{
const isWithinRange = dist(enemyx, enemyy, gunx, guny) < gunrange;
if(isWithinRange)
{
gunangle = atan2(enemyy - guny, enemyx - gunx) + radians(90);
}
push();
translate(gunx, guny);
// rect(-25, -20, 50, 40) // Draw the gun base
// ellipse(0, 0, gun.range*2) // display the gun range
rotate(gunangle);
image(firetowerturretImg, -28, -45); // Set the offset of the gun sprite and draw the gun
pop();
}
Here is a picture to help visualise the problem
As you can see, currently I'm just iterating through all the enemies and giving their location, so it's basically pointing to every enemy nearby.
Updates
1
I tried the approach given by #user3386109 , but wasn't able to implement it, also if possible I want the turret/gun to point towards the enemy till it leaves the range and not always point towards the closest enemy. It should start off with the closest and then keep pointing towards it till it leaves or the enemy dies(position removed from the list), whichever comes first. The function should then restart again and continue the process.
This process is the complete aiming for the tower. Add this to draw and it searches for enemies.
for (var i = 0; i < FireTowerPos.length; i++)
{
// image(firetowerbaseImg, FireTowerPos[i][0], FireTowerPos[i][1]);
// pointEnemy(mouseX, mouseY, FireTowerPos[i][0] +28, FireTowerPos[i][1]+25, FireTowerPos[i][2], FireTowerPos[i][3]);
image(firetowerbaseImg, FireTowerPos[i][0], FireTowerPos[i][1]);
var enemiesInRange = [];
let firetowerx = FireTowerPos[i][0];
let firetowery = FireTowerPos[i][1];
for (var j = 0; j < globalenemy1position.length; j++)
{
var checkDist = dist(globalenemy1position[j][0], globalenemy1position[j][1], firetowerx, firetowery);
let thisenemyx = globalenemy1position[j][0];
let thisenemyy = globalenemy1position[j][1];
if (checkDist < FireTowerRange)
{
enemiesInRange.push([thisenemyx, thisenemyy]);
pointEnemy(enemiesInRange[0][0], enemiesInRange[0][1], FireTowerPos[i][0] +28, FireTowerPos[i][1]+25, FireTowerPos[i][2], FireTowerPos[i][3]);
}
else
{
enemiesInRange.shift();
}
}
}
Related
As stated in the title, rendering slows down significantly at higher resolutions. I'm wondering if this is caused by beginShape() as well as why and how to get around it. Other functions that do not use beginShape() do not affect the frame rate negatively. Link to p5 editor here: https://editor.p5js.org/anton.ermkv/sketches/mSkLrkPJ9
Code below:
function w(v) {if (v == null) return width;return width * v;}
function h(v) {if (v == null) return height;return height * v;}
let zoff = 0;
let irregCircs = []
let numCircs;
function setup() {
createCanvas(windowWidth, windowHeight);
exType = chooseExpandType()
pixelDensity(1)
setIrregCircles(exType)
}
function draw() {
translate(width/2,height/2)
background(255,50);
drawIrregCircles()
console.log(frameRate())
}
function chooseExpandType() {
expandType = 'ellipses'
return expandType
}
function irregExpand(radius,noiseVal) {
beginShape();
for (let a = 0; a <= TWO_PI; a += radians(6)) {
let xoff = map(cos(a), -1, 1, noiseVal/3, noiseVal);
let yoff = map(sin(a), -1, 1, noiseVal/3, noiseVal);
let diff = map(noise(xoff, yoff, zoff), 0, 1, .65, 1.35);
let x = radius * diff * cos(a);
let y = radius * diff * sin(a);
vertex(x,y)
}
endShape(CLOSE);
zoff += 0.0001;
}
function setIrregCircles(expandType) {
numCircs = 4;
for (let i = 0; i < numCircs; i++) {
radius = map(i,0,numCircs,w(.03),w(.65))
noiseVal = random(1,3)
circ = new IrregCircle(radius,noiseVal,expandType,numCircs);
irregCircs.push(circ);
}
}
function drawIrregCircles() {
for (let i = 0; i < irregCircs.length; i++){
irregCircs[i].run();
}
}
class IrregCircle{
constructor(_radius,_noiseVal,_expandType,_numC) {
this.radius = _radius;
this.noiseVal = _noiseVal;
this.expandType = _expandType;
this.numC = _numC;
}
run() {
this.update()
this.checkEdges()
this.show()
}
update() {
this.radius += w(.0015)
}
checkEdges() {
if (this.radius > w(.73)) {
this.radius = w(.01)
}
}
show() {
noFill()
if (this.expandType === 'ellipses'){
push()
rotate(frameCount / 60)
stroke(35,20)
strokeWeight(w(.002))
irregExpand(this.radius,this.noiseVal)
irregExpand(this.radius*1.15,this.noiseVal*1.35)
irregExpand(this.radius*1.3,this.noiseVal*1.7)
irregExpand(this.radius*1.45,this.noiseVal*2)
irregExpand(this.radius*1.6,this.noiseVal*2.3)
irregExpand(this.radius*1.75,this.noiseVal*2.8)
pop()
}
}
}
Thanks in advance to anyone having a look.
the fact that you are printing the frameRate at each frame slows down your program significantly. You can replace:
console.log(frameRate())
by:
if(frameCount % 60 == 0)
console.log(frameRate())
to only print it every 60 frame.
I don't know if it solves your problem but on my side, it seems that it get rid of most of the freezing problem.
As you draw a lot of similar shapes, you should also try to compute an array of the points you need at the beginning and then reuse it at each frame and for each similar shape by scaling it by the right factor (Your code ran a lot faster when I removed the noise to draw circles only so I think what slows your code is the computation inside the BeginShape() block and not the BeginShape() itself).
So, at the moment I'm doing that, but the way my leaves are falling is weird, they're sticking to the same pattern as if they were still all stuck together, I know I could make them a class and give them random magnitude so each one of them would follow it's own path with its own velocity, but I am new to code and don't quite know how to do it, would there be a simpler way??? Thank you in advance (also it'd be nice to know if there is a way to add some time between the sprouting and the falling of the leaves).
var leaves = [];
var count = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
var a = createVector (width / 2, height);
var b = createVector (width / 2, height*3/4);
tree[0] = root;
}
function mousePressed() {
count++;
if (count % 3 === 0) {
for (var i = 0; i < tree.length; i++) {
if (!tree[i].finished) {
var leaf = tree[i].end.copy();
leaves.push(leaf);
}
}
function draw() {
background(51);
for (var i = 0; i < leaves.length; i++) {
fill(255, 204, 100);
noStroke();
ellipse(leaves[i].x, leaves[i].y, 2.7, 5);
leaves[i].x += random (0, 1.7);
leaves[i].y += random (0, 1.5);
}
}
I'm trying to create a little simulation with the help of HTML5 and Javascript using a canvas. My problem however is, I can't really think of a way to control the behavior of my pixels, without making every single pixel an object, which leads to an awful slowdown of my simulation.
Heres the code so far:
var pixels = [];
class Pixel{
constructor(color){
this.color=color;
}
}
window.onload=function(){
canv = document.getElementById("canv");
ctx = canv.getContext("2d");
createMap();
setInterval(game,1000/60);
};
function createMap(){
pixels=[];
for(i = 0; i <= 800; i++){
pixels.push(sub_pixels = []);
for(j = 0; j <= 800; j++){
pixels[i].push(new Pixel("green"));
}
}
pixels[400][400].color="red";
}
function game(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canv.width,canv.height);
for(i = 0; i <= 800; i++){
for(j = 0; j <= 800; j++){
ctx.fillStyle=pixels[i][j].color;
ctx.fillRect(i,j,1,1);
}
}
for(i = 0; i <= 800; i++){
for(j = 0; j <= 800; j++){
if(pixels[i][j].color == "red"){
direction = Math.floor((Math.random() * 4) + 1);
switch(direction){
case 1:
pixels[i][j-1].color= "red";
break;
case 2:
pixels[i+1][j].color= "red";
break;
case 3:
pixels[i][j+1].color= "red";
break;
case 4:
pixels[i-1][j].color= "red";
break;
}
}
}
}
}
function retPos(){
return Math.floor((Math.random() * 800) + 1);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script language="javascript" type="text/javascript" src="game.js"></script>
</head>
<body>
<canvas width="800px" height="800px" id="canv"></canvas>
</body>
</html>
So my two big questions are, what better way of controlling those pixels is there? And how can I speed up the pixel generation?
Hope you can help me.
Optimizing pixel manipulation
There are many options to speed up your code
Pixels as 32bit ints
The following will slug most machines with too much work.
// I removed fixed 800 and replaced with const size
for(i = 0; i <= size; i++){
for(j = 0; j <= size; j++){
ctx.fillStyle=pixels[i][j].color;
ctx.fillRect(i,j,1,1);
}
}
Don't write each pixel via a rect. Use the pixel data you can get from the canvas API via createImageData and associated functions. It uses typed arrays that are a little quicker than arrays and can have multiple view on the same content.
You can write all the pixels to the canvas in a single call. Not blindingly fast but a zillion times faster than what you are doing.
const size = 800;
const imageData = ctx.createImageData(size,size);
// get a 32 bit view
const data32 = new Uint32Array(imageData.data.buffer);
// To set a single pixel
data32[x+y*size] = 0xFF0000FF; // set pixel to red
// to set all pixels
data32.fill(0xFF00FF00); // set all to green
To get a pixel at a pixel coord
const pixel = data32[x + y * imageData.width];
See Accessing pixel data for more on using the image data.
The pixel data is not displayed until you put it onto the canvas
ctx.putImageData(imageData,0,0);
That will give you a major improvement.
Better data organization.
When performance is critical you sacrifice memory and simplicity to get more CPU cycles doing what you want and less doing a lot of nothing.
You have red pixels randomly expanding into the scene, you read every pixel and check (via a slow string comparison) if it is red. When you find one you add a random red pixel besides it.
Checking the green pixels is a waste and can be avoided. Expanding red pixels that are completely surrounded by other reds is also pointless. They do nothing.
The only pixels you are interested in are the red pixels that are next to green pixels.
Thus you can create a buffer that holds the location of all active red pixels, An active red has at least one green. Each frame you check all the active reds, spawning new ones if they can, and killing them if they are surrounded in red.
We don't need to store the x,y coordinate of each red, just the memory address so we can use a flat array.
const reds = new Uint32Array(size * size); // max size way over kill but you may need it some time.
You dont want to have to search for reds in your reds array so you need to keep track of how many active reds there are. You want all the active reds to be at the bottom of the array. You need to check each active red only once per frame. If a red is dead than all above it must move down one array index. But you only want to move each red only once per frame.
Bubble array
I dont know what this type of array is called its like a separation tank, dead stuff slowly moves up and live stuff moves down. Or unused items bubble up used items settle to the bottom.
I will show it as functional because it will be easier to understand. but is better implemented as one brute force function
// data32 is the pixel data
const size = 800; // width and height
const red = 0xFF0000FF; // value of a red pixel
const green = 0xFF00FF00; // value of a green pixel
const reds = new Uint32Array(size * size); // max size way over kill but you var count = 0; // total active reds
var head = 0; // index of current red we are processing
var tail = 0; // after a red has been process it is move to the tail
var arrayOfSpawnS = [] // for each neighbor that is green you want
// to select randomly to spawn to. You dont want
// to spend time processing so this is a lookup
// that has all the possible neighbor combinations
for(let i = 0; i < 16; i ++){
let j = 0;
const combo = [];
i & 1 && (combo[j++] = 1); // right
i & 2 && (combo[j++] = -1); // left
i & 4 && (combo[j++] = -size); // top
i & 5 && (combo[j++] = size); // bottom
arrayOfSpawnS.push(combo);
}
function addARed(x,y){ // add a new red
const pixelIndex = x + y * size;
if(data32[pixelIndex] === green) { // check if the red can go there
reds[count++] = pixelIndex; // add the red with the pixel index
data32[pixelIndex] = red; // and set the pixel
}
}
function safeAddRed(pixelIndex) { // you know that some reds are safe at the new pos so a little bit faster
reds[count++] = pixelIndex; // add the red with the pixel index
data32[pixelIndex] = red; // and set the pixel
}
// a frame in the life of a red. Returns false if red is dead
function processARed(indexOfRed) {
// get the pixel index
var pixelIndex = reds[indexOfRed];
// check reds neighbors right left top and bottom
// we fill a bit value with each bit on if there is a green
var n = data32[pixelIndex + 1] === green ? 1 : 0;
n += data32[pixelIndex - 1] === green ? 2 : 0;
n += data32[pixelIndex - size] === green ? 4 : 0;
n += data32[pixelIndex + size] === green ? 8 : 0;
if(n === 0){ // no room to spawn so die
return false;
}
// has room to spawn so pick a random
var nCount = arrayOfSpawnS[n].length;
// if only one spawn point then rather than spawn we move
// this red to the new pos.
if(nCount === 1){
reds[indexOfRed] += arrayOfSpawnS[n][0]; // move to next pos
}else{ // there are several spawn points
safeAddRed(pixelIndex + arrayOfSpawnS[n][(Math.random() * nCount)|0]);
}
// reds frame is done so return still alive to spawn another frame
return true;
}
Now to process all the reds.
This is the heart of the bubble array. head is used to index each active red. tail is the index of where to move the current head if no deaths have been encountered tail is equal to head. If however a dead item is encountered the head move up one while the tail remains pointing to the dead item. This moves all the active items to the bottom.
When head === count all active items have been checked. The value of tail now contains the new count which is set after the iteration.
If you were using an object rather than a Integer, instead of moving the active item down you swap the head and tail items. This effectively creates a pool of available objects that can be used when adding new items. This type of array management incurs not GC or Allocation overhead and is hence very quick when compared to stacks and object pools.
function doAllReds(){
head = tail = 0; // start at the bottom
while(head < count){
if(processARed(head)){ // is red not dead
reds[tail++] = reds[head++]; // move red down to the tail
}else{ // red is dead so this creates a gap in the array
// Move the head up but dont move the tail,
// The tail is only for alive reds
head++;
}
}
// All reads done. The tail is now the new count
count = tail;
}
The Demo.
The demo will show you the speed improvement. I used the functional version and there could be some other tweaks.
You can also consider webWorkers to get event more speed. Web worker run on a separate javascript context and provides true concurrent processing.
For the ultimate speed use WebGL. All the logic can be done via a fragment shader on the GPU. This type of task is very well suited to parallel processing for which the GPU is designed.
Will be back later to clean up this answer (got a little too long)
I have also added a boundary to the pixel array as the reds were spawning off the pixel array.
const size = canvas.width;
canvas.height = canvas.width;
const ctx = canvas.getContext("2d");
const red = 0xFF0000FF;
const green = 0xFF00FF00;
const reds = new Uint32Array(size * size);
const wall = 0xFF000000;
var count = 0;
var head = 0;
var tail = 0;
var arrayOfSpawnS = []
for(let i = 0; i < 16; i ++){
let j = 0;
const combo = [];
i & 1 && (combo[j++] = 1); // right
i & 2 && (combo[j++] = -1); // left
i & 4 && (combo[j++] = -size); // top
i & 5 && (combo[j++] = size); // bottom
arrayOfSpawnS.push(combo);
}
const imageData = ctx.createImageData(size,size);
const data32 = new Uint32Array(imageData.data.buffer);
function createWall(){//need to keep the reds walled up so they dont run free
for(let j = 0; j < size; j ++){
data32[j] = wall;
data32[j * size] = wall;
data32[j * size + size - 1] = wall;
data32[size * (size - 1) +j] = wall;
}
}
function addARed(x,y){
const pixelIndex = x + y * size;
if (data32[pixelIndex] === green) {
reds[count++] = pixelIndex;
data32[pixelIndex] = red;
}
}
function processARed(indexOfRed) {
var pixelIndex = reds[indexOfRed];
var n = data32[pixelIndex + 1] === green ? 1 : 0;
n += data32[pixelIndex - 1] === green ? 2 : 0;
n += data32[pixelIndex - size] === green ? 4 : 0;
n += data32[pixelIndex + size] === green ? 8 : 0;
if(n === 0) { return false }
var nCount = arrayOfSpawnS[n].length;
if (nCount === 1) { reds[indexOfRed] += arrayOfSpawnS[n][0] }
else {
pixelIndex += arrayOfSpawnS[n][(Math.random() * nCount)|0]
reds[count++] = pixelIndex;
data32[pixelIndex] = red;
}
return true;
}
function doAllReds(){
head = tail = 0;
while(head < count) {
if(processARed(head)) { reds[tail++] = reds[head++] }
else { head++ }
}
count = tail;
}
function start(){
data32.fill(green);
createWall();
var startRedCount = (Math.random() * 5 + 1) | 0;
for(let i = 0; i < startRedCount; i ++) { addARed((Math.random() * size-2+1) | 0, (Math.random() * size-2+1) | 0) }
ctx.putImageData(imageData,0,0);
setTimeout(doItTillAllDead,1000);
countSameCount = 0;
}
var countSameCount;
var lastCount;
function doItTillAllDead(){
doAllReds();
ctx.putImageData(imageData,0,0);
if(count === 0 || countSameCount === 100){ // all dead
setTimeout(start,1000);
}else{
countSameCount += count === lastCount ? 1 : 0;
lastCount = count; //
requestAnimationFrame(doItTillAllDead);
}
}
start();
<canvas width="800" height="800" id="canvas"></canvas>
The main cause of your slow down is your assumption that you need to loop over every pixel for every operation. You do not do this, as that would be 640,000 iterations for every operation you need to do.
You also shouldn't be doing any manipulation logic within the render loop. The only thing that should be there is drawing code. So this should be moved out to preferably a separate thread (Web Workers). If unable to use those a setTimeout/Interval call.
So first a couple of small changes:
Make Pixel class contain the pixel's coordinates along with the color:
class Pixel{
constructor(color,x,y){
this.color=color;
this.x = x;
this.y = y;
}
}
Keep an array of pixels that will end up creating new red pixels. And another one to keep track of what pixels have been updated so we know which ones need drawn.
var pixels = [];
var infectedPixesl = [];
var updatedPixels = [];
Now the easiest part of the code to change is the render loop. Since the only thing that it needs to do is draw the pixels it will be only a couple lines.
function render(){
var numUpdatedPixels = updatedPixels.length;
for(let i=0; i<numUpdatedPixels; i++){
let pixel = updatedPixels[i];
ctx.fillStyle = pixel.color;
ctx.fillRect(pixel.x,pixel.y,1,1);
}
//clear out the updatedPixels as they should no longer be considered updated.
updatedPixels = [];
//better method than setTimeout/Interval for drawing
requestAnimationFrame(render);
}
From there we can move on to the logic. We will loop over the infectedPixels array, and with each pixel we decide a random direction and get that pixel. If this selected pixel is red we do nothing and continue on. Otherwise we change it's color and add it to a temporary array affectedPixels. After which we test to see if all the pixels around the original pixel are all red, if so we can remove it from the infectedPixels as there is no need to check it again. Then add all the pixels from affectedPixels onto the infectedPixels as these are now new pixels that need to be checked. And the last step is to also add affectedPixels onto updatedPixels so that the render loop draws the changes.
function update(){
var affectedPixels = [];
//needed as we shouldn't change an array while looping over it
var stillInfectedPixels = [];
var numInfected = infectedPixels.length;
for(let i=0; i<numInfected; i++){
let pixel = infectedPixels[i];
let x = pixel.x;
let y = pixel.y;
//instead of using a switch statement, use the random number as the index
//into a surroundingPixels array
let surroundingPixels = [
(pixels[x] ? pixels[x][y - 1] : null),
(pixels[x + 1] ? pixels[x + 1][y] : null),
(pixels[x] ? pixels[x][y + 1] : null),
(pixels[x - 1] ? pixels[x - 1][y] : null)
].filter(p => p);
//filter used above to remove nulls, in the cases of edge pixels
var rand = Math.floor((Math.random() * surroundingPixels.length));
let selectedPixel = surroundingPixels[rand];
if(selectedPixel.color == "green"){
selectedPixel.color = "red";
affectedPixels.push(selectedPixel);
}
if(!surroundingPixels.every(p=>p.color=="red")){
stillInfectedPixels.push(pixel);
}
}
infectedPixels = stillInfectedPixel.concat( affectedPixels );
updatedPixels.push(...affectedPixels);
}
Demo
var pixels = [],
infectedPixels = [],
updatedPixels = [],
canv, ctx;
window.onload = function() {
canv = document.getElementById("canv");
ctx = canv.getContext("2d");
createMap();
render();
setInterval(() => {
update();
}, 16);
};
function createMap() {
for (let y = 0; y < 800; y++) {
pixels.push([]);
for (x = 0; x < 800; x++) {
pixels[y].push(new Pixel("green",x,y));
}
}
pixels[400][400].color = "red";
updatedPixels = [].concat(...pixels);
infectedPixels.push(pixels[400][400]);
}
class Pixel {
constructor(color, x, y) {
this.color = color;
this.x = x;
this.y = y;
}
}
function update() {
var affectedPixels = [];
var stillInfectedPixels = [];
var numInfected = infectedPixels.length;
for (let i = 0; i < numInfected; i++) {
let pixel = infectedPixels[i];
let x = pixel.x;
let y = pixel.y;
let surroundingPixels = [
(pixels[x] ? pixels[x][y - 1] : null),
(pixels[x + 1] ? pixels[x + 1][y] : null),
(pixels[x] ? pixels[x][y + 1] : null),
(pixels[x - 1] ? pixels[x - 1][y] : null)
].filter(p => p);
var rand = Math.floor((Math.random() * surroundingPixels.length));
let selectedPixel = surroundingPixels[rand];
if (selectedPixel.color == "green") {
selectedPixel.color = "red";
affectedPixels.push(selectedPixel);
}
if (!surroundingPixels.every(p => p.color == "red")) {
stillInfectedPixels.push(pixel);
}
}
infectedPixels = stillInfectedPixels.concat(affectedPixels);
updatedPixels.push(...affectedPixels);
}
function render() {
var numUpdatedPixels = updatedPixels.length;
for (let i = 0; i < numUpdatedPixels; i++) {
let pixel = updatedPixels[i];
ctx.fillStyle = pixel.color;
ctx.fillRect(pixel.x, pixel.y, 1, 1);
}
updatedPixels = [];
requestAnimationFrame(render);
}
<canvas id="canv" width="800" height="800"></canvas>
I'm in dire need of help with a graduation project that I'm working on. What I'm trying to achieve here in the code below is -to put it very simply- to initialize a 3d array of box() objects (this works) and then introduce another box() object (the end goal is to have an array of these as well) to move about within the same 3D grid. The moving unit picks a random location for initialization and a random target for destination (both within the 3D grid).
But the problem is that I need the moving unit not to overlap with the static base units, also move orthogonally, and always move one unit size each time. If I run the code below the moving unit reaches the target but without any of the restrictions that I mentioned above. It just takes the shortest vector and goes there regardless.
PS: Since p5js doesn't support wireframe in webgl mode I tried to show them with some transparency for better visual legibility. Although, the loadImage() functions are still there and could be replaced with any image of your liking to better differentiate units from one another.
I'd sincerely appreciate help with this since I'm also very short on time. Thanks in advance.
Here's the verifiable code:
var matrixSize = 5;
var locations = new Array(matrixSize);
var locBool = new Array(matrixSize);
//unit stuff
var unitSize = 40;
var units_a = []
var units_b;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
//3D array of location vectors & booleans
for(var i = 0; i < locations.length; i++){
locations[i] = new Array(matrixSize);
locBool[i] = new Array(matrixSize);
for(var j = 0; j < locations[i].length; j++){
locations[i][j] = new Array(matrixSize);
locBool[i][j] = new Array(matrixSize);
for(var k = 0; k < locations[i][j].length; k++){
locations[i][j][k] =
createVector(i*unitSize, j*unitSize, k*unitSize);
locBool[i][j][k] = false;
}
}
}
//base units
var threshold = 2; //decides on the percentage to be initialized
for (var i = 0; i < matrixSize; i++) {
for(var j = 0; j < matrixSize; j++){
for(var k = 0; k < matrixSize; k++){
stateRndm = random(10);
if(stateRndm <= threshold){
state = 1;
locBool[i][j][k] = true;
}else{
state = 0
}
units_a.push(new UnitOne(
i*unitSize,j*unitSize,k*unitSize, state));
}
}
}
units_b = new UnitTwo();
}
function draw() {
background(20);
ambientLight(235);
orbitControl();
rotateX(10);
rotateY(-10);
rotateZ(0);
//center the window and display the units
push();
translate(-unitSize*matrixSize/2, -unitSize*matrixSize/2, 0);
for(var i = 0; i < units_a.length; i++){
units_a[i].display();
}
units_b.display();
units_b.update();
units_b.move();
pop();
}
function UnitOne (x, y, z, state){
this.x = x;
this.y = y;
this.z = z;
this.state = state;
//this.img = loadImage("assets/tex_1.jpg");
//basic movement parameters
this.acceleration = createVector();
this.velocity = createVector();
this.location = createVector(this.x, this.y, this.z);
this.update = function(){
this.velocity.add(this.acceleration);
this.location.add(this.velocity);
this.acceleration.mult(0);
}
this.display = function(){
if(this.state == 1){
push();
scale(1);
//texture(this.img);
ambientMaterial(50, 200, 100, 20);
translate(this.x, this.y, this.z);
box(unitSize);
pop();
}
}
}
function UnitTwo() {
//assign random initial location
this.selector;
for(var i = 0; i < locations.length; i++){
for(var j = 0; j < locations[i].length; j++){
for(var k = 0; k < locations[i][j].length; k++){
this.selector = createVector(
floor(random(i))*unitSize,
floor(random(j))*unitSize,
floor(random(k))*unitSize);
}
}
}
print(this.selector);
//assign random target
this.targetSelector;
for(var i = 0; i < locations.length; i++){
for(var j = 0; j < locations[i].length; j++){
for(var k = 0; k < locations[i][j].length; k++){
this.targetSelector = createVector(
floor(random(i))*unitSize,
floor(random(j))*unitSize,
floor(random(k))*unitSize);
}
}
}
print(this.targetSelector);
//basic movement parameters
this.location = createVector(
this.selector.x,
this.selector.y,
this.selector.z);
this.acceleration = createVector();
this.velocity = createVector();
this.maxSpeed = 1;
this.maxForce = 2;
//this.img = loadImage("assets/tex_2.jpg");
this.display = function(){
push();
//texture(this.img);
ambientMaterial(200, 100, 40);
translate(this.location.x,
this.location.y, this.location.z);
scale(1);
box(unitSize);
pop();
}
this.update = function(){
this.velocity.add(this.acceleration);
this.location.add(this.velocity);
this.acceleration.mult(0);
}
}
UnitTwo.prototype.move = function(){
var target = createVector(this.targetSelector.x,
this.targetSelector.y,
this.targetSelector.z);
var desired = p5.Vector.sub(target, this.location);
var d = desired.mag();
//check the distance to slow down
if (d < unitSize/2)
desired.setMag(this.maxSpeed/2);
else
desired.setMag(this.maxSpeed);
var steer = p5.Vector.sub(desired, this.velocity);
steer.limit(this.maxForce);
this.acceleration.add(steer);
}
You've outlined both things you need to do. Which part of them are you stuck on?
Step 1: Make your unit move one cube at a time. You could do this by simply adding or subtracting 1 to its x, y, or z coordinate. Get this working first. Don't worry about avoiding collisions with the other cubes yet.
Step 2: When you get that working, add code that detects when the cube it's about to move to is occupied, and go in a different direction. You might have to implement a basic path finding algorithm, and Google is your friend for that.
You might also want to consider the possibility that your random generation has blocked all of the paths to the goal. What do you want to do in that case? And one more note: you don't need a triple-nested for loop just to generate a random value.
If you get stuck on one of these steps, please narrow your problem down to a MCVE showing just that step before you post again. Good luck.
I am working on a web audio stochastic oscillator and am having trouble with the scriptProcessorNode. My algorithm uses a random walk to determine dynamic breakpoints in the waveform and then interpolates between them.
As the breakpoints move on the x axis I thought the frequency of the oscillating waveform would change, but there is just a filtering effect, and the frequency seems to just be determined by the scriptProcessorNode buffer size, which must be a power of 2 between 256 and 16384.
How do you change the frequency of a scriptProcessorNode oscillator?
Here is my synthesis code:
scriptNode.onaudioprocess = function(audioProcessingEvent) {
walk(); //use random walk to generate new x/y position for each breakpoint
var outputBuffer = audioProcessingEvent.outputBuffer;
var lastPoint = 0;
var index = 0;
// linearly interpolate between the new breakpoint positions
for(var i = 0; i < breakpoint.length-1; i++) {
var y = breakpoint[lastPoint].y;
for(var channel = 0; channel <= 0;channel++) {
var outputData = outputBuffer.getChannelData(channel);
if(i != 0){
if(y >= breakpoint[i].y) {
while(y >= breakpoint[i].y) {
y = (breakpoint[i].m*index)+breakpoint[i].b;// y = m(x)+b
outputData[index] = y;
index++;
}
} else if(y <= breakpoint[i].y) {
while(y <= breakpoint[i].y) {
y = (breakpoint[i].m*index)+breakpoint[i].b;
outputData[index] = y;
index++;
}
}
}
}
lastPoint = i;
}
}
And here is a link to a working example: http://andrewbernste.in/bernie/gendy011.html
This is all based on Iannis Xenakis' GENDY stochastic synthesis program.
Thanks!
I solved the problem by using an index variable outside of my scriptNode.onaudioprocess function to write the waveform to the scriptNode buffer. That way the frequency at which the waveform is written to the buffer is not tied to the size of the buffer.
Here is the final code:
var index = 0;
var freq = 0.8;
scriptNode.onaudioprocess = function(audioProcessingEvent){
var outputBuffer = audioProcessingEvent.outputBuffer;
var outputData = outputBuffer.getChannelData(0);
for(var j = 0; j < outputData.length;j++){
// linearly interpolate between the new breakpoint positions
// get the interp point by comparing index to the x distance
var lerp = (index - breakpoint[point].x) / (breakpoint[point+1].x - breakpoint[point].x)
y = nx.interp(lerp,breakpoint[point].y,breakpoint[point+1].y);
if(point < breakpoint.length && index >= breakpoint[point+1].x) {
point++;
}
outputData[j] = y;
index+=freq;
if(index >= breakpoint[breakpoint.length-1].x){
index = 0;
point = 0;
walk();
}
}
}