Prozi / detect-collisions

Points, Lines, Boxes, Polygons (also hollow), Ellipses, Circles. RayCasting, offsets, rotation, scaling, bounding box padding, flags for static and ghost/trigger bodies
https://prozi.github.io/detect-collisions/
MIT License
206 stars 22 forks source link

feature suggestion, adding a label to the collision body for sorting and matching bodies #63

Closed jyoung4242 closed 1 year ago

jyoung4242 commented 1 year ago

one idea that might be useful, because i've found myself needing this while using your library, is maybe adding a string property to the Body types where you can add a label, like "tile", "enemy", "player"

This could assist in categorizing collision resolutions so you can quickly switch on type of body to a different resolution.

jyoung4242 commented 1 year ago

follow up, in the diagnostic draw() method for debugging, you could draw that label onto the canvas in the box/circle,... just a thought

jyoung4242 commented 1 year ago

another follow up, since i'm using TS, i just created a new type to extend box/circle that added a catagory property to the type, and i can switch on that but still, i'd think its useful...

leiavoia commented 1 year ago

This has come up for my project as well.

I am considering switching my project to this library from the older Sinova/Collisions lib. Being able to perform collisions on a filtered subset is critically important. I have hundreds of objects in a small space, but most of them do not collide with each other. Being able to check only a small fraction of objects would yield dramatic performance gains. It would be best to do any filtering before doing collision math checks. Ideally, i am looking for an optional string type or more flexible callback on collision, like:

system.checkAll( onCollideCallback, 'type_player' )

or

system.checkAll( onCollideCallback, filterCallback )

If the library internals already allow for this, please advise. I would also be interested in implementing this if it is not already.

Thanks!

Prozi commented 1 year ago

@leiavoia this library uses a library (rbush) to create collision trees no unnecessary math is done there

see https://prozi.github.io/detect-collisions/demo/?stress

BUT you can always try to use something like

players.forEach(player => {
  system.checkOne(player, onCollideCallback)
})

if that suits your use-case, to try some micro optimization

this way you will check only what players collide with, and not check if other objects do (with other things than players)

so this will detect player-nonplayer and player-player but not nonplayer-nonplayer collisions if that's where you're aiming

Prozi commented 1 year ago

@jyoung4242 thank you for your comments

one idea that might be useful, because i've found myself needing this while using your library, is maybe adding a string property to the Body types where you can add a label, like "tile", "enemy", "player"

This could assist in categorizing collision resolutions so you can quickly switch on type of body to a different resolution.

you can do this like

class Bullet extends Circle {};
class Player extends Circle {};

type ExtendedBody = Player | Bullet;

const system = new System<ExtendedBody>();

// ...
Prozi commented 1 year ago

see https://prozi.github.io/detect-collisions/classes/System.html

you can do new System<YourNewType>() or actually if you read

https://www.npmjs.com/package/rbush#creating-a-tree and our documentation and know that system extends rbush

then you can do

const maxEntriesInTreeNode = 16 // or other

new System<YourNewType>(maxEntriesInTreeNode)

if you'd ever need it or want to try to micro optimize here

leiavoia commented 1 year ago

I see. This may not work then.

In my scenario, i have a fishtank with fish and food flakes. Fish need to detect food collisions to eat the food. Fish do not collide with each other, but they do need to collide with obstacles (rocks) and food. Fish also need to be locally detectable by other fish (flocking behavior), even though there is no direct collision resolution.

Currently, I have something analogous to this:

fish.forEach( fish => {
  system.findCollisions( fish, onCollideCallback, 'Food' ); // only check "Food" objects
})
leiavoia commented 1 year ago

I suppose a roundabout solution would be to maintain separate "systems" with each system keeping track of a different object type.

Prozi commented 1 year ago

@leiavoia

I see. This may not work then.

In my scenario, i have a fishtank with fish and food flakes. Fish need to detect food collisions to eat the food. Fish do not collide with each other, but they do need to collide with obstacles (rocks) and food. Fish also need to be locally detectable by other fish (flocking behavior), even though there is no direct collision resolution.

Currently, I have something analogous to this:

fish.forEach( fish => {
  system.findCollisions( fish, onCollideCallback, 'Food' ); // only check "Food" objects
})

then u were quite close just do something like below I havent tested this but something along those lines are what you're looking for this is optimal and unless you got more than 1000 entries updated each frame I wouldn't worry about optimisations at all

// type definition
class Fish extends Circle {
  isFish = true

  eatFood(food) {
    this.system?.remove(food)
  }
}

class Food extends Circle {
  isFood = true
}

class Rock extends Polygon {
  isRock = true
}

class Tank extends System<Fish | Food | Rock> {}

// initialisation
const system = new Tank()

const fishes = [
  new Fish({ x: 10, y: 10 }, 10),
  new Fish({ x: 30, y: 10 }, 10),
  new Fish({ x: 50, y: 10 }, 10),
]

fishes.forEach(fish => {
  system.insert(fish)
})

system.insert(
  new Rock({ x: 30, y: 100 }, [{ x: 0, y: 0 }, { x: 50, y: 50 }, { x: 0, y: 50 }], { isStatic: true })
)

system.insert(
  new Food({ x: 30, y: -50 }, 5)
)

// usage - each frame - detect and handle collisions
fishes.forEach(fish => {
  system.checkOne(fish, ({ a: thatFish, b: maybeFood, overlapV: { x, y } }) => {
    if (maybeFood.isFood) {
      fish.eatFood(maybeFood)
    } else if (maybeFood.isRock) {
      // push back the fish by rock
      fish.setPosition(fish.x - x, fish.y - y)
    } else { // propably another fish
      // do nothing just log the collider
      console.log(maybeFood)
    }
  })
})

I suppose a roundabout solution would be to maintain separate "systems" with each system keeping track of a different object type.

this is the last thing I would recommend especially for the performance reasons, this seems it would be twice as slow trust in the broad phase search of the library and having all of the entries in collision trees this means for each entry in the system, it isn't checked agains each of the other objects, just the nearest candidates, in the same sector in the tree

Prozi commented 1 year ago

and since you also need to handle the food I wouldn't even use the fishes array and checkOne just replace it with one system.checkAll and handle it there based on those is* props like above TBH

since food collides with rocks right?

just use system.checkAll(handler) try it

leiavoia commented 1 year ago

I tried a working prototype with the collision detection code using this lib instead of the older Sinova lib and my own custom spacial grid system. It has more features, but it runs 25-25% slower now ;-(

Are there any performance tuning parameters or optimizations available? I see that RBush takes a few, but not sure if i can pass those in as parameters.

Thanks

Prozi commented 1 year ago

I tried a working prototype with the collision detection code using this lib instead of the older Sinova lib and my own custom spacial grid system. It has more features, but it runs 25-25% slower now ;-(

sorry to hear that, maybe I can help with the code if you show the main parts

Are there any performance tuning parameters or optimizations available? I see that RBush takes a few, but not sure if i can pass those in as parameters.

Thanks

System extends RBush so the constructor params are the same refer to https://www.npmjs.com/package/rbush#creating-a-tree

as for optimizations there are two powerful optimization methods:

  1. use isStatic property on bodies that don't move but collide
  2. use padding property > 0 on bodies

isStatic bodies are omitted in some computations

padding makes body not reinsert into collision tree when the movement it has is within the padding (close collision is still accurate based on real box etc)

Prozi commented 1 year ago

also what kind of calculations you do each frame?

what changes in the bodies?

is it only position or more?

how do you update the bodies?

jyoung4242 commented 1 year ago

@jyoung4242 thank you for your comments

one idea that might be useful, because i've found myself needing this while using your library, is maybe adding a string property to the Body types where you can add a label, like "tile", "enemy", "player" This could assist in categorizing collision resolutions so you can quickly switch on type of body to a different resolution.

you can do this like

class Bullet extends Circle {};
class Player extends Circle {};

type ExtendedBody = Player | Bullet;

const system = new System<ExtendedBody>();

// ...

i actually did something very similar to this and it worked well

jyoung4242 commented 1 year ago

is it fair to close this issue?