• Stars
    star
    201
  • Rank 194,491 (Top 4 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 6 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Detect collisions between different shapes such as Points, Lines, Boxes, Polygons (including concave), Ellipses, and Circles. Features include RayCasting and support for offsets, rotation, scaling, bounding box padding, with options for static and trigger bodies (non-colliding).

Detect-Collisions

npm version npm downloads per week build status

Introduction

Detect-Collisions is a robust TypeScript library for detecting collisions among varied entities. It uses Bounding Volume Hierarchy (BVH) and the Separating Axis Theorem (SAT) for efficient collision detection. Uniquely, it manages rotation, scale of bodies, and supports decomposing concave polygons into convex ones. It also optimizes detection with body padding. Ideal for gaming, simulations, or projects needing advanced collision detection, Detect-Collisions offers customization and fast performance.

Demos

Installation

$ npm install detect-collisions

API Documentation

For detailed documentation on the library's API, refer to the following link:

https://prozi.github.io/detect-collisions/modules.html

Usage

Step 1: Initialize Collision System

Detect-Collisions extends functionalities from RBush. To begin, establish a unique collision system.

const { System } = require("detect-collisions")
const system = new System()

Step 2: Understand Body Attributes

Bodies have various properties:

  • Position Attributes: pos: Vector, x: number, y: number. Setting body.pos.x or body.pos.y doesn't update the bounding box while setting body.x or body.y directly does. To set position and update bounding box use setPosition(x, y).

  • Scale, Offset: Bodies have scale: number shorthand property and setScale(x, y) method for scaling. The offset: Vector property and setOffset({ x, y }: Vector) method are for offset from body center for rotation purposes.

  • AABB Bounding Box: You can get the bounding box even on non-inserted bodies with getAABBAsBBox(): BBox method.

Bodies contain additional properties (BodyOptions) that can be set in runtime or during creation:

  • angle: number: Angle in radians, use deg2rad(degrees: number) for conversion. Use setAngle(angle: number) to change it during runtime.
  • isStatic: boolean: If set to true, the body won't move during system.separare(). For walls.
  • isTrigger: boolean: If set to true, the body won't move during system.separate(). For projectiles.
  • isCentered: boolean: If set to true, offset is set to center for rotation purposes.
  • padding: number: Bounding box padding, optimizes costly updates.

Use isConvex: boolean and convexPolygons: Vector[][] to check if the Polygon is convex.

Each body type has specific properties, for example, a Box has width & height properties.

Step 3: Create and Manage Bodies

Use BodyOptions as the last optional parameter during body creation.

const { deg2rad } = require("detect-collisions")
const options = {
  angle: deg2rad(90),
  isStatic: false,
  isTrigger: false,
  isCentered: false,
  padding: 0,
}

Create body of various types using:

const {
  Box,
  Circle,
  Ellipse,
  Line,
  Point,
  Polygon,
} = require("detect-collisions")

// create with options, without insert
const box = new Box(position, width, height, options)
const circle = new Circle(position, radius, options)
const ellipse = new Ellipse(position, radiusX, radiusY, step, options)
const line = new Line(start, end, options)
const point = new Point(position, options)
const polygon = new Polygon(position, points, options)

Insert a body into the system:

// insert any of the above
system.insert(body)

Create and insert body in one step:

// create with options, and insert
const box = system.createBox(position, width, height, options)
const circle = system.createCircle(position, radius, options)
const ellipse = system.createEllipse(position, radiusX, radiusY, step, options)
const line = system.createLine(start, end, options)
const point = system.createPoint(position, options)
const polygon = system.createPolygon(position, points, options)

Step 4: Manipulate Bodies

Move a body using body.setPosition(x, y). Remove it with system.remove(body). When a body moves, its bounding box in the collision tree needs an update, which is automatically done using body.setPosition(x, y).

Step 5: Collision Detection and Resolution

Check collisions for all bodies or a single body:

system.checkAll((response: Response) => {
  /* ... */
})
system.checkOne(body, (response: Response) => {
  /* ... */
})

For a direct collision check without broad-phase search, use system.checkCollision(body1, body2). However, this isn't recommended due to efficiency loss.

Access detailed collision information in the system.response object, which includes properties like a, b, overlap, overlapN, overlapV, aInB, and bInA.

In case of overlap during collision, subtract the overlap vector from the position of the body to eliminate the collision. This can be achieved as follows:

if (system.checkCollision(player, wall)) {
  const { overlapV } = system.response
  player.setPosition(player.x - overlapV.x, player.y - overlapV.y)
}

Step 6: Collision Response

After detecting a collision, you may want to respond or "react" to it in some way. This could be as simple as stopping a character from moving through a wall, or as complex as calculating the resulting velocities of a multi-object collision in a physics simulation.

Here's a simple example:

system.checkAll((response: Response) => {
  if (response.a.isPlayer && response.b.isWall) {
    // Player can't move through walls
    const { overlapV } = response
    response.a.setPosition(response.a.x - overlapV.x, response.a.y - overlapV.y)
  } else if (response.a.isBullet && response.b.isEnemy) {
    // Bullet hits enemy
    system.remove(response.a) // Remove bullet
    response.b.takeDamage() // Damage enemy
  }
})

Step 7: System Separation

There is an easy way to handle overlap and separation of bodies during collisions. Use system.separate() after updating the system. This function takes into account isTrigger and isStatic flags on bodies.

system.separate()

This function provides a simple way to handle collisions without needing to manually calculate and negate overlap.

Step 8: Cleaning Up

When you're done with a body, you should remove it from the system to free up memory and keep the collision checks efficient. You can do this with the remove method:

system.remove(body)

This will remove the body from the system's internal data structures, preventing it from being included in future collision checks. If you keep a reference to the body, you can insert it again later with system.insert(body).

Remember to always keep your collision system as clean as possible. This means removing bodies when they're not needed, and avoiding unnecessary updates. Following these guidelines will help ensure your project runs smoothly and efficiently.

And that's it! You're now ready to use the Detect-Collisions library in your project. Whether you're making a game, a simulation, or any other kind of interactive experience, Detect-Collisions provides a robust, efficient, and easy-to-use solution for collision detection. Happy coding!

Detecting collision after insertion

// create self-destructing collider
const testCollision = ({ x, y }, radius = 10) => {
  // create and add to tree
  const circle = system.createCircle({ x, y }, radius)
  // init as false
  const collided = system.checkOne(circle, () => {
    // ends iterating after first collision
    return true
  })

  // remove from tree
  system.remove(circle)

  return collided ? system.response : null
}

Handling Concave Polygons

As of version 6.8.0, Detect-Collisions fully supports non-convex or hollow polygons*, provided they are valid. Learn more about this feature from GitHub Issue #45 or experiment with it on Stackblitz.

Visual Debugging with Rendering

To facilitate debugging, Detect-Collisions allows you to visually represent the collision bodies. By invoking the draw() method and supplying a 2D context of a <canvas> element, you can draw all the bodies within a collision system.

const canvas = document.createElement("canvas")
const context = canvas.getContext("2d")

context.strokeStyle = "#FFFFFF"
context.beginPath()
system.draw(context)
context.stroke()

You can also opt to draw individual bodies.

context.strokeStyle = "#FFFFFF"
context.beginPath()
// draw specific body
body.draw(context)
// draw whole system
system.draw(context)
context.stroke()

To assess the Bounding Volume Hierarchy, you can draw the BVH.

context.strokeStyle = "#FFFFFF"
context.beginPath()
// draw specific body bounding box
body.drawBVH(context)
// draw bounding volume hierarchy of the system
system.drawBVH(context)
context.stroke()

Raycasting

Detect-Collisions provides the functionality to gather raycast data. Here's how:

const start = { x: 0, y: 0 }
const end = { x: 0, y: -10 }
const hit = system.raycast(start, end)

if (hit) {
  const { point, body } = hit

  console.log({ point, body })
}

In this example, point is a Vector with the coordinates of the nearest intersection, and body is a reference to the closest body.

Contributing to the Project

We welcome contributions! Feel free to open a merge request. When doing so, please adhere to the following code style guidelines:

  • Execute the npm run precommit script prior to submitting your merge request
  • Follow the conventional commits standard
  • Refrain from using the any type

Additional Considerations

Why not use a physics engine?

While physics engines like Matter-js or Planck.js are recommended for projects that need comprehensive physics simulation, not all projects require such complexity. In fact, using a physics engine solely for collision detection can lead to unnecessary overhead and complications due to built-in assumptions (gravity, velocity, friction, etc.). Detect-Collisions is purpose-built for efficient and robust collision detection, making it an excellent choice for projects that primarily require this functionality. It can also serve as the foundation for a custom physics engine.

Benchmark

$ git clone https://github.com/Prozi/detect-collisions.git
$ cd detect-collisions
$ yarn
$ yarn benchmark [milliseconds=1000]

will show you the Stress Demo results without drawing, only using Detect-Collisions and with different N amounts of dynamic, moving bodies

typical output:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚  items  β”‚ FPS β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€
β”‚    0    β”‚ 'total' β”‚ 659 β”‚
β”‚    1    β”‚  1000   β”‚ 244 β”‚
β”‚    2    β”‚  2000   β”‚ 133 β”‚
β”‚    3    β”‚  3000   β”‚ 87  β”‚
β”‚    4    β”‚  4000   β”‚ 55  β”‚
β”‚    5    β”‚  5000   β”‚ 40  β”‚
β”‚    6    β”‚  6000   β”‚ 31  β”‚
β”‚    7    β”‚  7000   β”‚ 22  β”‚
β”‚    8    β”‚  8000   β”‚ 18  β”‚
β”‚    9    β”‚  9000   β”‚ 16  β”‚
β”‚   10    β”‚  10000  β”‚ 13  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜
Done in 14.58s.