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.