How To Implement Javascript Polyline Collision Detection - javascript

I currently have a JavaScript platformer game. I have created a polygon-polygon collision detection with a response, using the SAT method portrayed on many other websites.
I have also created a function inside the Polygon constructor that will return either the sides exposed to the top of the polygon or the sides exposed to the bottom of the polygon, depending on which direction is chosen. The array that is returned contains many different Vectors, which will be explained more elaborately later.
The current code looks a bit like this:
Polygon.prototype.getSidesOn = function(dir) {
if (dir === "top" || dir === "bottom") {
var result = [], start = false, stop = false, elen =
this.calcPoints.length, e = 0, f = 0, point, next, prevX, prevY, directionX, directionY;
while (!stop) {
if (e >= 5*elen) {return;}
f = e%elen;
prev = f-1 < 0 ? this.calcPoints[elen-1] : this.calcPoints[f-1];
point = this.calcPoints[f];
prevX = directionX;
prevY = directionY;
directionX = point.x > prev.x ? "right" : point.x < prev.x ? "left" : directionX;
directionY = point.y < prev.y ? "up" : point.y > prev.y ? "down" : directionY;
if (prevX !== directionX && prevX && prevY) {
if (!start) {
start = dir === "top" ? directionY === "up" : directionY === "down";
} else {
break;
}
}
if (start) {
if (point.x !== prev.x) {
if (!result.length) {
result.push(new Vector(prev.x,prev.y),new
Vector(point.x,point.y));
} else {
result.push(new Vector(point.x,point.y));
}
} else {
break;
}
}
e++;
}
return result;
} else {
return;
}
}
I understand that it is a bit messy, but that is not a worry as I will optimise it later.
Basically, it works on ONLY convex polygons (I mean the word "convex" as in a triangle or a shape that does not go back into itself). Anyway, it works because of the fact that all convex polygons only have two points where the adjacent sides are facing the opposite X direction.
Anyway, the array that is outputted looks a bit like this:
//[Vector,Vector,Vector,Vector...]
that is not what it actually looks like, but basically, it outputs an array of Vectors for each point detected.
What I need to implement is a way to detect collision between two of these polylines. Unlike the polygon-polygon collision detection system, there does not need to be a response vector, and instead, the code should output either a "true" or "false" bool.
The only way that I can currently think of doing this is as follows:
function PolylinePolyline(line1,line2) {
var i, ilen = line1.length, j, jlen = line2.length;
for (i = 0; i < ilen; i++) {
for (j = 0; j < jlen; j++) {
var point1 = line1[i];
var point2 = i+1 < ilen ? line1[i+1] : line1[0];
var point3 = line2[j];
var point4 = j+1 < jlen ? line2[j+1] : line2[0];
var line01 = new line(point1,point2);
var line02 = new line(point3,point4);
if (LineLine(line01,line02) {
return true;
}
}
}
return false;
}
And although this works, it is very expensive to use if the lines are, let's say, 5 points long, and I have to detect collision between 4 of them. I would prefer an alternative that returns true if they are colliding and false if not. I will not explain why I need this, as it is a bit abstract but an example of a polyline would be this:
Note that the polylines are convex-shaped

You can speed up your collision search by defining (pseudo random) infinite length lines that go between two polygons such that the line splits up the polygons into 3 sets: above, below and colliding. Because they are dvidied by the line, you know that all above do not collide with all below. Repeat this a few times until you have a low number of undecided polygon pairs, then apply your old algorithm to the remaining pairs.
You can find (pseudo random) infinite length lines by calculating the normal at the middle of the line connecting the centers of two polygons in your set of polygons.

Related

AABB collision resolution slipping sides

So, I am currently reinventing the wheel (and learning a lot) by trying my hand at making a simple physics engine for my game engine. I have been searching the internet, trying (and failing) to fix my current problem. There are a lot of resources out there on the subject, but none of those that I have found seem to apply to my case.
THE PROBLEM IN SHORT: The collision resolution does not work as intended on some of the corners when two rectangles are colliding. How it fails varies based on the dimensions of the rectangles. What I am looking for is a "shortest overlap" kind of resolution for the collision or another fairly simple solution (I am open for suggestions!). (Scroll down for a better explaination and illustrations).
WARNING: The following code is probably not very efficient...
First of all, here is my physics loop. It simply loops through all of the game entities and checks if they collide with any other game entities. It is not efficient (n^2 and all of that), but it works for now.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target);
break;
}
}
}
}
}
}
}
},
Here is the code for the actual collision detection. This also seems to work alright.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() ||  entity.getRight() < target.getLeft() ||  entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is where the problems start to pop up. I want the entity to simply be "pushed" out of the target entity and have the velocity set to 0. This works fine as long as both the entity and the target are perfect squares. If let's say the entity (the player figure in the gif) is a rectangle, then the collision will "slipp" when colliding the longest sides (the X axis) with the target (the square). If I swap the player dimensions so that it is short and wide, then the same problem appears for the Y axis instead.
resolveTestTest: function(entity, target) {
var normalizedX = (target.getMidX() - entity.getMidX());
var normalizedY = (target.getMidY() - entity.getMidY());
var absoluteNormalizedX = Math.abs(normalizedX);
var absoluteNormalizedY = Math.abs(normalizedY);
console.log(absoluteNormalizedX, absoluteNormalizedY);
// The collision is comming from the left or right
if (absoluteNormalizedX > absoluteNormalizedY) {
if (normalizedX < 0) {
entity.pos.x = target.getRight();
} else {
entity.pos.x = target.getLeft() - entity.dim.width;
}
// Set velocity to 0
entity.vel.x = 0;
// The collision is comming from the top or bottom
} else {
if (normalizedY < 0) {
entity.pos.y = target.getBottom();
} else {
entity.pos.y = target.getTop() - entity.dim.height;
}
// Set velocity to 0
entity.vel.y = 0;
}
},
Collision on the Y axis works with these shapes
Collision on the X axis slips with these shapes
What can I do to fix this slipping problem? I have been bashing my head against this for the last 5 days, so I would be immensely grateful if some one could help push me in the right direction!
Thank you :)
-- EDIT: --
The slipping also happens if only moving in one direction along the left or right side.
-- EDIT 2 WORKING CODE: --
See my answer below for an example of the working code!
The important logical error you have made is this line:
if (absoluteNormalizedX > absoluteNormalizedY) {
This only works if both entities are square.
Consider a near-extremal case for your X-slipping example: if they almost touch at the corner:
Although the diagram is a little exaggerated, you can see that absoluteNormalizedX < absoluteNormalizedY in this case - your implementation would move on to resolve a vertical collision instead of the expected horizontal one.
Another error is that you always set the corresponding velocity component to zero regardless of which side the collision is on: you must only zero the component if is it in the opposite direction to the collision normal, or you won't be able to move away from the surface.
A good way to overcome this is to also record the collided face(s) when you do collision detection:
collideEntity: function(entity, target) {
// adjust this parameter to your liking
var eps = 1e-3;
// no collision
var coll_X = entity.getRight() > target.getLeft() && entity.getLeft() < target.getRight();
var coll_Y = entity.getBottom() > target.getTop() && entity.getTop() < target.getBottom();
if (!(coll_X && coll_Y)) return 0;
// calculate bias flag in each direction
var bias_X = entity.targetX() < target.getMidX();
var bias_Y = entity.targetY() < target.getMidY();
// calculate penetration depths in each direction
var pen_X = bias_X ? (entity.getRight() - target.getLeft())
: (target.getRight() - entity.getLeft());
var pen_Y = bias_Y ? (entity.getBottom() - target.getUp())
: (target.getBottom() - entity.getUp());
var diff = pen_X - pen_Y;
// X penetration greater
if (diff > eps)
return (1 << (bias_Y ? 0 : 1));
// Y pentration greater
else if (diff < -eps)
return (1 << (bias_X ? 2 : 3));
// both penetrations are approximately equal -> treat as corner collision
else
return (1 << (bias_Y ? 0 : 1)) | (1 << (bias_X ? 2 : 3));
},
updatePhysics: function(step) {
// ...
// pass collision flag to resolver function
var result = Vroom.collideEntity(entity, target);
if (result > 0) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target, result);
break;
}
}
// ...
}
Using a bit flag instead of a boolean array for efficiency. The resolver function can then be re-written as:
resolveTestTest: function(entity, target, flags) {
if (!!(flags & (1 << 0))) { // collision with upper surface
entity.pos.y = target.getTop() - entity.dim.height;
if (entity.vel.y > 0) // travelling downwards
entity.vel.y = 0;
}
else
if (!!(flags & (1 << 1))) { // collision with lower surface
entity.pos.y = target.getBottom();
if (entity.vel.y < 0) // travelling upwards
entity.vel.y = 0;
}
if (!!(flags & (1 << 2))) { // collision with left surface
entity.pos.x = target.getLeft() - entity.dim.width;
if (entity.vel.x > 0) // travelling rightwards
entity.vel.x = 0;
}
else
if (!!(flags & (1 << 3))) { // collision with right surface
entity.pos.x = target.getRight();
if (entity.vel.x < 0) // travelling leftwards
entity.vel.x = 0;
}
},
Note that unlike your original code, the above also allows corners to collide - i.e. for velocities and positions to be resolved along both axes.
MY WORKING CODE
So with some help and guidance from the amazing #meowgoesthedog I finally got on the right track and found what I was looking for. The problem (as #meowgoesthedog pointed out) was that my code was really only going to work with squares. The solution was to check the intersection of the colliding bodies and solve based on the shortest intersection. Note: this will probably not be a suitable solution if you need accurate physics with small and fast moving objects. The code for finding the intersection depth is based on this: https://github.com/kg/PlatformerStarterKit/blob/0e2fafb8dbc845279fe4116c37b6f2cdd3e636d6/RectangleExtensions.cs which is related to this project: https://msdn.microsoft.com/en-us/library/dd254916(v=xnagamestudio.31).aspx.
Here is my working code:
My physics loop has not been changed much, except for some better names for some functions.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveDisplace(entity, target);
break;
}
}
}
}
}
}
}
},
The collision detection remains the same as well.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() ||  entity.getRight() < target.getLeft() ||  entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is the code that basically fixes the problem. The comments in the code should explain what it does pretty well.
getIntersectionDepth: function(entity, target) {
// Calculate current and minimum-non-intersecting distances between centers.
var distanceX = entity.getMidX() - target.getMidX();
var distanceY = entity.getMidY() - target.getMidY();
var minDistanceX = entity.halfDim.width + target.halfDim.width;
var minDistanceY = entity.halfDim.height + target.halfDim.height;
// If we are not intersecting at all, return 0.
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY) {
return {
x: 0,
y: 0,
};
}
// Calculate and return intersection depths.
var depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
var depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return {
x: depthX,
y: depthY,
};
},
Here is the updated resolving function. It now takes intersection depth in to account when determining axis of collision and then uses the sign of the intersection depth for the colliding axis when determining the direction to resolve.
resolveDisplace: function(entity, target) {
var intersection = Vroom.getIntersectionDepth(entity, target);
if (intersection.x !== 0 && intersection.y !== 0) {
if (Math.abs(intersection.x) < Math.abs(intersection.y)) {
// Collision on the X axis
if (Math.sign(intersection.x) < 0) {
// Collision on entity right
entity.pos.x = target.getLeft() - entity.dim.width;
} else {
// Collision on entity left
entity.pos.x = target.getRight();
}
entity.vel.x = 0;
} else if (Math.abs(intersection.x) > Math.abs(intersection.y)) {
// Collision on the Y axis
if (Math.sign(intersection.y) < 0) {
// Collision on entity bottom
entity.pos.y = target.getTop() - entity.dim.height;
} else {
// Collision on entity top
entity.pos.y = target.getBottom();
}
entity.vel.y = 0;
}
}
},
Thank you all for your help!
The problem may be that you're correcting both X and Y collision based on the same position:
Player is at a certain position. Let's check collision.
Player's bottom right corner overlaps top left corner of object.
X position is corrected: Player is moved to the left.
Player's bottom right corner overlaps top left corner of object.
Y position is corrected: Player is moved up.
End result: The player is moved up and to the left.
You probably need to "get" the player's position again, between checks.

HTML5 Canvas, better pixel control and better speed

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>

Using arrays for tetris pieces and grid. Why won't this piece show up completely?

Here's the code, below is a link to the jsfiddle.
The first row of the block isn't drawn for some reason, all these 2 dimension for loops are hard for me to wrap my head around, and can't find the reason why the block is only being drawn from the second row and not complete.
The add_block function is supposed to read array data from any block and put it on the grid where my x and y coordinates are.
If there's anyone who knows how to rotate the block, that would be cool too, i know in order to turn something +90 degrees i need to transpose and then revese each row, but it hasn't really worked in earlier tries.
I know i'm not good at explaining but i'll be sure to answer any of your questions.
Thanks in advance! I really want to have a complete picture of how arrays and double for loops interact with eachother, in my head.
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 500;
var grid_columns = 10;
var grid_rows = 15;
var grid_cell_size = 10;
var grid = [];
function create_empty_grid(){
for(var i=0;i<grid_columns;i++){
grid[i] = [];
for(var j=0;j<grid_rows;j++){
grid[i][j] = 0;
}
}
// END DOUBLE FOR LOOPS
}
function clear_grid(){
for(var i=0;i<grid_columns;i++){
for(var j=0;j<grid_rows;j++){
grid[i][j] = 0;
}
}
// END DOUBLE FOR LOOPS
}
var x = 0;
var y = 0;
var w = 2;
var h = 3;
var block = [];
block[0] = [
[1,0,0,0],
[1,1,0,0],
[0,1,0,0],
[0,0,0,0]
];
function add_block(num){
var b = block[num];
for(var i=0;i<w;i++){
for(var j=0;j<h;j++){
if(i >= x && j >= y && i <= w && j <= h){
grid[i][j] = b[i][j];
}
}
}
// END DOUBLE FOR LOOPS
}
function draw(){
for(var i=0;i<grid_columns;i++){
for(var j=0;j<grid_rows;j++){
ctx.fillStyle = "black";
if(grid[i][j] === 1){
ctx.fillStyle = "red";
}else if(grid[i][j] === 0){
ctx.fillStyle = "green";
}
ctx.fillRect(i*grid_cell_size,j*grid_cell_size,grid_cell_size-1,grid_cell_size-1);
}
}
// END DOUBLE FOR LOOP
}
function update(){
}
function tick(){
clearRect(0,0,canvas.width,canvas.height);
clear_grid();
draw();
update();
}
create_empty_grid();
add_block(0);
draw();
View in jsfiddle
It seems like you're using i and j to represent "block" coordinates, and
x and y to represent "grid" coordinates. So I think this condition is wrong:
if(i >= x && j >= y && i <= w && j <= h){
grid[i][j] = b[i][j];
}
I think all you really need here is to replace the if statement with something like:
grid[i+x][j+y] = b[i][j];
But as #Arnauld pointed out, there is also a bit of confusion about whether i,j denotes "row, column" or "column, row", and it looks like your usage is opposite from the way the arrays are initialized. In other words, you're using:
grid[row][column] and b[row][column]
but the way you laid out the arrays, it needs to be
grid[column][row] and b[column][row]
So you'll need to make a few adjustments here and there to make the code do what you want.
The "b" arrays were upside-down for some reason so i changed grid[j+x][i+y] = b[j][i] to grid[j+x][i+y] = b[i][j] this displays the full block, but i still can't visualise the arrays or be able to prevent these problems in the future.
Arrays are so confusing to me.
I'll figure it out eventually i guess, but a day has to go by.

How do I determine a movable area in game squares

I'm building a turn based HTML game based on a 2D square grid. Each grid square could take a variable number of movement points to cross (IE: 1 MP for roads, 1.5 MP for grasslands, 2 MP for forests, etc). When the user clicks on a unit I want to determine all possible movable spaces with said unit's allotted movement points so that I can highlight them and make them clickable.
Is there a free library available to do this? I've seen a few pathing algorithms but nothing about determining movable area. How do other game developers handle this problem? I'm open to both vanilla JS and JQuery solutions.
Well, I decided to try and attack this myself. I've never been great at these sorts of algorithms so I'm sure there's a more efficient way to handle it than what I've done. However, for my purposes it runs quickly enough and is very simple and easy to understand.
In case it's helpful to anyone else looking to do the same, I've included the code below. This is an updated version of my original answer, which I modified to also store the path taken so that you can show the units moving through the correct spaces. This answer uses JQuery in the lower examples, but only in a few places; you can easily enough replace them with vanilla JS. And the first block of code, containing the actual path/area finding functionality, is pure JS.
<script>
var possibleMovementAreaArray = new Array(); // This array will hold our allowable movement tiles. Your other functions can access this after running possibleMovementArea().
function possibleMovementArea(unitIndex) {
// I'm storing each unit in my game in an array. So I pass in the index of the unit I want to determine the movement area for.
var x = unitList[unitIndex][10]; // x coordinate on the playgrid
var y = unitList[unitIndex][11]; // y coordinate on the playgrid
var mp = unitList[unitIndex][15]; // number of movement points
possibleMovementAreaArray.length = 0; // Clear our array so previous runs don't interfere.
findPossibleMovement(x, y, mp);
}
function findPossibleMovement(x, y, mp, prevStepX, prevStepY) {
// This is a recursive function; something I'm not normally too good at.
for (var d=1; d<=4; d++) {
// We run through each of the four cardinal directions. Bump this to 8 and add 4 more cases to include corners.
if (d == 1) {
// Check Up
var newX = x;
var newY = y - 1;
} else if (d == 2) {
// Check Down
var newX = x;
var newY = y + 1;
} else if (d == 3) {
// Check Left
var newX = x - 1;
var newY = y;
} else if (d == 4) {
// Check Right
var newX = x + 1;
var newY = y;
}
// Check to see if this square is occupied by another unit. Two units cannot occupy the same space.
spaceOccupied = false;
for (var j=1; j<=numUnits; j++) {
if (unitList[j][10] == newX && unitList[j][11] == newY)
spaceOccupied = true;
}
if (!spaceOccupied) {
// Modify this for loop as needed for your usage. I have a 2D array called mainMap that holds the ID of a type of terrain for each tile.
// I then have an array called terList that holds all the details for each type of terrain, such as movement points needed to get past.
// This for loop is just looking up the ID of the terrain for use later. Sort of like a "SELECT * FROM terrainInfo WHERE ID=terrainOfCurrentTile".
for (var j=1; j<=numTerrains; j++) {
if (newX > 0 && newX <= mapWidth && newY > 0 && newY <= mapHeight && terList[j][1] == mainMap[newX][newY])
break; // After finding the index of terList break out of the loop so j represents the correct index.
}
if (j <= numTerrains) { // Run if an actual terrain is found. No terrain is found if the search runs off the sides of the map.
var newMp = mp - terList[j][7]; // Decrement the movement points for this particular path.
if (newMp >= 0) { // Only continue if there were enough movement points to move to this square.
// Check to see if this square is already logged. For both efficiency and simplicity we only want each square logged once.
var newIndex = possibleMovementAreaArray.length
var alreadyLogged = false
if (possibleMovementAreaArray.length > 0) {
for (var j=0; j<possibleMovementAreaArray.length; j++) {
if (possibleMovementAreaArray[j][1] == newX && possibleMovementAreaArray[j][2] == newY) {
alreadyLogged = true;
var alreadyLoggedIndex = j;
}
}
}
if (!alreadyLogged) {
// This adds a row to the array and records the x and y coordinates of this tile as movable
possibleMovementAreaArray[newIndex] = new Array(6);
possibleMovementAreaArray[newIndex][1] = newX;
possibleMovementAreaArray[newIndex][2] = newY;
possibleMovementAreaArray[newIndex][3] = prevStepX; // This tracks the x coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][4] = prevStepY; // This tracks the y coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][5] = newMp; // Records remaining MP after the previous steps have been taken.
}
if (alreadyLogged && newMp > possibleMovementAreaArray[alreadyLoggedIndex][5]) {
// If this tile was already logged, but there was less MP remaining on that attempt, then this one is more efficient. Update the old path with this one.
possibleMovementAreaArray[alreadyLoggedIndex][3] = prevStepX;
possibleMovementAreaArray[alreadyLoggedIndex][4] = prevStepY;
possibleMovementAreaArray[alreadyLoggedIndex][5] = newMp;
}
if (newMp > 0) {
// Now update the list of previous steps to include this tile. This list will be passed along to the next call of this function, thus building a path.
if (prevStepX == '') {
var newPrevStepX = [newX];
var newPrevStepY = [newY];
} else {
// This code is required to make a full copy of the array holding the existing list of steps. If you use a simple equals then you just create a reference and
// subsequent calls are all updating the same array which creates a chaotic mess. This way we store a separate array for each possible path.
var newPrevStepX = prevStepX.slice();
newPrevStepX.push(newX);
var newPrevStepY = prevStepY.slice();
newPrevStepY.push(newY);
}
// If there are still movement points remaining, check and see where we could move next.
findPossibleMovement(newX, newY, newMp, newPrevStepX, newPrevStepY);
}
}
}
}
}
}
</script>
After running the above, you can then loop through the array to find all usable tiles. Here is how I did it:
<script>
// Shows the movement area based on the currently selected unit.
function showMovement() {
var newHTML = "";
curAction = "move";
possibleMovementArea(curUnit); // See above code
for (x=0; x<possibleMovementAreaArray.length; x++) {
// Loop over the array and do something with each tile. In this case I'm creating an overlay that I'll fade in and out.
var tileLeft = (possibleMovementAreaArray[x][1] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
var tileTop = (possibleMovementAreaArray[x][2] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
newHTML = newHTML + "<img id='path_" + possibleMovementAreaArray[x][1] + "_" + possibleMovementAreaArray[x][2] + "' onClick='mapClk(" + possibleMovementAreaArray[x][1] + ", " + possibleMovementAreaArray[x][2] + ", 0);' src='imgs/path.png' class='mapTile' style='left:" + tileLeft + "px; top:" + tileTop + "px;'>";
}
$("#movementDiv").html(newHTML); // Add all those images into a preexisting div.
$("#movementDiv").css("opacity", "0.5"); // Fade the div to 50%
$("#movementDiv").show(); // Make the div visible.
startFading(); // Run a routine to fade the div in and out.
}
</script>
Since we determined the path, we can easily show movement as well by looping through the stored information:
<script>
for (j=0; j<possibleMovementAreaArray[areaIndex][3].length; j++) {
// This loop moves the unit img to each tile on its way to its destination. The final destination tile is not included.
var animSpeed = 150; // Time in ms that it takes to move each square.
var animEase = "linear"; // We want movement to remain a constant speed through each square in this case.
var targetLeft = (possibleMovementAreaArray[areaIndex][3][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new horizonal position.
var targetTop = (possibleMovementAreaArray[areaIndex][4][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new vertical position.
$("#char_"+curUnit).animate({"left":targetLeft, "top":targetTop}, animSpeed, animEase); // Do the animation. Subsequent animations get queued.
}
// Now we need to move to that last tile.
newLeft = (x-1) * mapTileSize;
newTop = (y-1) * mapTileSize;
$("#char_"+curUnit).animate({"left":newLeft, "top":newTop}, 400, "easeOutCubic"); // Slow unit at the end of journey for aesthetic purposes.
$("#char_"+curUnit).addClass("unitMoved", 250); // Turns the image grayscale so it can easily be seen that it has already moved.
</script>
Hopefully this is helpful to others.

Improve function speed

I'm doing a BattleShip game in javascript with iio engine.
I'm trying to play against a computer so I have to put a random position for the ships (I hope you know the game :) ).
I have 5 ships that have to be placed in a grid (10x10). The problem is that the function is pretty slow, and sometimes the page don't get load at all.
I want to know if there are some emprovement for the speed of these function, I'm a little bit newbie :D
function posShips(size){
// var size -> size of the ship
var isOk = false; // flag var to check if the ship is in a right position
var isOk2 = true; // flag var, become false if the cell is already fill with another ship
var i;
var j;
var side; // horizontal or vertical
while(!isOk){
i = iio.getRandomInt(1,11);
j = iio.getRandomInt(1,11);
side = iio.getRandomInt(0,2);
if((side ? j : i)+size-1 < 11){ // Not out of the array
for (var k = 0; k < size; k++) { // Size of the ship
if(side){
if(gridHit[i][j+k].stat == "empty"){ //If is empty put the ship
gridHit[i][j+k].stat = "ship";
gridHit[i][j+k].setFillStyle("red")
}else{ // If not empty
isOk2 = false; //Position is not good, do all the thing again.
for (var a = 0; a < size; a++) { // Reset cell
gridHit[i][j+a].stat = "empty";
}
k = 10;
}
}else{
if(gridHit[i+k][j].stat == "empty"){ //If is empty put the ship
gridHit[i+k][j].stat = "ship";
gridHit[i+k][j].setFillStyle("red")
}else{ // If not empty
isOk2 = false; //Position is not good, do all the thing again.
for (var a = 0; a < size; a++) { // Reset cell
gridHit[i+a][j].stat = "empty";
}
k = 10;
}
}
};
if(isOk2)
isOk = true;
}
}
}
Don't pick ship positions that will fall outside the grid. Pick the direction first, and then limit the x and y initial positions based on size. e.g. if the size is 3, there's no point going above 7 for the initial value of the varying coordinate.
Don't change the array while you're searching. Do the search first, and only afterwards update the array. This avoids any "cleanup" operation.
Wherever possible, eliminate repeated deep object references. If accessing grid[y][x] repeatedly for differing x, take a reference to grid[y] first, and then use that for the subsequent accesses.
Break out of loops early, there's no point continuing to test a position if a previous one already failed.
Place your big ships first - it's easier to fit small ships into the gaps left between the big ones.
See http://jsfiddle.net/alnitak/Rp9Ke/ for my implementation, with the equivalent of your function being this:
this.place = function(size) {
// faster array access
var g = this.grid;
// initial direction, and vector
var dir = rand(2); // 0 - y, 1 - x
var dx = dir ? 1 : 0;
var dy = dir ? 0 : 1; // or 1 - dx
LOOP: while (true) {
// initial position
var x = dir ? rand(10 - size) : rand(10);
var y = dir ? rand(10) : rand(10 - size);
// test points
var n = size, tx = x, ty = y;
while (n--) {
if (g[ty][tx]) continue LOOP;
tx += dx;
ty += dy;
}
// fill points
n = size;
while (n--) {
g[y][x] = size;
x += dx;
y += dy;
}
break;
}
};

Categories