Impact

This forum is read only and just serves as an archive. If you have any questions, please post them on github.com/phoboslab/impact

1 decade ago by quidmonkey

I created a plugin for getting Box2D collision data into Impact (Note: This is for Box2d v2.0.2). It's a work in progress. Github link is here.

* Update *

Version 2.1 released! Big thanks to Joncom, Xatruch and pixelpusher, as I incorporated their code and formulated the version! This plugin is now as much theirs as it is mine. You can find both the new version and the previous version on my Github.

1 decade ago by quidmonkey

To use the plugin, override collideEntity() within your Box2DEntity to grab info about the colliding Entity - this is comparable to Impact's collideWith(). To grab tile info, override collideTile() - this is comparable to handleMovementTrace().

1 decade ago by quidmonkey

Since Box2D/Impact info is so scant, let me share with you some of my research and breakdown my collision plugin.

First, all Box2D collision per frame are broken down in an array of ContactEdges. This is what the for loops is cycling through:

for (var edge = this.body.m_contactList; edge; edge = edge.next)

For every edge of contact, Box2D creates a normal force vector, which is a perpendicular unit vector pointing away from the surface of contact. The plugin grabs the normal and uses this to calculate the point of contact & to determine if the Entity is standing. You can break the normal force down on both axes like so:

if( normal.x > 0 ){
//vector points right
}
else if( normal.x < 0 ){
//vector points left
}
else{
//point.x = 0 and thus, no horizontal collision
}

if( normal.y > 0 ){
//vector points down
}
else if( normal.y < 0 ){
//vector points up
}
else{
//point.y = 0 and thus, no vertical collision
}

The final tricky bit is this:

var f1 = this.shape.m_filter, f2 = ent.shape.m_filter;
if (f1.groupIndex != f2.groupIndex || f1.groupIndex > 0 || f1.categoryBits == f2.maskBits) {
     this.collideEntity(ent, point, normal);
}

Each Shape has a filter that it uses to filter out collisions with other Shapes. It does this in two ways: groupIndices & mask bits.

Filtering the groupIndex is a two-step process: first it checks to see if each Shape's groupIndex is equal, if so it then tests to see if it's positive or negative. If it's positive, those two Shapes will always collide; if it's negative, those two Shapes will never collide. Thus, if you have a bicycle with an Entity for each part (wheels, pedals, frame, etc.), you'll want to set the groupIndex for each to something like -8 so that the bicycle never collides with itself.

Filtering by mask bits is done by setting the categoryBits of the first Shape equal to the mask bits of the second. If those two match, the Shapes will collide. Box2D allows for 16 different categories for this type of filtering. You can read more here.

groupIndex testing gets precedence over mask bits. Thus, if a groupIndex is defined, Box2D will use it over the mask bits.

1 decade ago by fugufish

i was playing around with the plugin, but couldn't get it to work

i'm applying it to dominic's jetpack physics example (with Impact 1.18a)


// step 1: added your plugin

// step 2: this goes into EntityCrate

	collideEntity:function(other,point,normal){
		this.parent();

		if( normal.x > 0 ){
			console.log("vector points right");
		}
		else if( normal.x < 0 ){
			console.log("vector points left");
		}
		else{
			console.log("point.x = 0 and thus, no horizontal collision");
		}

		if( normal.y > 0 ){
			console.log("vector points down");
		}
		else if( normal.y < 0 ){
			console.log("vector points up");
		}
		else{
			console.log("point.y = 0 and thus, no vertical collision");
		}
	}


anything i'm missing? Probably bcos it's 3 am now , lol

1 decade ago by fugufish

seems like collideTile works, but collideEntity doesn't


EntityCrate = ig.Box2DEntity.extend({
	size: {x: 8, y: 8},
	
	type: ig.Entity.TYPE.B,
	checkAgainst: ig.Entity.TYPE.NONE,
	collides: ig.Entity.COLLIDES.NEVER,
	
	animSheet: new ig.AnimationSheet( 'media/crate.png', 8, 8 ),
	
	init: function( x, y, settings ) {
		this.addAnim( 'idle', 1, [0] );
		this.parent( x, y, settings );
	},
	
	// doesn't work
	collideEntity:function(other,point,normal){
		this.parent();
		console.log(other,point,normal);
	},

	// works, because the crates touch the tiles
	collideTile:function(tile,point,normal){
		this.parent();
		console.log(tile,point,normal);
	}
		
});

1 decade ago by quidmonkey

Did you include the plugin in main.js? Are you getting some sort of error?

Make sure crate is calling its parent update(). You don't need to call this.parent() in collideEntity().

I take it you're testing the collision between the projectile & crate?

Hmm...also, do a console.log on the crate.init(). See what the default groupIndex is.

1 decade ago by quidmonkey

Change line 45 of plugin to this:

if (!f1.groupIndex || f1.groupIndex != f2.groupIndex || f1.groupIndex > 0 || f1.categoryBits == f2.maskBits) {
                        this.collideEntity(ent, point, normal);
                    }

Need to test for groupIndex being undefined.

1 decade ago by fugufish

ok got it,

i needed to set the groupIndex for each entity type differently.

        // for each entity type : Eg: player get's groupIndex of 1, crate gets 2, bullet gets 3
	createBody: function() {
		var bodyDef = new b2.BodyDef();
		bodyDef.position.Set(
			(this.pos.x + this.size.x / 2) * b2.SCALE,
			(this.pos.y + this.size.y / 2) * b2.SCALE
		);

		this.body = ig.world.CreateBody(bodyDef);

		var shapeDef = new b2.PolygonDef();
		shapeDef.SetAsBox(
			this.size.x / 2 * b2.SCALE,
			this.size.y / 2 * b2.SCALE
		);


		shapeDef.density = 1;
		shapeDef.restitution = 0;
		shapeDef.friction = 1;

                // collision detection setup
		shapeDef.filter.groupIndex = 1; 

		this.body.CreateShape(shapeDef);
		this.body.SetMassFromShapes();
	},

1 decade ago by quidmonkey

That'll work, though you'll run into issues later with everything having the same groupIndex. I'd advise adding the additional undefined test to line 45 of the plugin. I updated both the OP and Github. Enjoy!

1 decade ago by fugufish

small fix line 43 :

  // double dots after 'this'
  var f1 = this..shape.m_filter,

1 decade ago by quidmonkey

Fixed. Thx.

1 decade ago by Xatruch

Thanks so much quidmonkey for this plugin ^^

1 decade ago by PaulManjarres

Hi, I started using the ImpactJS framework with Box2d Plugin and Collision plugin, everything works perfects thanks. But I run into an issue, when I have a rotated entity the collideTile method fails,it's detecting the collision before the actual object touch a tile. I already use the box2d debug draw to see if the body object was wrong, but no, Box2d is handling correctly the collision, but the collision plugin is calling the collideTile method before the collision. The problem only happens when I rotate the player Body.
Any advice would be really appreciated. Thanks so much for your time.

1 decade ago by quidmonkey

Paul, could you post your code where this is happening?

1 decade ago by PaulManjarres

Hi thanks for the answer. The code is based on the ImpactJS Box2D demo. I have a simple player that can go up like the jetpack example with a force applied. The player also can rotate left or right, but when he rotate the collision plugin tells a collisionTile when the player is close to a tile, but without touching it

Without rotation, everything works perfect.

Here is part of my entity class, the rest is almost the same that the examples.

currentAngle: 0,

collideTile:function(tile,point,normal){			
			console.log(tile,point,normal);
},
update: function(){		
			// space key pressed
			if( ig.input.state('space_pressed) ) {
                                // Apply a force in the direction of the player's head
                                // 1.578 =  PI/2
				this.body.ApplyForce(
					new b2.Vec2(-100*Math.cos(this.currentAngle+1.5708)
						    ,100*Math.sin(this.currentAngle-1.5708)),
					this.body.GetPosition() );
			}			
				
		// right arrow key pressed, rotate the player clockwise.
		if(ig.input.state('right')){		
			if(this.currentAngle > 6.2832){
				this.currentAngle = 0;				
			}
			this.currentAngle+=0.1;						
		}		
 
                //left arrow key pressed, rotate the player counter clockwise
		if(ig.input.state('left')){		
			if(this.currentAngle < -6.2832){
				this.currentAngle = 0;				
			}			
			this.currentAngle-=0.1;						
		}	

                // Apply the rotation to the entity body
		this.body.SetXForm(this.body.GetPosition(), this.currentAngle);
		this.parent();
	}


Thanks.

1 decade ago by quidmonkey

Paul-

I believe it's a question of timing. Here's the Entity update loop from my code:
update: function () {
       this.parent();
       this.collision();
}

Notice that collision detection happens after the .update() loop. Try exchanging the method calls so that collision detection happens before the .update() loop:
update: function () {
       this.collision();
       this.parent();
}

1 decade ago by quidmonkey

And try placing the call to this.parent() at the top of your Entity's .update():
update: function(){        
    this.parent();

    //change angle code

    // Apply the rotation to the entity body
    this.body.SetXForm( this.body.GetPosition(), this.currentAngle );
}

1 decade ago by PaulManjarres

Hi thanks for your answer, I just tried that code but no luck, the problem is still the same.
Don't know if anyone had this problem ever :S.

1 decade ago by quidmonkey

Unfortunately, I no longer have an active build to test this with, but try replacing:
//calculate point of contact
var x = this.pos.x + normal.x.map(1, -1, 0, 1) * this.size.x;
var y = this.pos.y + normal.y.map(1, -1, 0, 1) * this.size.y;
var point = {
    x: x,
    y: y
};

With this:
var point = edge.contact.m_manifold.points[0],
	x = point.x,
	y = point.y;

1 decade ago by PaulManjarres

Hi!, sorry for the absence. I test the adjustement you gave me but it's not working either. The collision is reported way before it actually happens, only when the body had been rotated.
I had to go with Impactjs without box2d because of time but I'll try to do more tests to this issue.
Thanks.

1 decade ago by Xatruch

Hi paul, I had the same problem. The way quidmonkey does its collision detection is by AABB rectangles similar to impacts. After some investigation, i found this on the web:

A very important point to note if you do this, is that the existence of a contact in these lists does not mean that the two fixtures of the contact are actually touching - it only means their AABBs are touching. If you want to know if the fixtures themselves are really touching you can use IsTouching() to check. Moron that later.


- http://www.iforce2d.net/b2dtut/collision-anatomy

So this is the way I do to make collision detection:

on your player entity:

init: function(x, y, settings) {
 this.parent(x, y, settings);
 if(!ig.global.wm) {
    this.body.m_userData = 'player';
    this.body.entity = this;
 } 
}

on lib\plugins\box2d\game.js
add a new method.
 addContactListener: function() {
		var listener = new b2.ContactListener();
		listener.Add = function(point){
			//console.log('Add Contact', point);
			var body1 = point.shape1.GetBody();
			var body2 = point.shape2.GetBody();

			// player vs enemy
			if(
			   (body1.m_userData == 'player' || body2.m_userData == 'player') &&
			   (body1.m_userData == 'enemy' || body2.m_userData == 'enemy')
			   ) {
				// diferenciando los entities
				var eA = body1.m_userData == 'player' ? body1.entity : body2.entity; 
				var eB = body1.m_userData == 'enemy' ? body1.entity : body2.entity;
				
				eA.hit(5, eB);
			}

		//listener.Remove = function(point) { 
			//console.log('Remove Contact'); 
		//}
		//listener.Persist = function(point) { 
		//	console.log('Persist Contact'); 
		//}
		//listener.Result = function(point) { 
		//	console.log('Result Contact'); 
		//}
		
		ig.world.SetContactListener(listener);
	},




then run it on the loadLevel function:

loadLevel: function( data ) {
		
		// Find the collision layer and create the box2d world from it
		for( var i = 0; i < data.layer.length; i++ ) {
			var ld = data.layer[i];
			if( ld.name == 'collision' ) {
				ig.world = this.createWorldFromMap( ld.data, ld.width, ld.height, ld.tilesize );
				break;
			}
		}
                this.addContactListener();
		
		this.parent( data );
	},

Hope this is useful.

1 decade ago by PaulManjarres

Hi Xatruch !, thank you very much for the help. That works for me!. Now I'm able to handle the collisions the way I needed.

Thanks!.

1 decade ago by quidmonkey

Great stuff, Xatruch. You should create a plugin yourself, since your method is superior. I may retire mine.

1 decade ago by Xatruch

Hi quidmonkey thanks for the words. I really don't know how I can make this as a plugin?
I'll appreciate if you or anybody could help me?

1 decade ago by pixelpusher

even better, you can simply override Impact's built-in collision detection with Box2D using this method. As above, in lib\plugins\box2d\game.js:

addContactListener: function() {
        var listener = new b2.ContactListener();
        listener.Add = function(point){
            //console.log('Add Contact', point);
            var body1 = point.shape1.GetBody();
            var body2 = point.shape2.GetBody();

//console.log(body1);

		// if just background collision, ignore
		if (body1.entity == null || body2.entity == null)
		return;

//console.log(body1.entity.checkAgainst, body2.entity.type);

			// Skip entities that don't check, don't get checked and don't collide
			if (
				body1.entity.type == ig.Entity.TYPE.NONE &&
				body1.entity.checkAgainst == ig.Entity.TYPE.NONE &&
				body1.entity.collides == ig.Entity.COLLIDES.NEVER
			) 
			return;
			
			ig.Entity.checkPair( body1.entity, body2.entity );
			
        //listener.Remove = function(point) { 
            //console.log('Remove Contact'); 
        //}
        //listener.Persist = function(point) { 
        //    console.log('Persist Contact'); 
        //}
        //listener.Result = function(point) { 
        //    console.log('Result Contact'); 
        //}
        }
        ig.world.SetContactListener(listener);
    }	

and then in /lib/impact/game.js, simply comment out all the code inside
##
checkEntities: function() {

}
##

. Now you can still use the collisions types, entity types, and check() function!

1 decade ago by pixelpusher

Also, I found that objects weren't being destroyed properly, so I edited the update() function of the /lib/impact/game.js file as follows:

// remove all killed entities
		for( var i = 0; i < this._deferredKill.length; i++ ) {
			if (this._deferredKill[i] instanceof ig.Box2DEntity) 
			{
				if (this._deferredKill[i].body != null)
				ig.world.DestroyBody(this._deferredKill[i].body);
				console.log('pop');
			}
			this.entities.erase( this._deferredKill[i] );
		}
		this._deferredKill = [];	

1 decade ago by quidmonkey

Xatruch & pixelpusher:
ig.module(
	'plugins.box2d.collision'
)
.requires(
	'plugins.box2d.entity',
	'plugins.box2d.game'
)
.defines(function(){

ig.Box2DEntity.inject({

	init: function (x, y, settings) {
		this.parent(x, y, settings);
		if (!ig.global.wm) {
			this.body.entity = this;
		}
	}

});

ig.Box2DGame.inject({

	// remove impact's collision detection
	// for performance
	checkEntities: function () {},

	loadLevel: function (data) {
		this.parent(data);

		// create impact collision listener
		var listener = new b2.ContactListener();
		listener.Add = function(point){
			var a = point.shape1.GetBody().entity,
				b = point.shape2.GetBody().entity;

			// is this an entity collision?
			if (!a || !b) {
				return;
			}

			// preserve impact's entity checks even
			// though these are unnecessary
			if (a.checkAgainst & b.type) {
				a.check(b);
			}
			
			if (b.checkAgainst & a.type) {
				b.check(a);
			}

			// call impact
			if (point.normal.y) {
				a.collideWith(b, 'y');
				b.collideWith(a, 'y');
			}
			else {
				a.collideWith(b, 'x');
				b.collideWith(a, 'x');
			}
		};

		// attach to box2d world
		ig.world.SetContactListener(listener);
	}

});

});

This is an elegant solution. Good job!

This way will allow you to use Impact's check and collideWith functions and will make box2d more modular.

1 decade ago by quidmonkey

New version of the plugin released! OP has been updated with the details. Once again, a big thanks to Xatruch and pixelpusher! Hopefully this will now unlock the black box that is Box2d for Impact.

1 decade ago by number101010

Thanks for all the work guys. This is just what I was looking for, but when I used it I wasn't getting events for collisions with the environment (handleMovementTrace). In collision.js I changed the body to the below code.

I didn't call handleMovementTrace itself because I'm not passing a res object and it was causing exceptions in code that was expecting a valid res. However, I think with some more work the res object could be rebuilt from the information available and the correct function call could be made.

//is this a tile collision?
if (!a || !b)
{			
	if(a) { if(a.collideTile) { a.collideTile(); } } //jpe - fix this? i.e. pass res object? http://impactjs.com/documentation/class-reference/collisionmap#trace
	if(b) { if(b.collideTile) { b.collideTile(); } }
}
// is this an entity collision?
else
{
	// preserve impact's entity checks even
	// though these are unnecessary
	if (a.checkAgainst & b.type) {
		a.check(b);
	}
	
	if (b.checkAgainst & a.type) {
		b.check(a);
	}

	// call impact
	if (point.normal.y) {
		a.collideWith(b, 'y');
		b.collideWith(a, 'y');
	}
	else {
		a.collideWith(b, 'x');
		b.collideWith(a, 'x');
	}
}

1 decade ago by CACUser

Hi,
I am a newbie to impactjs. I looked at the Box2d physics sample that Dom put out. I was trying to combine box2d entities and regular impactjs entities. Is this possible? Can I use the plugin mentioned here? Also I started with Dom's physics Box2d sample - I believe the version is 1.18. How/where can I get Box2d v2.0.2 for impactjs?

Thanks in advance, much appreciated!
Page 1 of 2
« first « previous next › last »