Advanced overhead friction

Official forum for the Chipmunk2D Physics Library.
Post Reply
norfenstein
Posts: 3
Joined: Sat Mar 03, 2012 4:47 pm
Contact:

Advanced overhead friction

Post by norfenstein »

I've been evaluating Chipmunk for use in an overhead 2D game and want to do a few things more complex than what's demonstrated in the Tank example. Namely:
  1. Have different areas with different friction. So instead of all objects having the same linear and angular friction everywhere, be able to define "ground" shapes with arbitrary friction values for the bodies touching them.
  2. Allow moving ground, i.e. platforms that can spin or slide around and react appropriately to the bodies "on top" of them (imagine a carousel that might fling objects off it by spinning too fast, or a raft docked on a beach that a player could launch with the momentum from jumping onto it).
  3. Support surface velocity -- both for conveyor belt type ground and regular old player movement. Should interact realistically with movable platforms.
I've made some progress and actually have something that seems to mostly handle points 1 and 2, but I'm somewhat hampered by not having a strong grasp of the physics involved nor how best to do this in Chipmunk. I'd appreciate some feedback on whether what I'm doing makes sense (and is in fact reasonably realistic), how to proceed, and if there's a better solution entirely.


I have three logical "layers" for shapes (they're actually collision types in Chipmunk): GROUND, ON_GROUND, and IN_AIR. The preSolve() collision handler for GROUND/ON_GROUND pairs returns false, and the begin() handler for them sets up the joints to simulate the linear and angular friction. The separate() handler removes those joints. This, so far, works fine for ground that never moves: I can set up gear and pivot joints anchored at the centers of the touching bodies and it behaves just like in the Tank demo.

For movable platforms I made the compromise of only simulating one "contact point" between GROUND and ON_GROUND objects: in the preSolve() callback of the pivot constraint I update the ground object's anchor to be the current position of the "on ground" object. This seemed to work well enough: an ON_GROUND object moving onto a GROUND object at an offset appears to spin it realistically enough. If you try with non-moving ground, however, it's easy to see that its behavior is not correct -- instead of sliding straight, ON_GROUND object will curve around the center of the GROUND shape due to the nature of the pivot joint.

I had hoped to avoid it, but I'm not majorly opposed to modifying Chipmunk itself, so I next tried creating a new joint that would behave like a PivotJoint, but with less... pivoting. This is where my understanding of Chipmunk and the maths involved started to break down. Without really knowing how it works, I discovered that this line in cpPivotJoint's applyImpulse function:

Code: Select all

cpVect j = mult_k(cpvsub(joint->bias, vr), joint->k1, joint->k2);
when replaced with this:

Code: Select all

cpVect j = cpvsub(joint->bias, vr);
removes that curving over stationary platforms while maintaining correct-looking behavior for mobile platforms.

I don't think this mutant "friction joint" is particularly stable, however (it breaks down entirely if MaxForce is too high, and can be weirdly jittery where a pivot joint would be stable), and I'd like a better understanding of what's going on before I try using it seriously. I'm also pretty unsure of how to incorporate surface velocity into this. Can any one point me in a better direction, or at least help me to understand how this should work mathematically? I can provide source code for my test project if it'd help.
User avatar
slembcke
Site Admin
Posts: 4166
Joined: Tue Aug 14, 2007 7:13 pm
Contact:

Re: Advanced overhead friction

Post by slembcke »

Hmm. I don't have a lot of time to go too in depth with this right now. Getting ready to head out to GDC in just a few hours.

So the Tank demo works by adding joints between the objects and a static body. What you can do instead is to make a separate body for each moving platform and use the static one for non-moving ground so that you can enable the sleeping features. For the moving platforms, you only need to update their velocity when it changes. You don't need to update the position (because position correction is disabled anyway), and you don't need to update their velocity if it's constant.

The trick with this is that you'll need to add and remove joints between these different bodies as the objects move from one ground to another. Chipmunk can't really help you out too much with that though. You might be able to use a second space that you use only for sensor callbacks or something as the object moves around maybe? Probably the easiest thing to do is just to have a list of moving platforms and check if the center of your object is on them each frame.
Can't sleep... Chipmunks will eat me...
Check out our latest projects! -> http://howlingmoonsoftware.com/wordpress/
norfenstein
Posts: 3
Joined: Sat Mar 03, 2012 4:47 pm
Contact:

Re: Advanced overhead friction

Post by norfenstein »

I'll give an example of what I have so far. I've been using Chipmunk's excellent demo framework as a testbed; below is demo code that can be slotted in with the stock Chipmunk demos (I'm using version 6.0.3).

Code: Select all

#include <stdlib.h>
#include <math.h>

#include "chipmunk.h"
#include "ChipmunkDemo.h"

enum COLLISION_TYPES {
	GROUND = 1,
	ON_GROUND,
	IN_AIR
};

static cpSpace *space;



static void pivotAdjust(cpConstraint *pivot, cpSpace *space) {
	cpBody *ground = pivot->a, *object = pivot->b;
	cpPivotJointSetAnchr1(pivot, cpBodyWorld2Local(ground, cpBodyGetPos(object)));
}
static void addGroundConstraints(cpSpace *space, cpBody *ground, cpBody *object){
	cpConstraint *constraint;

	//angular friction
	if (cpBodyGetMoment(object) != INFINITY) {
		constraint = cpSpaceAddConstraint(space, cpGearJointNew(ground, object, 0.0f, 1.0f));
		cpConstraintSetMaxBias(constraint, 0);
		cpConstraintSetMaxForce(constraint, 10000.0f);
	}

	//linear friction
	constraint = cpSpaceAddConstraint(space, cpPivotJointNew2( ground, object, cpBodyWorld2Local(ground, cpBodyGetPos(object)), cpvzero));
	cpConstraintSetPreSolveFunc(constraint, (cpConstraintPreSolveFunc)pivotAdjust);
	cpConstraintSetMaxBias(constraint, 0);
	cpConstraintSetMaxForce(constraint, 2000.0f);
}
static int groundBegin(cpArbiter *arb, cpSpace *space, void *data){
	CP_ARBITER_GET_BODIES(arb, ground, object);
	cpSpaceAddPostStepCallback(space, (cpPostStepFunc)addGroundConstraints, ground, object);

	return cpTrue;
}

static int groundPreSolve(cpArbiter *arb, cpSpace *space, void *data){
	return cpFalse;
}

static void removeConstraint(cpBody *a, cpConstraint *constraint, cpBody *b){
	if(cpConstraintGetA(constraint) == a && cpConstraintGetB(constraint) == b){
		cpSpaceRemoveConstraint(space, constraint);
	}
}
static void removeCollisionConstraints(cpSpace *space, cpBody *a, cpBody *b){
	cpBodyEachConstraint(a, (cpBodyConstraintIteratorFunc)removeConstraint, b);
}
static void groundSeparate(cpArbiter *arb, cpSpace *space, void *data){
	CP_ARBITER_GET_BODIES(arb, ground, object);
	cpSpaceAddPostStepCallback(space, (cpPostStepFunc)removeCollisionConstraints, ground, object);
}



static void addBoundaries(cpSpace *space, int width, int height, cpFloat elasticity, cpFloat friction) {
	cpBody *staticBody = cpSpaceGetStaticBody(space);
	cpFloat hWidth = width / 2.0f, hHeight = height / 2.0f;
	cpShape *shape;

	//left
	shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-hWidth,-hHeight), cpv(-hWidth,hHeight), 0.0f));
	cpShapeSetElasticity(shape, elasticity);
	cpShapeSetFriction(shape, friction);
	cpShapeSetLayers(shape, NOT_GRABABLE_MASK);

	//right
	shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(hWidth,-hHeight), cpv(hWidth,hHeight), 0.0f));
	cpShapeSetElasticity(shape, elasticity);
	cpShapeSetFriction(shape, friction);
	cpShapeSetLayers(shape, NOT_GRABABLE_MASK);

	//bottom
	shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-hWidth,-hHeight), cpv(hWidth,-hHeight), 0.0f));
	cpShapeSetElasticity(shape, elasticity);
	cpShapeSetFriction(shape, friction);
	cpShapeSetLayers(shape, NOT_GRABABLE_MASK);

	//top
	shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-hWidth,hHeight), cpv(hWidth,hHeight), 0.0f));
	cpShapeSetElasticity(shape, elasticity);
	cpShapeSetFriction(shape, friction);
	cpShapeSetLayers(shape, NOT_GRABABLE_MASK);
}

static void frictionTest(cpSpace *space) {
	cpSpaceSetGravity(space, cpvzero);

	cpBody *body;
	cpShape *shape;
	cpConstraint *constraint;
	cpFloat size;
	cpVect pos;

	//Non-moving platform
	size = 180.0f;
	pos = cpv(-90, 60);
#if 0
	shape = cpSpaceAddShape(space, cpBoxShapeNew2(cpSpaceGetStaticBody(space), cpBBNew(pos.x - size / 2, pos.y - size / 2, pos.x + size / 2, pos.y + size / 2)));
#else
	body = cpSpaceAddBody(space, cpBodyNew(1.0f, cpMomentForBox(1.0f, size, size)));
	cpBodySetPos(body, pos);
	shape = cpSpaceAddShape(space, cpBoxShapeNew(body, size, size));
	cpSpaceAddConstraint(space, cpPivotJointNew(cpSpaceGetStaticBody(space), body, cpvadd(pos, cpv(size/2-10, size/2-10))));
	cpSpaceAddConstraint(space, cpPivotJointNew(cpSpaceGetStaticBody(space), body, cpvsub(pos, cpv(size/2-10, size/2-10))));
#endif
	cpShapeSetElasticity(shape, 0.0f);
	cpShapeSetFriction(shape, 1.0f);
	cpShapeSetCollisionType(shape, GROUND);
	cpShapeSetLayers(shape, NOT_GRABABLE_MASK);

	//Spinning platform
	size = 90.0f;
	pos = cpv(180, 0);
	body = cpSpaceAddBody(space, cpBodyNew(1.0f, cpMomentForCircle(1.0f, 0.0f, size, cpvzero)));
	cpBodySetPos(body, pos);
	shape = cpSpaceAddShape(space, cpCircleShapeNew(body, size, cpvzero));
	cpShapeSetCollisionType(shape, GROUND);
	constraint = cpSpaceAddConstraint(space, cpPivotJointNew(cpSpaceGetStaticBody(space), body, pos));

	//Moving non-ground object
	size = 30.0f;
	pos = cpv(-240, 0);
	body = cpSpaceAddBody(space, cpBodyNew(1.0f, cpMomentForBox(1.0f, size, size)));
	cpBodySetPos(body, pos);
	shape = cpSpaceAddShape(space, cpBoxShapeNew(body, size, size));
	cpShapeSetCollisionType(shape, ON_GROUND);

	cpBodyApplyImpulse(body, cpv(1000, 0), cpvzero);
}

static cpSpace * init(void) {
	space = cpSpaceNew();

	cpSpaceAddCollisionHandler(space, GROUND, ON_GROUND, groundBegin, groundPreSolve, NULL, groundSeparate, NULL);

	addBoundaries(space, 640, 480, 1.0f, 1.0f);
	
	frictionTest(space);
	
	return space;
}

static void update(int ticks) {
	int steps = 3;
	cpFloat dt = 1.0f/60.0f/(cpFloat)steps;
	
	for(int i=0; i<steps; i++){
		cpSpaceStep(space, dt);
	}
}

static void destroy(void) {
	ChipmunkDemoFreeSpaceChildren(space);
	cpSpaceFree(space);
}

ChipmunkDemo ZFriction = {
	"ZFriction",
	init,
	update,
	ChipmunkDemoDefaultDrawImpl,
	destroy,
};
The adding and removing of joints I have working nicely with begin() and separate() collision handlers. And for linear friction, using a pivot joint -- at least when the ground bodies are static -- appears to behave (mostly) correctly. I just don't understand why using a dynamic body (even if it's not actually able to move) produces different (incorrect) behavior.

In my demo I have an ON_GROUND box that is impulsed across two GROUND platforms. The first platform is fixed in place* -- the moving box should just be slowed down by this platform and not have its trajectory altered. The second platform is pinned in its center, allowing it to spin but not otherwise move. The box is aligned with the second platform so that if its approach is orthogonal the platform shouldn't spin. With a static body for the first platform the box flies straight, with a dynamic body it pivots slightly around the center of the ground shape. I have no idea why this would be different for static bodies. I'm sure my strategy of using a cpConstraintPreSolveFunc callback to update the pivot joint's anchor is somehow problematic, but I don't know of any other method.

Does the example make what I'm trying to do a little clearer?

*I use two pivot joints for this, which I realize you're not supposed to do. It's a contrived example (a real game scenario could, for example, have a movable platform jammed in a corner or something). Regardless, I think it shows that something isn't right.
Post Reply

Who is online

Users browsing this forum: No registered users and 2 guests