Giter VIP home page Giter VIP logo

collisions's Introduction

Collisions

Collisions is a JavaScript library for quickly and accurately detecting collisions between Polygons, Circles, and Points. It combines the efficiency of a Bounding Volume Hierarchy (BVH) for broad-phase searching and the accuracy of the Separating Axis Theorem (SAT) for narrow-phase collision testing.

Installation

npm install collisions

Note: This library uses the native ECMAScript Module syntax. Most environments support native modules, but the following exceptions apply:

Bundling solutions such as Webpack or Rollup.js make native modules compatible with all environments.

Documentation

View the documentation (this README is also there).

Demos

Usage

import Collisions from 'collisions';

// Create the collision system
const system = new Collisions();

// Create a Result object for collecting information about the collisions
const result = system.createResult();

// Create the player (represented by a Circle)
const player = system.createCircle(100, 100, 10);

// Create some walls (represented by Polygons)
const wall1 = system.createPolygon(400, 500, [[-60, -20], [60, -20], [60, 20], [-60, 20]], 1.7);
const wall2 = system.createPolygon(200, 100, [[-60, -20], [60, -20], [60, 20], [-60, 20]], 2.2);
const wall3 = system.createPolygon(400, 50, [[-60, -20], [60, -20], [60, 20], [-60, 20]], 0.7);

// Update the collision system
system.update();

// Get any potential collisions (this quickly rules out walls that have no chance of colliding with the player)
const potentials = player.potentials();

// Loop through the potential wall collisions
for(const wall of potentials) {
	// Test if the player collides with the wall
	if(player.collides(wall, result)) {
		// Push the player out of the wall
		player.x -= result.overlap * result.overlap_x;
		player.y -= result.overlap * result.overlap_y;
	}
}

Getting Started

1. Creating a Collision System

Collisions provides functions for performing both broad-phase and narrow-phase collision tests. In order to take full advantage of both phases, bodies need to be tracked within a collision system.

Call the Collisions constructor to create a collision system.

import Collisions from 'collisions';

const system = new Collisions();

2. Creating, Inserting, Updating, and Removing Bodies

Collisions supports the following body types:

  • Circle: A shape with infinite sides equidistant from a single point
  • Polygon: A shape made up of line segments
  • Point: A single coordinate

To use them, import the desired body class, call its constructor, and insert it into the collision system using insert().

import {Collisions, Circle, Polygon, Point} from 'collisions';

const system = new Collisions();

const circle  = new Circle(100, 100, 10);
const polygon = new Polygon(50, 50, [[0, 0], [20, 20], [-10, 10]]);
const line    = new Polygon(200, 5, [[-30, 0], [10, 20]]);
const point   = new Point(10, 10);

system.insert(circle)
system.insert(polygon, line, point);

Collision systems expose several convenience functions for creating bodies and inserting them into the system in one step. This also avoids having to import the different body classes.

import Collisions from 'collisions';

const system = new Collisions();

const circle  = system.createCircle(100, 100, 10);
const polygon = system.createPolygon(50, 50, [[0, 0], [20, 20], [-10, 10]]);
const line    = system.createPolygon(200, 5, [[-30, 0], [10, 20]]);
const point   = system.createPoint(10, 10);

All bodies have x and y properties that can be manipulated. Additionally, Circle bodies have a scale property that can be used to scale their overall size. Polygon bodies have scale_x and scale_y properties to scale their points along a particular axis and an angle property to rotate their points around their current position (using radians).

circle.x     = 20;
circle.y     = 30;
circle.scale = 1.5;

polygon.x       = 40;
polygon.y       = 100;
polygon.scale_x = 1.2;
polygon.scale_y = 3.4;
polygon.angle   = 1.2;

And, of course, bodies can be removed when they are no longer needed.

system.remove(polygon, point);
circle.remove();

3. Updating the Collision System

Collision systems need to be updated when the bodies within them change. This includes when bodies are inserted, removed, or when their properties change (e.g. position, angle, scaling, etc.). Updating a collision system is done by calling update() and should typically occur once per frame.

system.update();

The optimal time for updating a collision system is after its bodies have changed and before collisions are tested. For example, a game loop might use the following order of events:

function gameLoop() {
	handleInput();
	processGameLogic();

	system.update();

	handleCollisions();
	render();
}

4. Testing for Collisions

When testing for collisions on a body, it is generally recommended that a broad-phase search be performed first by calling potentials() in order to quickly rule out bodies that are too far away to collide. Collisions uses a Bounding Volume Hierarchy (BVH) for its broad-phase search. Calling potentials() on a body traverses the BVH and builds a list of potential collision candidates.

const potentials = polygon.potentials();

Once a list of potential collisions is acquired, loop through them and perform a narrow-phase collision test using collides(). Collisions uses the Separating Axis Theorem (SAT) for its narrow-phase collision tests.

const potentials = polygon.potentials();

for(const body of potentials) {
	if(polygon.collides(body)) {
		console.log('Collision detected!');
	}
}

It is also possible to skip the broad-phase search entirely and call collides() directly on two bodies.

Note: Skipping the broad-phase search is not recommended. When testing for collisions against large numbers of bodies, performing a broad-phase search using a BVH is much more efficient.

if(polygon.collides(line)) {
	console.log('Collision detected!');
}

5. Getting Detailed Collision Information

There is often a need for detailed information about a collision in order to react to it appropriately. This information is stored using a Result object. Result objects have several properties set on them when a collision occurs, all of which are described in the documentation.

For convenience, there are several ways to create a Result object. Result objects do not belong to any particular collision system, so any of the following methods for creating one can be used interchangeably. This also means the same Result object can be used for collisions across multiple systems.

Note: It is highly recommended that Result objects be recycled when performing multiple collision tests in order to save memory. The following example creates multiple Result objects strictly as a demonstration.

import {Collisions, Result, Polygon} from 'collisions';

const system     = new Collisions();
const my_polygon = new Polygon(100, 100, 10);

const result1 = new Result();
const result2 = Collisions.createResult();
const result3 = system.createResult();
const result4 = Polygon.createResult();
const result5 = my_polygon.createResult();

To use a Result object, pass it into collides(). If a collision occurs, it will be populated with information about the collision. Take note in the following example that the same Result object is being reused each iteration.

const result     = system.createResult();
const potentials = point.potentials();

for(const body of potentials) {
	if(point.collides(body, result)) {
		console.log(result);
	}
}

6. Negating Overlap

A common use-case in collision detection is negating overlap when a collision occurs (such as when a player hits a wall). This can be done using the collision information in a Result object (see Getting Detailed Collision Information).

The three most useful properties on a Result object are overlap, overlap_x, and overlap_y. Together, these values describe how much and in what direction the source body is overlapping the target body. More specifically, overlap_x and overlap_y describe the direction vector, and overlap describes the magnitude of that vector.

These values can be used to "push" one body out of another using the minimum distance required. More simply, subtracting this vector from the source body's position will cause the bodies to no longer collide. Here's an example:

if(player.collides(wall, result)) {
	player.x -= result.overlap * result.overlap_x;
	player.y -= result.overlap * result.overlap_y;
}

Lines

Creating a line is simply a matter of creating a single-sided polygon (i.e. a polygon with only two coordinate pairs).

const line = new Polygon(200, 5, [[-30, 0], [10, 20]]);

Concave Polygons

Collisions uses the Separating Axis Theorem (SAT) for its narrow-phase collision tests. One caveat to SAT is that it only works properly on convex bodies. However, concave polygons can be "faked" by using a series of Lines. Keep in mind that a polygon drawn using Lines is "hollow".

Handling true concave polygons requires breaking them down into their component convex polygons (Convex Decomposition) and testing them for collisions individually. There are plans to integrate this functionality into the library in the future, but for now, check out poly-decomp.js.

Rendering

For debugging, it is often useful to be able to visualize the collision bodies. All of the bodies in a Collision system can be drawn to a <canvas> element by calling draw() and passing in the canvas' 2D context.

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

// ...
context.strokeStyle = '#FFFFFF';
context.beginPath();

system.draw(context);

context.stroke();

Bodies can be individually drawn as well.

context.strokeStyle = '#FFFFFF';
context.beginPath();

polygon.draw(context);
circle.draw(context);

context.stroke();

The BVH can also be drawn to help test Bounding Volume Padding.

context.strokeStyle = '#FFFFFF';
context.beginPath();

system.drawBVH(context);

context.stroke();

Bounding Volume Padding

When bodies move around within a collision system, the internal BVH has to remove and reinsert the body in order to determine where it belongs in the hierarchy. This is one of the most costly operations in maintaining a BVH. In general, most projects will never see a performance issue from this unless they are dealing with thousands of moving bodies at once. In these cases, it can sometimes be beneficial to "pad" the bounding volumes of each body so that the BVH doesn't need to remove and reinsert bodies that haven't changed position too much. In other words, padding the bounding volume allows "breathing room" for the body within it to move around without being flagged for an update.

The tradeoff is that the slightly larger bounding volumes can trigger more false-positives during the broad-phase potentials() search. While the narrow phase will ultimately rule these out using Axis Aligned Bounding Box tests, putting too much padding on bodies that are crowded can lead to too many false positives and a diminishing return in performance. It is up to the developer to determine how much padding each body will need based on how much it can move within a single frame and how crowded the bodies in the system are.

Padding can be added to a body when instantiating it (see the documentation for each body) or at any time by changing its padding property.

const padding = 5;
const circle  = new Circle(100, 100, 10, 1, padding);

// ...

circle.padding = 10;

Only using SAT

Some projects may only have a need to perform SAT collision tests without broad-phase searching. This can be achieved by avoiding collision systems altogether and only using the collides() function.

import {Circle, Polygon, Result} from 'collisions';

const circle  = new Circle(45, 45, 20);
const polygon = new Polygon(50, 50, [[0, 0], [20, 20], [-10, 10]]);
const result  = new Result();

if(circle.collides(polygon, result)) {
	console.log(result);
}

FAQ

Why shouldn't I just use a physics engine?

Projects requiring physics are encouraged to use one of the several physics engines out there (e.g. Matter.js, Planck.js). However, many projects end up using physics engines solely for collision detection, and developers often find themselves having to work around some of the assumptions that these engines make (gravity, velocity, friction, etc.). Collisions was created to provide robust collision detection and nothing more. In fact, a physics engine could easily be written with Collisions at its core.

Why does the source code seem to have quite a bit of copy/paste?

Collisions was written with performance as its primary focus. Conscious decisions were made to sacrifice readability in order to avoid the overhead of unnecessary function calls or property lookups.

Sometimes bodies can "squeeze" between two other bodies. What's going on?

This isn't caused by faulty collisions, but rather how a project handles its collision responses. There are several ways to go about responding to collisions, the most common of which is to loop through all bodies, find their potential collisions, and negate any overlaps that are found one at a time. Since the overlaps are negated one at a time, the last negation takes precedence and can cause the body to be pushed into another body.

One workaround is to resolve each collision, update the collision system, and repeat until no collisions are found. Keep in mind that this can potentially lead to infinite loops if the two colliding bodies equally negate each other. Another solution is to collect all overlaps and combine them into a single resultant vector and then push the body out, but this can get rather complicated.

There is no perfect solution. How collisions are handled depends on the project.

collisions's People

Contributors

kefniark avatar shimshamsam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

collisions's Issues

Declaration file

I'm using this library with typescript, and to have types and Autocomplete, I wrote a declaration file (mostly based on your nice jsdoc).

I just would like to know your preference:

  • Can I add this declaration file to this repository and add a new field in package.json :
   "types": "collisions.d.ts",

Sweeping and linecasting

Hello,
I am using your collisions system for my game and, with some adaptation (converting it to module.exports - I couldn't get the import thing to work on node.js), it's working pretty well.
I have some questions however:

  1. Is the correct method for raycasting to create a 2 point polygon and checking its potentials? I haven't encountered issues, but there might be corner cases I don't know about.

  2. Sweeping items. Some items in my game will move pretty fast (ex. bullets) and I couldn't find a logic that handles this, therefore I think implementing pre-movement sweeping is necessary.
    I was wondering if it's a good approach to create a rectangle polygon with the widest points of the body being sweeped and a copy of the polygon at the target point. Then I check all the potentials for both, sorting them based on their distance from the original body. And for last, I use the x y overlap of the rectangle polygon (or the negative x y overlap if its closer to the original body than the middle of the rectangle of the body) as a point to move the original body to.

Thank you very much!

Is it possible to report contact point(s)?

Hi,
Only overlap information reported when collision happened. Is it possible to also provide contact points in report?
Overlap information provides contact normal vector, and penetration depth. But not contact points. To implement a proper physics engine, points are also needed to apply force from that points.

Btw love the work here.

Circle collision issue

I tried to use this library and realized some projectile were able to go through wall without triggering any collision (mostly circle collider).

The same issue is reproducible even in the samples.
For example, if we add few extra walls to Stress.mjs:

this.collisions.createPolygon(382.5, 255, [[-62.5, -5], [-62.5, 5], [62.5, 5], [62.5, -5]]);
this.collisions.createPolygon(507.5, 255, [[-62.5, -5], [-62.5, 5], [62.5, 5], [62.5, -5]]);
this.collisions.createPolygon(257.5, 255, [[-62.5, -5], [-62.5, 5], [62.5, 5], [62.5, -5]]);
this.collisions.createPolygon(132.5, 255, [[-62.5, -5], [-62.5, 5], [62.5, 5], [62.5, -5]]);

this.collisions.createPolygon(574.5, 697, [[-5, 62.5], [-5, -62.5], [5, -62.5], [5, 62.5]]);
this.collisions.createPolygon(574.5, 572, [[-5, 62.5], [-5, -62.5], [5, -62.5], [5, 62.5]]);
this.collisions.createPolygon(574.5, 447, [[-5, 62.5], [-5, -62.5], [5, -62.5], [5, 62.5]]);
this.collisions.createPolygon(574.5, 323, [[-5, 62.5], [-5, -62.5], [5, -62.5], [5, 62.5]]);

this.collisions.createPolygon(65, 697, [[-5, 62.5], [-5, -62.5], [5, -62.5], [5, 62.5]]);
this.collisions.createPolygon(65, 572, [[-5, 62.5], [-5, -62.5], [5, -62.5], [5, 62.5]]);
this.collisions.createPolygon(65, 447, [[-5, 62.5], [-5, -62.5], [5, -62.5], [5, 62.5]]);
this.collisions.createPolygon(65, 323, [[-5, 62.5], [-5, -62.5], [5, -62.5], [5, 62.5]]);

this.collisions.createPolygon(382.5, 764.5, [[-62.5, -5], [-62.5, 5], [62.5, 5], [62.5, -5]]);
this.collisions.createPolygon(507.5, 764.5, [[-62.5, -5], [-62.5, 5], [62.5, 5], [62.5, -5]]);
this.collisions.createPolygon(257.5, 764.5, [[-62.5, -5], [-62.5, 5], [62.5, 5], [62.5, -5]]);
this.collisions.createPolygon(132.5, 764.5, [[-62.5, -5], [-62.5, 5], [62.5, 5], [62.5, -5]]);

we can already see circle collider being able to go through some of the walls

image

Encapsulating body within a higher level object

I'm playing with the Tank demo and I would like to encapsulate a collision body within different objects, such as: target and bullet. This way when the bullet collides with a target, I can check what hit target (if the bullet, the tank or other) and react accordingly.

Unfortunately I can't seem to find the correct approach. When I iterate the body.potentials(); I can't backtrack to which object contained it and I can't seem to find a unique id that is assigned to a given body that I could use as reference.

Can you please advise on the correct approach?

Collisions sometimes not detecting

I have a small game with a circle player and some polygons and circles around (~1.1k colliders). Once and a while I will find that a collision is not detected between the player and an object. I don't know why this is happening as I use a class to create the objects and other objects collisions work perfectly. I don't think it's because I passed some mystical object limitation number, because objects created after the broken ones work fine.

I am using counter clock-wise polygons. Created like this:

var poly=new Polygon(x, y, ...);

system.insert(poly);

I know this repo is pretty dead but maybe someone faced a similar issue and can help. Thanks.

multiplayer sample

hey, can any help me.

can someone give me a little example. tank in multiplayer, with node.js.
just don't get it...
thanks

Could you release 2.0.14 to npm?

Hi!

It seems like the code in master is updated and it works well when importing the module from a parcel+typescript project.

The latest npm version (2.0.13) doesn't seem to work well on that kind of project though.

Could you release a new patch version with the latest master code?

For now (in case anyone else wants to use it), I've been adding "collisions": "git://github.com/Sinova/Collisions.git#f59299c1a333542187db37e99dcf8fa65bfa7ab3" to the package.json and pointing to the latest commit. Which works, but it is not the best.

Very cool library!

Can't import with browserify and babel

Im trying desperately to import this package but can't seem to get it to work.

Installing from npm and requiring the package as I would any other package throws this error:
ParseError: 'import' and 'export' may appear only with 'sourceType: module'

I then dug around for a few hours with babel, browserify, grunt-browserify, and did a lot of research. It just seems as though using .mjs files isn't that supported yet?

I then tried switching over to rollup however rollup had problems with most other libraries (fixing one, broke another). So i gave up on that. Babel it is.

I then tried setting this package as a git submodule, and then importing it relative to my code.
Brilliant!
But babel still didn't like the .mjs files.
So I created aliases for them all so hopefully names wouldnt mattter. I still couldn't get them to work.

Next i tried adding a grunt task to copy all the files to .js. The problem being that you're referencing the relative files with extensions. So that solution along with the aliases still don't work.

Is there any way i can get this to work without having to fork the repo? I think it would need to support standard commonjs imports.

Raycasting?

Any way to achieve raycasting with your lib? Also, great work!

How to get the normal of the line I collide with?

Hi,

Thanks for this library, we're looking forward to using it. After reading the documentation, we have one question however. How would one go about calculating the rebound vector / direction when a collision occurs? For example, when bouncing a ball against a wall, how do I make it bounce in the right direction?

Thank you

BVH insert issue

I was getting weird crash with the BVH tree used in this project.

After some research, I realized the issue was caused by my pooling system (which use insert/remove quite intensively). Because one of the object was created through:

const poly = system.createPolygon(200, 5, [[-30, 0], [10, 20]]);
// and not 
const poly = new Polygon(200, 5, [[-30, 0], [10, 20]]);

This API underneath do an .insert() automatically, and my code was doing a second one later.
Double .insert() can completely break the tree and create infinite loop (most of the time in potentials()).

This could be nice to avoid this kind of crash by:

  • preventing this kind of behavior
  • or automatically call remove when a double insert is detected
  • or throw an error.

In my case, even if it's a bit hacky, I just added a check on the _bhv property to prevent it:

	/**
	 * Inserts bodies into the collision system
	 * @param {...Circle|...Polygon|...Point} bodies
	 */
	insert(...bodies) {
		for(const body of bodies) {
			if (body._bvh) {
				continue;
			}
			this._bvh.insert(body, false);
		}

		return this;
	}

	/**
	 * Removes bodies from the collision system
	 * @param {...Circle|...Polygon|...Point} bodies
	 */
	remove(...bodies) {
		for(const body of bodies) {
			if (!body._bvh) {
				continue;
			}
			this._bvh.remove(body, false);
		}

		return this;
	}

Apparently there is already an error to prevent a body to be in two different tree:

throw new Error('Body belongs to another collision system');

confusing example about Line

I'm trying to use this package, but the example of how to create line is ambiguous:

If new Polygon(200, 5, [[-30, 0], [10, 20]]) is a line, why doesn't it only contains two point likes new Polygon(200, 5, [[-30, 0]]) ?
It looks likes a triangle.

mjs couse trouble in create-react-app

It throws Unhandled Rejection (TypeError): __WEBPACK_IMPORTED_MODULE_1_collisions___default.a is not a constructor

If I import Collisions, { Circle, Polygon } from 'collisions';

And console.log(Collisions) gives /static/media/Collisions.c776ea0e.mjs

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.