Mercurial > projects > chipmunkd
diff trunk/chipmunkd/cpShape.d @ 4:7ebbd4d05553
initial commit
author | Extrawurst |
---|---|
date | Thu, 02 Dec 2010 02:11:26 +0100 |
parents | |
children | c03a41d47b60 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/chipmunkd/cpShape.d Thu Dec 02 02:11:26 2010 +0100 @@ -0,0 +1,533 @@ + +// written in the D programming language + +module chipmunkd.cpShape; + +import chipmunkd.cpBody; +import chipmunkd.chipmunk; +import chipmunkd.chipmunk_types_h; +import chipmunkd.cpVect_h; +import chipmunkd.cpBB; + +import std.stdio; + +struct cpSegmentQueryInfo { + cpShape *shape; // shape that was hit, null if no collision + cpFloat t; // Distance along query segment, will always be in the range [0, 1]. + cpVect n; // normal of hit surface +} + +// Enumeration of shape types. +enum cpShapeType{ + CP_CIRCLE_SHAPE, + CP_SEGMENT_SHAPE, + CP_POLY_SHAPE, + CP_NUM_SHAPES +} + +// Shape class. Holds function pointers and type data. +struct cpShapeClass { + cpShapeType type; + + // Called by cpShapeCacheBB(). + cpBB function(cpShape *shape, cpVect p, cpVect rot) cacheData; + // Called to by cpShapeDestroy(). + void function(cpShape *shape) destroy; + + // called by cpShapePointQuery(). + cpBool function(cpShape *shape, cpVect p)pointQuery; + + // called by cpShapeSegmentQuery() + void function(cpShape *shape, cpVect a, cpVect b, cpSegmentQueryInfo *info) segmentQuery; +} + +// Basic shape struct that the others inherit from. +struct cpShape{ + // The "class" of a shape as defined above + /+const+/ cpShapeClass *klass; + + // cpBody that the shape is attached to. + cpBody* _body; + + // Cached BBox for the shape. + cpBB bb; + + // Sensors invoke callbacks, but do not generate collisions + cpBool sensor; + + // *** Surface properties. + + // Coefficient of restitution. (elasticity) + cpFloat e; + // Coefficient of friction. + cpFloat u; + // Surface velocity used when solving for friction. + cpVect surface_v; + + // *** User Definable Fields + + // User defined data pointer for the shape. + cpDataPointer data; + + // User defined collision type for the shape. + cpCollisionType collision_type; + // User defined collision group for the shape. + cpGroup group; + // User defined layer bitmask for the shape. + cpLayers layers; + + // *** Internally Used Fields + + // Shapes form a linked list when added to space on a non-null body + cpShape* next; + + // Unique id used as the hash value. + cpHashValue hashid; +} + +// +//// Low level shape initialization func. +//cpShape* cpShapeInit(cpShape *shape, const struct cpShapeClass *klass, cpBody *body); +// +//// Basic destructor functions. (allocation functions are not shared) +//void cpShapeDestroy(cpShape *shape); +//void cpShapeFree(cpShape *shape); +// +//// Cache the BBox of the shape. +//cpBB cpShapeCacheBB(cpShape *shape); +// +//// Test if a point lies within a shape. +//cpBool cpShapePointQuery(cpShape *shape, cpVect p); +// +//#define CP_DeclareShapeGetter(struct, type, name) type struct##Get##name(cpShape *shape) +// + +// Circle shape structure. +struct cpCircleShape{ + cpShape shape; + + // Center in body space coordinates + cpVect c; + // Radius. + cpFloat r; + + // Transformed center. (world space coordinates) + cpVect tc; +} + +//// Basic allocation functions for cpCircleShape. +//cpCircleShape *cpCircleShapeAlloc(void); +//cpCircleShape *cpCircleShapeInit(cpCircleShape *circle, cpBody *body, cpFloat radius, cpVect offset); +//cpShape *cpCircleShapeNew(cpBody *body, cpFloat radius, cpVect offset); +// +//CP_DeclareShapeGetter(cpCircleShape, cpVect, Offset); +//CP_DeclareShapeGetter(cpCircleShape, cpFloat, Radius); + +// Segment shape structure. +struct cpSegmentShape{ + cpShape shape; + + // Endpoints and normal of the segment. (body space coordinates) + cpVect a, b, n; + // Radius of the segment. (Thickness) + cpFloat r; + + // Transformed endpoints and normal. (world space coordinates) + cpVect ta, tb, tn; +} +// +//// Basic allocation functions for cpSegmentShape. +//cpSegmentShape* cpSegmentShapeAlloc(void); +//cpSegmentShape* cpSegmentShapeInit(cpSegmentShape *seg, cpBody *body, cpVect a, cpVect b, cpFloat radius); +//cpShape* cpSegmentShapeNew(cpBody *body, cpVect a, cpVect b, cpFloat radius); +// +//CP_DeclareShapeGetter(cpSegmentShape, cpVect, A); +//CP_DeclareShapeGetter(cpSegmentShape, cpVect, B); +//CP_DeclareShapeGetter(cpSegmentShape, cpVect, Normal); +//CP_DeclareShapeGetter(cpSegmentShape, cpFloat, Radius); +// +//// For determinism, you can reset the shape id counter. +//void cpResetShapeIdCounter(void); +// +//// Directed segment queries against individual shapes. +//void cpSegmentQueryInfoPrint(cpSegmentQueryInfo *info); +// +//cpBool cpShapeSegmentQuery(cpShape *shape, cpVect a, cpVect b, cpSegmentQueryInfo *info); +// +static cpVect +cpSegmentQueryHitPoint(const cpVect start, const cpVect end, const cpSegmentQueryInfo info) +{ + return cpvlerp(start, end, info.t); +} + +static cpFloat +cpSegmentQueryHitDist(const cpVect start, const cpVect end, const cpSegmentQueryInfo info) +{ + return cpvdist(start, end)*info.t; +} + +cpHashValue SHAPE_ID_COUNTER = 0; + +void +cpResetShapeIdCounter() +{ + SHAPE_ID_COUNTER = 0; +} + +cpShape* +cpShapeInit(cpShape *shape, /+const+/ cpShapeClass *klass, cpBody *_body) +{ + shape.klass = klass; + + shape.hashid = SHAPE_ID_COUNTER; + SHAPE_ID_COUNTER++; + + shape._body = _body; + shape.sensor = 0; + + shape.e = 0.0f; + shape.u = 0.0f; + shape.surface_v = cpvzero; + + shape.collision_type = 0; + shape.group = CP_NO_GROUP; + shape.layers = CP_ALL_LAYERS; + + shape.data = null; + shape.next = null; + +// cpShapeCacheBB(shape); + + return shape; +} + +void +cpShapeDestroy(cpShape *shape) +{ + if(shape.klass.destroy) shape.klass.destroy(shape); +} + +void +cpShapeFree(cpShape *shape) +{ + if(shape){ + cpShapeDestroy(shape); + cpfree(shape); + } +} + +cpBB +cpShapeCacheBB(cpShape *shape) +{ + cpBody *_body = shape._body; + + shape.bb = shape.klass.cacheData(shape, _body.p, _body.rot); + return shape.bb; +} + +cpBool +cpShapePointQuery(cpShape *shape, cpVect p){ + return shape.klass.pointQuery(shape, p); +} + +cpBool +cpShapeSegmentQuery(cpShape *shape, cpVect a, cpVect b, cpSegmentQueryInfo *info){ + cpSegmentQueryInfo blank = {null, 0.0f, cpvzero}; + (*info) = blank; + + shape.klass.segmentQuery(shape, a, b, info); + return (info.shape !is null); +} + +void +cpSegmentQueryInfoPrint(cpSegmentQueryInfo *info) +{ + writefln("Segment Query:\n"); + writefln("\tt: %s\n", info.t); +// writefln("\tdist: %f\n", info.dist); +// writefln("\tpoint: %s\n", cpvstr(info.point)); + //writefln("\tn: %s\n", cpvstr(info.n)); //TODO: +} + +cpCircleShape * +cpCircleShapeAlloc() +{ + return cast(cpCircleShape *)cpcalloc(1, cpCircleShape.sizeof); +} + +static cpBB +bbFromCircle(const cpVect c, const cpFloat r) +{ + return cpBBNew(c.x-r, c.y-r, c.x+r, c.y+r); +} + +static cpBB +cpCircleShapeCacheData(cpShape *shape, cpVect p, cpVect rot) +{ + cpCircleShape *circle = cast(cpCircleShape *)shape; + + circle.tc = cpvadd(p, cpvrotate(circle.c, rot)); + return bbFromCircle(circle.tc, circle.r); +} + +static cpBool +cpCircleShapePointQuery(cpShape *shape, cpVect p){ + cpCircleShape *circle = cast(cpCircleShape *)shape; + return cpvnear(circle.tc, p, circle.r); +} + +static void +circleSegmentQuery(cpShape *shape, cpVect center, cpFloat r, cpVect a, cpVect b, cpSegmentQueryInfo *info) +{ + // offset the line to be relative to the circle + a = cpvsub(a, center); + b = cpvsub(b, center); + + cpFloat qa = cpvdot(a, a) - 2.0f*cpvdot(a, b) + cpvdot(b, b); + cpFloat qb = -2.0f*cpvdot(a, a) + 2.0f*cpvdot(a, b); + cpFloat qc = cpvdot(a, a) - r*r; + + cpFloat det = qb*qb - 4.0f*qa*qc; + + if(det >= 0.0f){ + cpFloat t = (-qb - cpfsqrt(det))/(2.0f*qa); + if(0.0f<= t && t <= 1.0f){ + info.shape = shape; + info.t = t; + info.n = cpvnormalize(cpvlerp(a, b, t)); + } + } +} + +static void +cpCircleShapeSegmentQuery(cpShape *shape, cpVect a, cpVect b, cpSegmentQueryInfo *info) +{ + cpCircleShape *circle = cast(cpCircleShape *)shape; + circleSegmentQuery(shape, circle.tc, circle.r, a, b, info); +} + +static /+const+/ cpShapeClass cpCircleShapeClass = { + cpShapeType.CP_CIRCLE_SHAPE, + &cpCircleShapeCacheData, + null, + &cpCircleShapePointQuery, + &cpCircleShapeSegmentQuery, +}; + +cpCircleShape * +cpCircleShapeInit(cpCircleShape *circle, cpBody *_body, cpFloat radius, cpVect offset) +{ + circle.c = offset; + circle.r = radius; + + cpShapeInit(cast(cpShape *)circle, &cpCircleShapeClass, _body); + + return circle; +} + +cpShape * +cpCircleShapeNew(cpBody *_body, cpFloat radius, cpVect offset) +{ + return cast(cpShape *)cpCircleShapeInit(cpCircleShapeAlloc(), _body, radius, offset); +} +//TODO: +//CP_DefineShapeGetter(cpCircleShape, cpVect, c, Offset) +//CP_DefineShapeGetter(cpCircleShape, cpFloat, r, Radius) + +cpSegmentShape * +cpSegmentShapeAlloc() +{ + return cast(cpSegmentShape *)cpcalloc(1, cpSegmentShape.sizeof); +} + +static cpBB +cpSegmentShapeCacheData(cpShape *shape, cpVect p, cpVect rot) +{ + cpSegmentShape *seg = cast(cpSegmentShape *)shape; + + seg.ta = cpvadd(p, cpvrotate(seg.a, rot)); + seg.tb = cpvadd(p, cpvrotate(seg.b, rot)); + seg.tn = cpvrotate(seg.n, rot); + + cpFloat l,r,s,t; + + if(seg.ta.x < seg.tb.x){ + l = seg.ta.x; + r = seg.tb.x; + } else { + l = seg.tb.x; + r = seg.ta.x; + } + + if(seg.ta.y < seg.tb.y){ + s = seg.ta.y; + t = seg.tb.y; + } else { + s = seg.tb.y; + t = seg.ta.y; + } + + cpFloat rad = seg.r; + return cpBBNew(l - rad, s - rad, r + rad, t + rad); +} + +static cpBool +cpSegmentShapePointQuery(cpShape *shape, cpVect p){ + if(!cpBBcontainsVect(shape.bb, p)) return cpFalse; + + cpSegmentShape *seg = cast(cpSegmentShape *)shape; + + // Calculate normal distance from segment. + cpFloat dn = cpvdot(seg.tn, p) - cpvdot(seg.ta, seg.tn); + cpFloat dist = cpfabs(dn) - seg.r; + if(dist > 0.0f) return cpFalse; + + // Calculate tangential distance along segment. + cpFloat dt = -cpvcross(seg.tn, p); + cpFloat dtMin = -cpvcross(seg.tn, seg.ta); + cpFloat dtMax = -cpvcross(seg.tn, seg.tb); + + // Decision tree to decide which feature of the segment to collide with. + if(dt <= dtMin){ + if(dt < (dtMin - seg.r)){ + return cpFalse; + } else { + return cpvlengthsq(cpvsub(seg.ta, p)) < (seg.r*seg.r); + } + } else { + if(dt < dtMax){ + return cpTrue; + } else { + if(dt < (dtMax + seg.r)) { + return cpvlengthsq(cpvsub(seg.tb, p)) < (seg.r*seg.r); + } else { + return cpFalse; + } + } + } + + return cpTrue; +} + +static cpBool inUnitRange(cpFloat t){return (0.0f < t && t < 1.0f);} + +static void +cpSegmentShapeSegmentQuery(cpShape *shape, cpVect a, cpVect b, cpSegmentQueryInfo *info) +{ + // TODO this function could be optimized better. + + cpSegmentShape *seg = cast(cpSegmentShape *)shape; + cpVect n = seg.tn; + // flip n if a is behind the axis + if(cpvdot(a, n) < cpvdot(seg.ta, n)) + n = cpvneg(n); + + cpFloat an = cpvdot(a, n); + cpFloat bn = cpvdot(b, n); + + if(an != bn){ + cpFloat d = cpvdot(seg.ta, n) + seg.r; + cpFloat t = (d - an)/(bn - an); + + if(0.0f < t && t < 1.0f){ + cpVect point = cpvlerp(a, b, t); + cpFloat dt = -cpvcross(seg.tn, point); + cpFloat dtMin = -cpvcross(seg.tn, seg.ta); + cpFloat dtMax = -cpvcross(seg.tn, seg.tb); + + if(dtMin < dt && dt < dtMax){ + info.shape = shape; + info.t = t; + info.n = n; + + return; // don't continue on and check endcaps + } + } + } + + if(seg.r) { + cpSegmentQueryInfo info1 = {null, 1.0f, cpvzero}; + cpSegmentQueryInfo info2 = {null, 1.0f, cpvzero}; + circleSegmentQuery(shape, seg.ta, seg.r, a, b, &info1); + circleSegmentQuery(shape, seg.tb, seg.r, a, b, &info2); + + if(info1.t < info2.t){ + (*info) = info1; + } else { + (*info) = info2; + } + } +} + +static /+const+/ cpShapeClass cpSegmentShapeClass = { + cpShapeType.CP_SEGMENT_SHAPE, + &cpSegmentShapeCacheData, + null, + &cpSegmentShapePointQuery, + &cpSegmentShapeSegmentQuery, +}; + +cpSegmentShape * +cpSegmentShapeInit(cpSegmentShape *seg, cpBody *_body, cpVect a, cpVect b, cpFloat r) +{ + seg.a = a; + seg.b = b; + seg.n = cpvperp(cpvnormalize(cpvsub(b, a))); + + seg.r = r; + + cpShapeInit(cast(cpShape *)seg, &cpSegmentShapeClass, _body); + + return seg; +} + +cpShape* +cpSegmentShapeNew(cpBody *_body, cpVect a, cpVect b, cpFloat r) +{ + return cast(cpShape *)cpSegmentShapeInit(cpSegmentShapeAlloc(), _body, a, b, r); +} +//TODO: +//CP_DefineShapeGetter(cpSegmentShape, cpVect, a, A) +//CP_DefineShapeGetter(cpSegmentShape, cpVect, b, B) +//CP_DefineShapeGetter(cpSegmentShape, cpVect, n, Normal) +//CP_DefineShapeGetter(cpSegmentShape, cpFloat, r, Radius) + +// Unsafe API (chipmunk_unsafe.h) + +void +cpCircleShapeSetRadius(cpShape *shape, cpFloat radius) +{ + assert(shape.klass is &cpCircleShapeClass, "Shape is not a circle shape."); + cpCircleShape *circle = cast(cpCircleShape *)shape; + + circle.r = radius; +} + +void +cpCircleShapeSetOffset(cpShape *shape, cpVect offset) +{ + assert(shape.klass is &cpCircleShapeClass, "Shape is not a circle shape."); + cpCircleShape *circle = cast(cpCircleShape *)shape; + + circle.c = offset; +} + +void +cpSegmentShapeSetEndpoints(cpShape *shape, cpVect a, cpVect b) +{ + assert(shape.klass is &cpSegmentShapeClass, "Shape is not a segment shape."); + cpSegmentShape *seg = cast(cpSegmentShape *)shape; + + seg.a = a; + seg.b = b; + seg.n = cpvperp(cpvnormalize(cpvsub(b, a))); +} + +void +cpSegmentShapeSetRadius(cpShape *shape, cpFloat radius) +{ + assert(shape.klass is &cpSegmentShapeClass, "Shape is not a segment shape."); + cpSegmentShape *seg = cast(cpSegmentShape *)shape; + + seg.r = radius; +}