I wrote some code in p5.js to see if i can properly make a collision detection system but when i put more than 2 squares in, squares seem to bump each other inside of other squares. I'd like to know if there's anyway to stop this plus, if you have any good pointers on how to do tidy/shorten my code id like to hear them.
My code:
var r; //later defined as an array for the squares
var num; //number of squares
function setup(){
r = [];
num = 10;
createCanvas(windowWidth,windowHeight- 4);
for(var i = 0;i < num; i++){
r[i] = new Box(random(width-40),random(height-40),40,40);
}
}
function draw(){
background(40);
for(var i = 0;i < num; i++) {
r[i].show();
for(var j = 0;j<num; j++){
//this is the if statement evaluating if the left and right of the square is touching each other. i is one square and j is the other. you see in each if statement i have the acceleration being added, this is because if it wasn't then they would be true if the squares were touching each other on any side
if(r[i].right+r[i].xa >= r[j].left && r[i].bottom >= r[j].top && r[i].top <= r[j].bottom && r[i].left + r[i].xa <= r[j].right){
r[i].xa *= -1;
r[j].xa *= -1;
}
//this is also just as confusing just read through it carefully
if(r[i].bottom + r[i].ya >= r[j].top && r[i].right >=r[j].left && r[i].left <= r[j].right && r[i].top + r[i].ya <= r[j].bottom){
r[i].ya *= -1;
r[j].ya *= -1;
}
}
}
}
function Box(x, y, wid, hei){
this.x = x;//input for square shape
this.y = y;//ditto
this.width = wid;//ditto
this.height= hei;//ditto
this.xa = random(2,5);//xa is the x acceleration
this.ya = random(2,5);//ya is the y acceleration
this.left;
this.right;
this.top;
this.bottom;
this.show = function(){
this.left = this.x; //i define left,right,top,bottom in show function so they get updated
this.right = this.x +this.width;
this.top = this.y;
this.bottom = this.y +this.height;
push();
fill(255);
noStroke();
rect(this.x,this.y,this.width,this.height);
pop();//push pop just in case i want to change square colors individually in the future
this.x += this.xa;//adding acceleration to the squares
this.y += this.ya;//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
if(this.x > width-this.width||this.x <0){//bouncing off the right and left wall
this.xa *= -1;
if(this.x > width/2){// making sure if the square spawns or glitches on the other side of the wall it doesn't get stuck, this checks which side the square is on when it touches the wall then moves it directly on the wall
this.x = width-this.width;
}else{
this.x = 0;
}
}
if(this.y > height-this.height||this.y <0){// same as above but for the y axis
this.ya *= -1;
if(this.y > height/2){
this.y = height-this.height;
}else{
this.y = 0;
}
}
}
}
function windowResized(){
createCanvas(windowWidth,windowHeight- 4);//window resizing adjustment
}
you can view it using this.
just copy and paste.
The solution to the unsolvable
Sorry no such thing
Collision solutions are not easy when you have many moving objects in the scene.
Your immediate problem
Your problem if mainly because you are making an assumption on the box's direction of travel when they collide. You multiply the direction by -1 to reverse direction.
All good for 2 objects, but add a 3rd and you will end up with the 3 coming together. Each in turn you change the direction, box1 hits box2 both move away from each other, then in the same frame box1 hits box3 and now box1 and box3 are moving apart.Your speeds are constant so after a three way collision there will always be 2 boxes traveling in the same direction but overlapping.
The overlapping boxes on the next frame detect the overlap and both reverse direction, as they are already traveling in the same direction the direction switch does not help them move apart.
A step forward
Well a step apart, The following modification to the code just ensures that when possible a collision results in the box move away from each other.
function draw() {
background(40);
for (var i = 0; i < num; i++) {
const bx1 = r[i];
r[i].show();
for (var j = 0; j < num; j++) {
if (j !== i) {
// t for top, b for bottom, r for right and l for left. 1 for first box 2 for second
// bx for box
const bx2 = r[j];
const t1 = bx1.top + bx1.ya;
const b1 = bx1.bottom + bx1.ya;
const l1 = bx1.left + bx1.xa;
const r1 = bx1.right + bx1.xa;
const t2 = bx2.top + bx2.ya;
const b2 = bx2.bottom + bx2.ya;
const l2 = bx2.left + bx2.xa;
const r2 = bx2.right + bx2.xa;
// the or's mean that the condition will complete at the first passed clause
// If not (not over lapping) AKA is overlapping
if (!(t1 > b2 || b1 < t2 || l1 > r2 || r1 < l2)) {
if (r1 >= l2) {
bx1.xa = -Math.abs(bx1.xa);
bx2.xa = Math.abs(bx2.xa);
}
if (l1 <= r2) {
bx1.xa = Math.abs(bx1.xa);
bx2.xa = -Math.abs(bx2.xa);
}
if (b1 >= t2) {
bx1.ya = -Math.abs(bx1.ya);
bx2.ya = Math.abs(bx2.ya);
}
if (t1 <= b2) {
bx1.ya = Math.abs(bx1.ya);
bx2.ya = -Math.abs(bx2.ya);
}
}
}
}
}
}
But that only moves the problem away from overlapping, now there are many collision that are wrong as there is no test to determine the point of collision
In the above code you are trying to solve from an unsolvable position. Boxes in real life never overlap. Boxes in real life will slow down and speed up. perfectly flat sides will never collide with more than on side at a time.
To do this you will need to use integration. Its not that hard and is just a process of dividing time into smaller steps. Collide, move, check for overlap, move apart then back to collide.
Verlet integration
Also verlet integration will make it easier. Rather than store a boxes speed as a vector you store the current position and the previous position.
box.x = 10;
box.y = 10;
box.ox = 8; // the boxes old position
box.oy = 8;
You move a box as follows
sx = box.x - box.ox;
sy = box.y - box.oy;
box.ox = box.x;
box.oy = box.y;
box.x += sx; // the boxes old position
box.y += sy;
When you hit something you need to change the old position so as to give the next iteration the correct direction
if(box.y > ground){
box.y = ground - (box.y - ground); // move away from ground same dist as moved into ground
box.oy = box.y -sy;
}
Do them all in groups.
Move all at once, then test for collision at once. Dont move and test one at a time.
Verlet integration is much more forgiving as it lets speed of movement absorb some of the error. Rather than be all in position as the standard vector method does.
Related
In this example we use math functions to create a blurred copy of an image. The way we will blur the image is by scrambling pixels that are near each other.
We begin by creating a blank image and writing the loop to let us color each pixel in the image. For each pixel we will do one of two things: half the time, we will simply copy the pixel from the old picture into the new picture without changing anything. The other half of the time we will find a pixel nearby and copy that one instead.
Now we must figure out how to find a "nearby" pixel. We will define some value for how far away the new pixel will be (we used 10 pixels) and then we write a function that will give a (x,y) point that is a random amount between 0 and 10 pixels away in each direction. Before we use the new (x,y) point, we must check to be sure it is still a valid pixel position in the image. For example, we may be at a pixel that is on the very top of the image. Our random point generator tells us to go up by 3 pixels, but since we are on the top of the image (y = 0) we cannot very well go up by three pixels (y would be -3)! If the random number is too big (larger than the dimension -1) or too small (less than 0) then we will just use the closest number that is valid.
Once we have a valid pixel that is some amount away we use its red, green, and blue values as the new pixel's values.
function nearby(image, x, y) {
var newX = x + Math.random() * 10 - 5;
if (newX > image1.getWidth()) {
newX = newX - 5;
}
if (newX < 0) {
newX = newX + 5;
} else {
newX = newX;
}
var newY = y + Math.random() * 10 - 5;
if (newY > image1.getHeight()) {
newY = newY - 5;
}
if (newY < 0) {
newY = newY + 5;
} else {
newY = newY;
}
return image.getPixel(newX, newY);
}
function blur(image) {
for (var pixel of image.values()) {
x = pixel.getX();
y = pixel.getY();
var orgPixel = image.getPixel(x, y);
if (Math.random() < 0.5) {
var other = nearby(image, x, y);
output.setPixel(x, y, other);
} else {
output.setPixel(x, y, orgPixel);
}
}
return output;
}
var image1 = new SimpleImage("duvall.jpg");
var output = new SimpleImage(image1.getWidth(), image1.getHeight());
blur(image1);
print(output);
----i used one of the pictures in my file but any picture should do.
So lately I've been trying to make a 2D Zelda-like game. I want to make a camera to follow the player.
So I looked at https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate, https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations, and some others in my search (MDN has an article on it but I couldn't follow although it didn't look like what I was looking for).
I also didn't want to just center the player, I want to have a camera which has a limit, so you have to go a certain amount outside of the camera for the map to start scrolling.
function camera(data) {
var x, y;
if(I.x <= 2 && I.x >= -2 && I.y <= 2 && I.y >= -2) { x = 0; y = 0;}
if(I.x > 2) { x = -I.size; y = 0; }
if(I.x < -2) { x = I.size; y = 0; }
if(I.y > 2) { x = 0; y = -I.size; }
if(I.y < -2) { x = 0; y = I.size; }
ctx.translate(x, y);
draw.map();
draw.camera();
draw.players(data);
ctx.resetTransform();
}
draw.map() draws the tiles.
draw.camera() draws a little dotted box so I know the boundary of the camera.
draw.players(data) draws every player.
I.size refers to the size of each tile(16 in this case).
I.x & I.y are self explanatory.
I do have a working version(uses node and socket.io):
http://dais-jaackotorus.codeanyapp.com:8080/
EDIT:
Almost forgot! The problem with this code is that it follows the player for only one tile and then it doesn't any longer, and it goes outside of the camera range instead of staying inside and I dont understand why.
Here's a simplified example:
https://jsfiddle.net/2xbo0kas/
The trick is to start drawing the world around the player. So, in the jsfiddle, you can see the player is stationary but the map moves, so that the player is always centered into the viewport.
What the fiddle does not show is the final position of the player once you reach the edge of the map (where you'd draw a stationary map but update the player rectangle).
function draw() {
var startx = Math.max([player.x - size.width], 0);
var endx = Math.min(startx + size.width, map.length);
var starty = Math.max([player.y - size.height], 0);
var endy = Math.min(starty + size.height, map[0].length);
for (var x = startx; x < endx; x++) {
for (var y = starty; y < endy; y++) {
var drawx = x - startx;
var drawy = y - starty;
//draw tile
}
}
//draw player
}
So I am rendering a Polyline with the Y-Values of a sin wave with the code below
var amplitude = 50;
var dx = (TWO_PI / period) * 10
var yValues = new Array(floor(widthOfWave / xSpacing));
var poly = [];
this.calculate = () => {
//Increment theta
theta += 0.02;
//For every x value, calculate the y value with SIN function
var x = theta;
for(var i = 0; i < yValues.length; i++) {
yValues[i] = sin(x) * amplitude;
x += dx;
}
this.render = () => {
this.calculate();
ellipseMode(CENTER);
beginShape();
for(var i = 0; i < yValues.length; i++) {
var temp = createVector((i * spacing), windowWidth + yValues[i]);
curveVertex(temp.x, temp.y);
poly.push(temp);
}
endShape();
}
Which renders the wave
This is exactly what I want, but the problem I am having is when I try to incorporate p5.collide2d (Github Link Here). I want to have an ellipse, the 'Player' in this case, be able to ride the wave by holding left and right on the keyboard arrows. I haven't gotten to the keyboard interaction, because I am currently stuck on having the Ellipse (a perfect circle) not just falling through the curve at sometimes.
Here is my code for the current collision I am testing with.
this.checkCollision = (objX, objY, objSize) => {
var hit = false;
var hit = collideCirclePoly(objX, objY, objSize, poly);
return hit;
}
//Check for the collision
var hit = hill.checkCollision(player1.x, player1.y, player1.size);
if(hit) player1.didCollide();
//Player's didCollide function
this.didCollide = () => {
newSpeed = this.ySpeed * -0.8;
this.ySpeed = newSpeed;
}
This is how the circle (the "Player") and the wave intereact whenever I try to run it though.
I can't seem to figure out why the interaction is happening this way. I have tried extending the bounds of the collision, but it just makes it appear very glitchy and it still sometimes just passes through the wave with what appears to be no collision.
I am fairly new to p5.js and processing, so I am most likely missing something very simple. Thanks for your help ahead of time!
Edited title and here to reflect my question better.
My first description didn't get answers of the kind I was looking for and I didn't know there was a fancy name for this kind of shape I'm trying to make.
I studied Graham Scan algorithms but they don't completely cover the answer I'm looking for either, as they tend to "cut corners" if the shape becomes strange say.
Based on some articles I found, I'd started writing my own algorithm and it works a lot of the cases to be sure but I keep coming into problems I cant seem to completely stamp out, I believe the problem might be where I'm trying to calculate each of the corners.
This is the logic of my algorithm -
1st = Find the corners, top-left, top-right, bottom-right, bottom-left
2nd = Enter a while loop, travelling towards a target, starting from top-left travelling to top-right then to bottom right to bottom left and back to top left.
3rd = in each iteration of this while loop, we find the neighbours of each tile if they exist in this list, and choose the next tile to travel to, based on which comes first in an order decided by priority.
Priority explained, if the priority is to get to the top right corner, we take our time by first trying to travel to the left, then up, right and down but ONLY if a tile in this direction exists and ONLY if this tile isn't the previous tile we were standing at.
After we arrive at the top right corner, we shift the priority list making the first priority become the last. So
priority = [left, up, right, down]
becomes
priority = [up, right, down, left]
In the majority of cases my code IS performing as expected, like the logic seems sound, but there are a few anomaly moments where it doesn't seem to pick the right tile based on the priority I'm giving it and instead it wanders about the whole array. If anyone can help me where I'm making a mistake I'd appreciate this.
Heres my current algorithm code >
draw_hull = function() {
var list = [],
coordinates = [],
original_list = this.territory,
_x = 0, // coords are in arrays, [x,y] style.
_y = 1; // this is just for readers readability
// make list to be looped through, for each tile I deliberately added 4,
//it'll look more readable in the final product.
for (var i = 0; i < original_list.length; i++) {
list.push( [ (original_list[i][_x] * 32) + 8, (original_list[i][_y] * 32) + 8 ] );
list.push( [ (original_list[i][_x] * 32) + 24, (original_list[i][_y] * 32) + 8 ] );
list.push( [ (original_list[i][_x] * 32) + 24, (original_list[i][_y] * 32) + 24 ] );
list.push( [ (original_list[i][_x] * 32) + 8, (original_list[i][_y] * 32) + 24 ] );
}
// find corners
var topleft = 0, topright = 0, bottomright = 0, bottomleft = 0, hull = [];
for (var i = 0; i < list.length; i++) {
var x = list[i][_x];
var y = list[i][1];
if (x <= list[topleft][_x] && y <= list[topleft][_y]) topleft = i;
if (x >= list[topright][_x] && y <= list[topright][_y]) topright = i;
if (x >= list[bottomright][_x] && y >= list[bottomright][_y]) bottomright = i;
if (x <= list[bottomleft][_x] && y >= list[bottomleft][_y]) bottomleft = i;
}
// start drawing paths from one corner to the next, repeating until a full loop has been made
var priorities = ["l","u","r","d"], // travel the outline in this order, left up right down
current = topleft, // current tile
last = topleft; // last tile
target = [topright,bottomright,bottomleft,topleft],
target_iterator = 0, // target iterator so we know which corner we're striving towards
xx = 0, // an iterator to make sure this loop doesnt go on forever
done = false;
while(!done) {
// add the current tile to our hull list.
hull.push(current);
var next = {},
cx = list[current][_x],
cy = list[current][_y];
for (var i = 0; i < list.length; i++) {
var x = list[i][_x], y = list[i][_y];
if (x === cx && y === cy -16) { next.up = i; continue; }
if (x === cx && y === cy +16) { next.down = i; continue; }
if (x === cx -16 && y === cy) { next.left = i; continue; }
if (x === cx +16 && y === cy) { next.right = i; continue; }
}
var i = 0, check = priorities;
for (var i = 0; i < priorities.length; i++) {
var check = priorities[i];
// skip this if next check doesnt exist, or is the tile we just came from
if (next[check] == null || next[check] == undefined|| next[check] === last) continue;
var visited = false;
for (var j = 1; j < hull.length; j++) {
if (hull[j] === next[check]) {
visited = true;
continue;
}
}
if (visited) {
continue;
}
break;
}
last = current;
current = next[check];
xcoords = list[current][_x];
ycoords = list[current][_y];
coordinates.push([xcoords,ycoords]);
if (current === target[target_iterator]) {
priorities.push(priorities.shift());
target_iterator++;
if (target_iterator === 4) break;
}
xx++;
if (xx > 50) {break; debugger;}
}
if (xx < 50) this.hull = coordinates;
}
You should use moveTo() and lineTo() with a stroke(). Further documentation is available at MDN.
A simple drawing function could look like this: (or check Fiddle)
function drawShape(coords) {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
for(var i = 0; i < coords.length; i++) {
if(i === 0) {
ctx.moveTo(coords[i].x, coords[i].y);
} else {
ctx.lineTo(coords[i].x, coords[i].y);
}
}
ctx.closePath();
ctx.stroke();
}
You can easily stroke an "unlimited" amount of corners, or vertices of a shape using moveTo() and lineTo(). Below is an example of how you could draw a triangle, but you could easily extend it by adding more path methods.
var ctx = canvas.getContext('2d');
// Filled triangle
ctx.beginPath();
ctx.moveTo(25,25); //moves 'pen' to coords
ctx.lineTo(105,25); //draws line from prior coords to new specified coords.
ctx.lineTo(25,105);
ctx.fill(); //fills in shape
// Stroked triangle
ctx.beginPath();
ctx.moveTo(125,125);
ctx.lineTo(125,45);
ctx.lineTo(45,125);
ctx.closePath();
ctx.stroke(); //strokes along path (i.e shape outline)
<canvas id="canvas" width="200" height="200"></canvas>
This code can be found at https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes along with detailed advice on how to draw more complex shapes.
Ok, further studies, I found a name for the type of shape I'm trying to draw.
Its called an Rectilinear polygon.
Wikipedia's article
Stack Overflow already has an answer for this, was just hard to find.
I have an object that sits at point 0,0. This object cannot share space with any other object of its type that may appear on top of it, next to it, above it, etc.. There may be more than a few of these objects present overlapping each other and i have no knowledge of where the other ones are placed until i try the collision detection method.
My thinking is that i'll use a collision detection along side a grid search. Along the lines of the photo below.
The object will first try its default best case location. If that doesn't work then it tries to the left, left-above, left-below, etc, until it has searched all the #1 positions. Then it moves onto the #2 positions and so on until it finds a place to drop the element where it won't be overlapping another.
this is the code i'm playing around with right now but it is choosing some very, very random locations for things. I'm pretty sure it isn't following the algorithm i described above.
for (let i = 0; i < 5 && this._hasCollisions(this._tagWrapper); i++) {
/**
* This algorithm explores positions inside nested boxes.
* The move algorithm behaves the following way. It goes,
* down, up, left, down, up, right * 2, repeat.
*
* For example this is how it works given the height of 5 and a width of 7
* numbers are expressed in the offset generated
* 1: 5,0 4: 5,-7 7: 5,7 10: 10,-14
* 2: -5,0 5: -5,-7 8: -5,7 11: -10,-14
* 3: 0,-7 6: 0,7 9: 0,-14
*/
// Calculate which box the collision detector is working in
// This happens every 9 iterations
let multiplier = (i / 9) + 1;
/**
* Get the x offset
*/
if (i % 3 === 0) {
// Clear the height offset on multiples of 3
xOffset = 0;
} else {
// Set the height to the multiplier
xOffset = this._tagWrapper.offsetWidth * multiplier;
}
if (i % 3 === 2) {
// Get the sequence 2, 5, 8, 11, 14, etc..
xOffset *= -1;
}
/**
* Get the y offset
*/
if (i > 2) {
// Set the width to a multiple of the multiplier and assign the existing negativeness
yOffset = this._tagWrapper.offsetHeight * multiplier * (yOffset > 0 ? 1 : -1);
}
if (i % 3 === 0) {
// Flip the sign every 3 numbers
yOffset *= -1;
}
console.log('iteration', i);
this._tagWrapper.style.top = (basePosition.y + yOffset) + 'px';
this._tagWrapper.style.left = (basePosition.x + xOffset) + 'px';
}
What is the best way to go about performing this search? I already hav
Something like this work? (most of the code is just for the visualization)
// just draw a table to visualize
var SIZE = 15;
for (var i = 0; i < SIZE; i++) {
$("#a").append("<tr>");
for (var j = 0; j < SIZE; j++) {
$("#a > tr").last().append("<td>.</td>");
}
}
// where to start searching from
var startX = 8;
var startY = 8;
function loop() {
// tell the world which grid we are on
$("#a > tr:nth-child(" + y + ") > td:nth-child(" + x + ")").css("backgroundColor", "red");
// check if done here!!! - x and y are our positions in the grid
// also do bounds checking here
if (isX) {
x = x + xDirection;
i--;
if (!i) {
// switch dimension
isX = !isX;
i = moveFor;
// switch direction
xDirection *= -1;
}
} else {
y = y + yDirection;
i--;
if (!i) {
// switch dimension
isX = !isX;
// increase the width / height we are spanning
moveFor += 1;
i = moveFor;
// switch direction
yDirection *= -1;
}
}
// jsut so that we have a nice animation
if (x > 0 && y > 0 && x <= SIZE && y <= SIZE) {
setTimeout(loop, 10)
}
}
var x = startX;
var y = startY;
var moveFor = 1;
// our step (down) counter
var i = moveFor;
var xDirection = -1;
var yDirection = -1;
// are we moving along x or y
var isX = true;
loop();
body {
font-family: monospace;
}
td {
height: 20px;
width: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody id="a"></tbody>
</table>
I would suggest using distance solution
point1 has x1 and y1
point2 has x2 and y2
var d = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 );
link here: Get distance between two points in canvas
Here is an implementation of scanning points in a ring around a center point. You define the center point and the distance you want to sample and it returns the list of points in clock-wise order. It is in Python not JavaScript but it is simple enough that you can translate if needed.