I am starting on a physics/particle simulator and I am having some trouble with collision detection:
http://mmhudson.com/physics.html
Im not so much looking for a code solution, but someone to explain the issue to me conceptually.
The way it works is I check to see if the particle is going to be inside/intersect with the object when it is next moved. If it is, the gravity multiplier is reversed so its direction is reversed.
The equation for movement I use is:
Next location = current speed + rate of gravity + current location
Where speed is the gravity multiplier
Hopefully someone has seen an issue like this before or is willing to check out the source of my page.
Any help at all is greatly appreciated
No conceptual explanation, but a bunch of random observations:
I'd recommend adding canvas width/height variables and comparing them against the particle position. Right now, your particles keep falling even if they drop off the canvas. Something like:
if( particles[i][1] > height )
particles.splice(i,1);
newPY < objects[k][2] + objects[k][3] + radius
is really weird. What are you getting from this? Adding the width and height of the objects and the particle radius? If you remove this part, particles will bounce off objects as long as they have "momentum".
As for momentum, I assume you want to figure out how to stop the particles from falling through the objects. Given current code, I'd do this: add a fifth variable to the particle, defaulting to the height of the canvas. Then, once you find out that you have an impact, save the impact position to the particle and after the loop, check if the particle is below that point. If so, reset it to that point. Dirty fix, but hey, it works. I've added the complete loop below that worked for me. To stop the objects from being wiped out by the clearRect method, maybe consider redrawing them.
You are checking for particles on top of the objects only, but I assume the "falling through" aspect is part of the bug you asked about, so not too important at the moment:
particles[i][1] < objects[k][1] + objects[k][3] + radius
Could be however, if you decided to play around and reduce gravity, so that particles would instead gain momentum and bounce against objects above.
As for your objCheck variable, you confuse the width for y in the last && part. It should be:
mY < objects[i][1] + objects[i][3] + radius
instead of
mY < objects[i][2] + objects[i][3] + radius
Right now, your objCheck is not working.
Also
for(var i=0; i < particles.length; i++) {
var clrRadius = 2*radius;
canvas.clearRect(particles[i][0]-radius, particles[i][1]-radius, clrRadius, clrRadius);
}
is better than
for(var i=0; i < particles.length; i++){
var clrRadius = radius + 4;
canvas.clearRect(particles[i][0]-(clrRadius/2), particles[i][1]-(clrRadius/2), clrRadius, clrRadius);
}
edit: Seems you changed the code, since I last checked it, so the above might no longer be relevant!
edit2: added particle stopping fix. Here's the complete gravity loop:
for(var i=0; i<particles.length; i++){
var clrRadius = 2*radius;
canvas.clearRect(particles[i][0]-radius, particles[i][1]-radius, clrRadius, clrRadius);
}
for(var i=0; i < particles.length; i++){
var newPY = particles[i][1] += particles[i][2] + particles[i][3];
for(var k=0; k<objects.length; k++){
if(
//particle
particles[i][0] > objects[k][0] - radius &&
particles[i][0] < objects[k][0] + objects[k][2] + radius &&
particles[i][1] > objects[k][1] - radius &&
particles[i][1] < objects[k][1] + objects[k][3] + radius //&&
){
//reverse gravity
particles[i][2] = particles[i][2] * -1;
particles[i][5] = objects[k][1] - radius;
}
}
particles[i][2] += particles[i][3]*weight;
particles[i][1] += particles[i][2];
if( particles[i][1] > particles[i][5] )
particles[i][1] = particles[i][5];
if( particles[i][1] > height )
particles.splice(i,1);
}
for(var i=0; i <particles.length; i++){
canvas.fillStyle = "#000";
canvas.beginPath();
canvas.arc(particles[i][0], particles[i][1], radius, 0, Math.PI*2, true);
canvas.closePath();
canvas.fill();
}
I wouldn't use a gravity multiplier.
Each object should look something like this:
var circle = {
x: 0, // x position
y: 0, // y position
dx: 0, // x velocity
dy: 0 // y velocity
}
To update the particle, multiply velocity (dx, dy) by some time interval and add this to the current position.
Every cycle, add some change in velocity as a result of gravity.
If you detect a collision change the velocity so the circles bounce off each other. An example of this would be:
// In a collision, simply reverse the direction of movement
// so the circles move away from each other.
function onCollision(circleA, circleB) {
circleA.dx *= -1;
circleA.dy *= -1;
circleB.dx *= -1;
circleB.dy *= -1;
}
Related
I'm currently making an HTML5 game, and I'm trying to draw various things onto the canvas. My game is basically just where you move around in an infinite area, but I can't figure out how to optimize my code to draw bushes onto the screen. It works properly, but It lags a lot and I know there's ways to optimize it. Here's my current code:
for(var x=offset[0];x<(offset[0]+canvas.width)+300;x++) {
for(var y=offset[1];y<(offset[1]+canvas.height)+300;y++) {
if(x % 85 == 0 && y % 85 == 0 && noise.simplex2(x, y) == 0) {
ctx.drawImage(treeimage, ((x-offset[0])*-1)+canvas.width, ((y-offset[1])*-1)+canvas.height);
}
}
}
treeimage is defined as so:
var treeimage = new Image(); treeimage.src = 'images/mapobjects/tree2.png';
offset[] is an array with the values being the offset of the objects relative to the player (So when the player moves left, it goes up) horizontally and vertically respectively. I use simplex noise to generate the bushes because I like them to be in small clumps. The problem that makes the FPS so low is that at the resolution of my screen, I'm running 2 modulo functions 2137104 per frame, and that gets even worse at higher resolutions. I tried to make it faster by looping through every tile of my game instead of every pixel(each tile is 85x85, so incrementing y and x by 85 instead of 1) and then adding the player offset % 85, but I had issues with that jumping around because the offset % 85 didn't go to 0 right when it jumped to the next tile, and I tried and tried to get that working in many different ways, but this is the only way I could get it to work. This is how it looks, and everything works fine besides the code being super slow.
Is there something I was missing when I was trying to optimize it, or is there a completely different way that would fix it as well. I've never really had to optimize code, so this is a new thing for me. I can tell all the lag is coming from this code because without it and just incrementing by 85 it works perfectly fine. Thank you!
7225 pointless operations per image
Conditions slow code down. When ever possible you should try to avoid them.
eg the line...
if(x % 85 == 0 && y % 85 == 0 && noise.simplex2(x, y) == 0) {
... means that you are evaluating the if statement 85 * 85 (7225) times for every less than one tree this is a massive amount of unneeded overhead.
Remove those 7224 useless iterations.
Avoid indexing arrays when possible by storing repeated array lookups in a variable.
Simplify your math. eg ((x-offset[0])*-1)+canvas.width can be simplified to canvas.width - x + offset[0].
Offload as much as you can to the GPU. By default all position calculations are via the transform done on the GPU so that above math can be done once before the loop.
General rule for performance, reduce the amount of code inside a loop by moving what you can to outside the loop.
The snippet below implements the above points.
As you have not provided details as to the ranges of offset and canvas size the code below could be further optimized
var x, y;
const STEP = 85;
const offsetX = offset[0];
const offsetY = offset[1];
const startX = Math.floor(offsetX / STEP) * STEP;
const startY = Math.floor(offsetY / STEP) * STEP;
const endX = startX + canvas.width;
const endY = startY + canvas.height;
ctx.setTransform(1, 0, 0, 1, canvas.width - offsetX, canvas.height - offsetY);
for (x = startX; x < endX; x += STEP) {
for (y = startY; y < endY; y += STEP) {
if (noise.simplex2(x, y) == 0) {
ctx.drawImage(treeimage, x, y);
}
}
}
// reset transform
ctx.setTransform(1, 0, 0, 1, 0, 0);
Consider a quad tree
The call to simplex2 I suspect will be very slow. All the implementations I have seen are done very poorly. As the result of simplex is constant for any coordinate it should only be done once per coordinate before the game starts (outside the code in production).
As you want an infinite (like) playfield (infinite is impossible) the RAM requirement way too large. There is not much I can suggest, well apart from... Drop the infinite and set a practical limit to the playfield size which will allow you to create a quad tree map that will make it fly.
Many many years ago, as computers weren't as fast as today and you had to do some hefty mathematical operations like computing the sine or cosine - or even the modulo - there was just one option:
instead of calculating it everytime you need it, calculate it once and store it in a huge look-up table. Looking up a value is of course way faster than computation.
So in your case I'd recommend generating two arrays for the modulo of x and y
let xModulo = [];
let yModulo = [];
for (let a = 0; a < canvas.width; a++) {
xModulo.push(a % 85);
}
for (let a = 0; a < canvas.height; a++) {
yModulo.push(a % 85);
}
and in your render loop look up the values from the arrays like:
if (xModulo[x] == 0 && yModulo[y] == 0 && noise.simplex2(x, y) == 0) {
ctx.drawImage(treeimage, ((x - offset[0]) * -1) + canvas.width, ((y - offset[1]) * -1) + canvas.height);
}
That should give a noticeable performance boost. Depending on your needs you might need to change canvas.width / canvas.height to some higher value.
You might even consider generating a look-up table for the simplex noise.
I struggle with both anything math, and describing my problems, so I'm going to keep include lots of images to help visualize it. I've been plotting prime numbers on a polar graph, to demonstrate the patterns that emerge.
For anyone unfamiliar, it looks like this.
Visualization
The problem that I've been having is that eventually the circles get too small to be seen. The code I'm using looks like this:
let radius;
let theta;
let toCalc = 50000
let primes;
let size = 1;
function setup() {
createCanvas(800,800);
primes = getPrimes(toCalc);
//put origin point in center
r = height * 0.45;
theta = 0;
noStroke();
color(255,255,255)
scale(0.01)
}
function draw() {
translate(width / 2, height / 2);
background(10);
scale(size);
//the for loop takes forever to fully create all of the circles, so it takes a really long time //to fully generate so i might leave it overnight sometime
for(var i = 0; i < toCalc; i++)
{
current = primes[i];
let x = current * cos(current);
let y = current * sin(current);
//the horrific (5/exp(size)*2) is something i threw together to get exponential scaling
//with the zoom at roughly the right level
circle(x, y, (5/exp(size))*3)
}
console.log(size)
}
function getPrimes(max) {
//i timed this code and apparently its actually really really performant,
//even up to a million primes. its the for loop that really kills performance.
var sieve = [], i, j, primes = [];
for (i = 2; i <= max; ++i) {
if (!sieve[i]) {
// i has not been marked -- it is prime
primes.push(i);
for (j = i << 1; j <= max; j += i) {
sieve[j] = true;
}
}
}
return primes;
}
//measuring mousewheel for zoom
function mouseWheel(event) {
//ternary to check if it was a scrolldown or scrollup
//have to make zoom out by size/10 because size is represented by a value from 0 to 1
event.delta >= 0 ? size-=size/10 : size+=size/10
//returning false to block page scrolling
return false;
}
Currently, totally arbitrarily, I have been using this to scale the circles, and it works relatively well up to a scale of 0.005 on a scale of 0 to 1, but then the circles get too small and disappear.
5/exp(size))*3
What would be the best way to scale circles like this as the user zoomed out?
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.
I'm making a 2D game in JavaScript. For it, I need to be able to "perfectly" check collision between my players(the game has two players, open the picture please) and the walls! I mean, I have a function that actually works, but when I make them jump against the walls they pass through the walls and keep moving until they reach another area or even leave the canvas!
Also, if they are falling down and I make them collide with a wall, they just stop there wich is also pretty bad!
I really need help with that!! It's a university project and I have to finnish it really soon!
My game looks like this
The collision detection function I have is here:
function blockRectangle (objA, objB) {
var distX = (objA.x + objA.width / 2) - (objB.x + objB.width / 2);
var distY = (objA.y + objA.height / 2) - (objB.y + objB.height / 2);
var sumWidth = (objA.width + objB.width) / 2;
var sumHeight = (objA.height + objB.height) / 2;
if (Math.abs(distX) < sumWidth && Math.abs(distY) < sumHeight) {
var overlapX = sumWidth - Math.abs(distX);
var overlapY = sumHeight - Math.abs(distY);
if (overlapX > overlapY) {
objA.y = distY > 0 ? objA.y + overlapY : objA.y - overlapY;
}
else {
objA.x = distX > 0 ? objA.x + overlapX : objA.x - overlapX;
}
}
}
I did the walls with a maze and I'm using a for cycle to check the collisions with all of the walls I have saved in an array!
As you can see here:
for (var i in walls) {
var wall = walls[i];
if ((player.x < (wall.x + wall.width)) && ((player.x + player.width) > wall.x) && (player.y < (wall.y + wall.height)) && ((player.height + player.y) > wall.y)) {
player.falling = false;
}
blockRectangle(player, wall);
}
Please help me!!! Thank you all!
In your case I doubt a pixel perfect collision is required.
You can maintain a boolean matrix to store the position of solid objects. Solid objects like walls or players. Then in every frame you can check if your player is trying to move to a position where there is a solid object, if it is then stop it. You don't have to create grid of width x height in pixels, but rather choose a largest block (single element in the grid) in which each solid object reasonably occupies most of the block.
For example you can choose block size to be player_width / 2 x player_height /2.
See following image with grid
Another simple way could be to just check the background pixel color. Since your game is simple, background and object colors are different. So you just have to check if the player is trying to move somewhere where pixel color is not of background, thus there is a solid object and player should stop. You don't have to test for a lot of pixels, just 1 pixel in the direction the player is trying to move. (1 for horizontal and 1 for vertical). This however can not be used if you don't have a clear background color. Background color here is kind of the boolean grid for us in the previous suggestion.
I have a space ship in a canvas. It has velocities, ship.vx and ship.vy. When it's 30px away from the canvas borders I set ship.vx & ship.vy to 0 and move the background objects in ship's opposite direction. At this moment the ship is stuck at a point. That's all good. Now if I try to move it left-right(stuck at top/bottom) or top-down(stuck at left/right) it doesn't since it is stuck in the point where vx & vy are set to 0.
If i accelerate in it's opposite direction it takes like 5 seconds to pick it's velocity (around 2), so it's basically at the same point for 5 seconds.
I tried not to set vy to 0 when out of x-axis and vice-versa but the ship keeps moving slowly in the other axis.
So what i'm trying to achieve is the ship'll get stuck when it's 30 px from border, but if i try to move or accelerate in other 3 directions it'll pretend as if it's not stuck.
Any of you know any mechanisms?
Thanks.
function stuckShip(){
if(
(ship.x - ship.width < 0) ||
(ship.x + ship.width > w) ||
(ship.y + ship.height > h) ||
(ship.y - ship.height < 0))
{
ship.vx = 0;
ship.vy = 0;
}
}
function againAndAgain(){
var angle = ship.rotation;
var x = Math.cos(angle);
var y = Math.sin(angle);
var ax = x*thrust,
ay = y*thrust;
ship.vx += ax;
ship.vy += ay;
stuckShip();
ship.draw(context);
}
document.addEventListener('keydown', function(e){
switch(e.keyCode){
case 38:
thrust = 0.35;
break;
case 37:
ship.rotation -= 3;
break;
case 39:
ship.rotation += 3;
break;
}
}
Manipulating the velocity of an object based on it's position on the screen for display-purposes is never the best idea.
Often you use Parent-based systems, so you have one main-container and all objects (including the ship) are child to that container and move relatively to the main-container. Now you can update the container's position, if the ship's global position is in that 30px-band, to make it "lock" on the edge of the screen.
Ha ha, simple, just set ship's position to 31px from border,
if(ship.x <= 30){
ship.x = 30 + 1;
}
What it does is, when the ship is 30px from the left, it'll set ship.x to 31, so it'll never be stuck, just swinging 1px back and forth. i am not sure if it's a perfect solution, but it doesn't pull back the ship for 5 seconds.