mirror of https://github.com/axmolengine/axmol.git
945 lines
41 KiB
HTML
945 lines
41 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>Chipmunk Game Dynamics Documentation</title>
|
|
<style type="text/css">
|
|
h1 {
|
|
text-align: center;
|
|
font-size: 300%;
|
|
}
|
|
|
|
h2, h3 {
|
|
background-color: BurlyWood;
|
|
padding: 3px;
|
|
}
|
|
|
|
p {
|
|
margin-left: 1em;
|
|
}
|
|
|
|
p.expl {
|
|
margin-left: 2em;
|
|
}
|
|
|
|
pre {
|
|
background-color: lightGrey;
|
|
padding: 3px;
|
|
margin-left: 1em;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h1>Chipmunk Game Dynamics</h1>
|
|
|
|
|
|
<h2>Introduction</h2>
|
|
|
|
|
|
<p>First of all, Chipmunk is a 2D rigid body physics library distributed under the <span class="caps">MIT</span> license. Though not yet complete, it is intended to be fast, numerically stable, and easy to use.</p>
|
|
|
|
|
|
<p>It’s been a long time in coming, but I’ve finally made a stable and usable physics implementation. While not feature complete yet, it’s well on it’s way. I would like to give a Erin Catto a big thank you, as the most of the ideas for the constraint solver come from his Box2D example code. (<a href="http://www.gphysics.com/">gPhysics Website</a>). His contact persistence idea allows for stable stacks of objects with very few iterations of the contact solution. Couldn’t have gotten that working without his help.</p>
|
|
|
|
|
|
<h2>Overview</h2>
|
|
|
|
|
|
<ul>
|
|
<li><strong>rigid bodies:</strong> A rigid body holds the physical properties of an object. (mass, position, rotation, velocity, etc.) It does not have a shape by itself. If you’ve done physics with particles before, rigid bodies differ mostly in that they are able to rotate.</li>
|
|
<li><strong>collision shapes:</strong> By attaching shapes to bodies, you can define the a body’s shape. You can attach many shapes to a single body to define a complex shape, or none if it doesn’t require a shape.</li>
|
|
<li><strong>joints:</strong> You can attach joints between two bodies to constrain their behavior.</li>
|
|
<li><strong>spaces:</strong> Spaces are the basic simulation unit in Chipmunk. You add bodies, shapes and joints to a space, and then update the space as a whole.</li>
|
|
</ul>
|
|
|
|
|
|
<p><strong>Rigid bodies, collision shapes and sprites:</strong></p>
|
|
|
|
|
|
<p>There is often confusion between rigid bodies and their collision shapes in Chipmunk and how they relate to sprites. A sprite would be a visual representation of an object, the sprite is drawn at the position of the rigid body. The collision shape would be the material representation of the object, and how it should collide with other objects. A sprite and collision shape have little to do with one another other than you probably want the collision shape to match the sprite’s shape.</p>
|
|
|
|
|
|
<h2><span class="caps">C API</span> Documentation</h2>
|
|
|
|
|
|
<h3>Initializing Chipmunk</h3>
|
|
|
|
|
|
<p>Initializing Chipmunk is an extremely complicated process. The following code snippet steps you through the process:</p>
|
|
|
|
|
|
<pre><code>
|
|
cpInitChipmunk(); /* Actually, that's pretty much it */
|
|
</code></pre>
|
|
|
|
<h3>Chipmunk memory management</h3>
|
|
|
|
|
|
<p>For many of the structures you will use, Chipmunk uses a more or less standard set of memory management functions. For instance:</p>
|
|
|
|
|
|
<ul>
|
|
<li><code>cpSpaceAlloc()</code> allocates but does not initialize a <code>cpSpace</code> struct.</li>
|
|
<li><code>cpSpaceInit(space, other_args)</code> initializes a <code>cpSpace</code> struct.</li>
|
|
<li><code>cpSpaceNew(args)</code> allocates and initializes a <code>cpSpace</code> struct using <code>args</code>.</li>
|
|
<li><code>cpSpaceDestroy(space)</code> frees all dependancies, but does not free the <code>cpSpace</code> struct.</li>
|
|
<li><code>cpSpaceFree(space)</code> frees all dependancies and the <code>cpSpace</code> struct.</li>
|
|
</ul>
|
|
|
|
|
|
<p>While you will probably use the new/free versions exclusively, the others can be helpful when writing language extensions.</p>
|
|
|
|
|
|
<p>In general, you are responsible for freeing any structs that you allocate. The only exception is that <code>cpShapeDestroy()</code> also destroys the specific shape struct that was passed to the constructor.</p>
|
|
|
|
|
|
<h3>Chipmunk floats: <code>cpFloat</code></h3>
|
|
|
|
|
|
<pre><code>
|
|
typedef float cpFloat;
|
|
</code></pre>
|
|
|
|
<p>All Chipmunk code should be <code>double</code> safe, so feel free to redefine this.</p>
|
|
|
|
|
|
<h3>Chipmunk vectors: <code>cpVect</code></h3>
|
|
|
|
|
|
<p><strong>User accessible fields:</strong></p>
|
|
|
|
|
|
<pre><code>
|
|
typedef struct cpVect{
|
|
cpFloat x,y;
|
|
} cpVect
|
|
</code></pre>
|
|
|
|
<p class="expl">Simply a 2D vector packed into a struct. May change in the future to take advantage of <span class="caps">SIMD</span>.</p>
|
|
|
|
|
|
<pre><code>
|
|
#define cpvzero ((cpVect){0.0f, 0.0f})
|
|
</code></pre>
|
|
|
|
<p class="expl">Constant for the zero vector.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpv(const cpFloat x, const cpFloat y)
|
|
</code></pre>
|
|
|
|
<p class="expl">Convenience constructor for creating new <code>cpVect</code> structs.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvadd(const cpVect v1, const cpVect v2)
|
|
cpVect cpvsub(const cpVect v1, const cpVect v2)
|
|
</code></pre>
|
|
|
|
<p class="expl">Add or subtract two vectors.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvneg(const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Negate a vector.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvmult(const cpVect v, const cpFloat s)
|
|
</code></pre>
|
|
|
|
<p class="expl">Scalar multiplication.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpFloat cpvdot(const cpVect v1, const cpVect v2)
|
|
</code></pre>
|
|
|
|
<p class="expl">Vector dot product.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpFloat cpvcross(const cpVect v1, const cpVect v2)
|
|
</code></pre>
|
|
|
|
<p class="expl">2D vector cross product analog. The cross product of 2D vectors exists only in the z component, so only that value is returned.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvperp(const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns the perpendicular vector. (90 degree rotation)</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvproject(const cpVect v1, const cpVect v2)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns the vector projection of <code>v1</code> onto <code>v2</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvrotate(const cpVect v1, const cpVect v2)
|
|
</code></pre>
|
|
|
|
<p class="expl">Uses complex multiplication to rotate (and scale) <code>v1</code> by <code>v2</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvunrotate(const cpVect v1, const cpVect v2)
|
|
</code></pre>
|
|
|
|
<p class="expl">Inverse of <code>cpvrotate()</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpFloat cpvlength(const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns the length of <code>v</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpFloat cpvlengthsq(const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns the squared length of <code>v</code>. Faster than <code>cpvlength()</code> when you only need to compare lengths.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvnormalize(const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns a normalized copy of <code>v</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpvforangle(const cpFloat a)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns the unit length vector for the given angle (in radians).</p>
|
|
|
|
|
|
<pre><code>
|
|
cpFloat cpvtoangle(const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns the angular direction <code>v</code> is pointing in (in radians).</p>
|
|
|
|
|
|
<pre><code>
|
|
*cpvstr(const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns a string representation of <code>v</code>. <strong><span class="caps">NOTE</span>:</strong> <em>The string points to a static local and is reset every time the function is called.</em></p>
|
|
|
|
|
|
<h3>Chipmunk bounding boxes: <code>cpBB</code></h3>
|
|
|
|
|
|
<p><strong>User accessible fields:</strong></p>
|
|
|
|
|
|
<pre><code>
|
|
typedef struct cpBB{
|
|
cpFloat l, b, r ,t;
|
|
} cpBB
|
|
</code></pre>
|
|
|
|
<p class="expl">Simple bounding box struct. Stored as left, bottom, right, top values.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpBB cpBBNew(const cpFloat l, const cpFloat b, const cpFloat r, const cpFloat t)
|
|
</code></pre>
|
|
|
|
<p class="expl">Convenience constructor for <code>cpBB</code> structs.</p>
|
|
|
|
|
|
<pre><code>
|
|
int cpBBintersects(const cpBB a, const cpBB b)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns true if the bounding boxes intersect.</p>
|
|
|
|
|
|
<pre><code>
|
|
int cpBBcontainsBB(const cpBB bb, const cpBB other)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns true if <code>bb</code> completely contains <code>other</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
int cpBBcontainsVect(const cpBB bb, const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns true if <code>bb</code> contains <code>v</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpBBClampVect(const cpBB bb, const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns a copy of <code>v</code> clamped to the bounding box.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpBBWrapVect(const cpBB bb, const cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Returns a copy of <code>v</code> wrapped to the bounding box.</p>
|
|
|
|
|
|
<h3>Chipmunk spatial hashes: <code>cpSpaceHash</code></h3>
|
|
|
|
|
|
<p><em>The spatial hash isn’t yet ready for user use. However, it has been made in a generic manner that would allow it to be used for more than just Chipmunk’s collision detection needs.</em></p>
|
|
|
|
|
|
<h3>Chipmunk rigid bodies: <code>cpBody</code></h3>
|
|
|
|
|
|
<p><strong>User accessible fields:</strong></p>
|
|
|
|
|
|
<pre><code>
|
|
typedef struct cpBody{
|
|
cpFloat m, m_inv;
|
|
cpFloat i, i_inv;
|
|
|
|
cpVect p, v, f;
|
|
cpFloat a, w, t;
|
|
cpVect rot;
|
|
} cpBody
|
|
</code></pre>
|
|
|
|
<ul>
|
|
<li><code>m</code>, and <code>m_inv</code> are the mass and its inverse.</li>
|
|
<li><code>i</code>, and <code>i_inv</code> are the moment of inertia and its inverse.</li>
|
|
<li><code>p</code>, <code>v</code>, and <code>f</code> are the position, velocity and force respectively.</li>
|
|
<li><code>a</code>, <code>w</code>, and <code>t</code> are the angle (in radians), angular velocity (rad/sec), and torque respectively.</li>
|
|
<li><code>rot</code> is the rotation of the body as a unit length vector. (can be used with <code>cpvrotate()</code>)</li>
|
|
</ul>
|
|
|
|
|
|
<pre><code>
|
|
cpBody *cpBodyAlloc(void)
|
|
cpBody *cpBodyInit(cpBody *body, cpFloat m, cpFloat i)
|
|
cpBody *cpBodyNew(cpFloat m, cpFloat i)
|
|
|
|
void cpBodyDestroy(cpBody *body)
|
|
void cpBodyFree(cpBody *body)
|
|
</code></pre>
|
|
|
|
<p class="expl">Uses the standard suite of Chipmunk memory functions. <code>m</code> and <code>i</code> are the mass and moment of inertia for the body.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpBodySetMass(cpBody *body, cpFloat m);
|
|
void cpBodySetMoment(cpBody *body, cpFloat i);
|
|
void cpBodySetAngle(cpBody *body, cpFloat a);
|
|
</code></pre>
|
|
|
|
<p class="expl">Because several of the values are linked, (m/m_inv, i/i_inv, a/rot) don’t set them explicitly, use these setter functions instead.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpBodyLocal2World(cpBody *body, cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Convert from body local coordinates to world space coordinates.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpBodyWorld2Local(cpBody *body, cpVect v)
|
|
</code></pre>
|
|
|
|
<p class="expl">Convert from world space coordinates to body local coordinates.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpBodyApplyImpulse(cpBody *body, cpVect j, cpVect r)
|
|
</code></pre>
|
|
|
|
<p class="expl">Apply the impulse <code>j</code> to <code>body</code> with offset <code>r</code>. Both <code>j</code> and <code>r</code> should be in world coordinates.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpBodyResetForces(cpBody *body)
|
|
</code></pre>
|
|
|
|
<p class="expl">Zero both the forces and torques accumulated on <code>body</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpBodyApplyForce(cpBody *body, cpVect f, cpVect r)
|
|
</code></pre>
|
|
|
|
<p class="expl">Apply (accumulate) the force <code>f</code> on <code>body</code> with offset <code>r</code>. Both <code>f</code> and <code>r</code> should be in world coordinates.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpBodyUpdateVelocity(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt)
|
|
</code></pre>
|
|
|
|
<p class="expl">Updates the velocity of the body using Euler integration. You don’t need to call this unless you are managing the object manually instead of adding it to a <code>cpSpace</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpBodyUpdatePosition(cpBody *body, cpFloat dt)
|
|
</code></pre>
|
|
|
|
<p class="expl">Updates the position of the body using Euler integration. Like <code>cpBodyUpdateVelocity()</code> you shouldn’t normally need to call this yourself.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpDampedSpring(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat rlen, cpFloat k, cpFloat dmp, cpFloat dt)
|
|
</code></pre>
|
|
|
|
<p class="expl">Apply a spring force between bodies <code>a</code> and <code>b</code> at anchors <code>anchr1</code> and <code>anchr2</code> respectively. <code>k</code> is the spring constant (force/distance), <code>rlen</code> is the rest length of the spring, <code>dmp</code> is the damping constant (force/velocity), and <code>dt</code> is the time step to apply the force over. <strong>Note:</strong> <em>not solving the damping forces in the impulse solver causes problems with large damping values. This function will eventually be replaced by a new constraint (joint) type.</em></p>
|
|
|
|
|
|
<p><strong>Notes:</strong></p>
|
|
|
|
|
|
<ul>
|
|
<li>Use forces to modify the rigid bodies if possible. This is likely to be the most stable.</li>
|
|
<li>Modifying a body’s velocity shouldn’t necessarily be avoided, but applying large changes can cause strange results in the simulation. Experiment freely, but be warned.</li>
|
|
<li><strong>Don’t</strong> modify a body’s position every step unless you really know what you are doing. Otherwise you’re likely to get the position/velocity badly out of sync.</li>
|
|
</ul>
|
|
|
|
|
|
<h3>Chipmunk collision shapes: <code>cpShape</code></h3>
|
|
|
|
|
|
<p>There are currently 3 possible collision shapes:</p>
|
|
|
|
|
|
<ul>
|
|
<li><strong>Circles</strong>: Fastest collision shape. They also roll smoothly.</li>
|
|
<li><strong>Line segments</strong>: Meant mainly as a static shape. They can be attached to moving bodies, but they don’t generate collisions with other line segments.</li>
|
|
<li><strong>Convex polygons</strong>: Slowest, but most flexible collision shape.</li>
|
|
</ul>
|
|
|
|
|
|
<p><strong>User accessible fields:</strong></p>
|
|
|
|
|
|
<pre><code>
|
|
typedef struct cpShape{
|
|
cpBB bb;
|
|
|
|
unsigned long collision_type;
|
|
unsigned long group;
|
|
unsigned long layers;
|
|
|
|
void *data;
|
|
|
|
cpBody *body;
|
|
cpFloat e, u;
|
|
cpVect surface_v;
|
|
} cpShape;
|
|
</code></pre>
|
|
|
|
<ul>
|
|
<li><code>bb</code>: The bounding box of the shape. Only guaranteed to be valid after <code>cpShapeCacheBB()</code> is called.</li>
|
|
<li><code>collision_type</code>: A user definable field, see the collision pair function section below for more information.</li>
|
|
<li><code>group</code>: Shapes in the same non-zero group do not generate collisions. Useful when creating an object out of many shapes that you don’t want to self collide. Defaults to <code>0</code>;</li>
|
|
<li><code>layers</code>: Shapes only collide if they are in the same bit-planes. i.e. <code>(a->layers & b->layers) != 0</code> By default, a shape occupies all 32 bit-planes.</li>
|
|
<li><code>data</code>: A user definable field.</li>
|
|
<li><code>body</code>: The rigid body the shape is attached to.</li>
|
|
<li><code>e</code>: Elasticity of the shape. A value of 0.0 gives no bounce, while a value of 1.0 will give a “perfect” bounce. However due to inaccuracies in the simulation using 1.0 or greater is not recommended however. <em>See the notes at the end of the section.</em></li>
|
|
<li><code>u</code>: Friction coefficient. Chipmunk uses the Coulomb friction model, a value of 0.0 is frictionless. <a href="http://www.roymech.co.uk/Useful_Tables/Tribology/co_of_frict.htm">Tables of friction coefficients</a>. <em>See the notes at the end of the section.</em></li>
|
|
<li><code>surface_v</code>: The surface velocity of the object. Useful for creating conveyor belts or players that move around. This value is only used when calculating friction, not the collision.</li>
|
|
</ul>
|
|
|
|
|
|
<pre><code>
|
|
void cpShapeDestroy(cpShape *shape)
|
|
void cpShapeFree(cpShape *shape)
|
|
</code></pre>
|
|
|
|
<p class="expl"><code>Destroy</code> and <code>Free</code> functions are shared by all shape types.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpBB cpShapeCacheBB(cpShape *shape)
|
|
</code></pre>
|
|
|
|
<p class="expl">Updates and returns the bounding box of <code>shape</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpResetShapeIdCounter(void)
|
|
</code></pre>
|
|
|
|
<p class="expl">Chipmunk keeps a counter so that every new shape is given a unique hash value to be used in the spatial hash. Because this affects the order in which the collisions are found and handled, you should reset the shape counter every time you populate a space with new shapes. If you don’t, there might be (very) slight differences in the simulation.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpCircleShape *cpCircleShapeAlloc(void)
|
|
cpCircleShape *cpCircleShapeInit(cpCircleShape *circle, cpBody *body, cpVect offset, cpFloat radius)
|
|
cpShape *cpCircleShapeNew(cpBody *body, cpVect offset, cpFloat radius)
|
|
</code></pre>
|
|
|
|
<p class="expl"><code>body</code> is the body to attach the circle to, <code>offset</code> is the offset from the body’s center of gravity in body local coordinates.</p>
|
|
|
|
|
|
<pre><code>
|
|
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)
|
|
</code></pre>
|
|
|
|
<p class="expl"><code>body</code> is the body to attach the segment to, <code>a</code> and <code>b</code> are the endpoints, and <code>radius</code> is the thickness of the segment.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpPolyShape *cpPolyShapeAlloc(void)
|
|
cpPolyShape *cpPolyShapeInit(cpPolyShape *poly, cpBody *body, int numVerts, cpVect *verts, cpVect offset)
|
|
cpShape *cpPolyShapeNew(cpBody *body, int numVerts, cpVect *verts, cpVect offset)
|
|
</code></pre>
|
|
|
|
<p class="expl"><code>body</code> is the body to attach the poly to, <code>verts</code> is an array of <code>cpVect</code>’s defining a convex hull with a counterclockwise winding, <code>offset</code> is the offset from the body’s center of gravity in body local coordinates.</p>
|
|
|
|
|
|
<strong>Notes:</strong>
|
|
<ul>
|
|
<li>You can attach multiple collision shapes to a rigid body. This should allow you to create almost any shape you could possibly need.</li>
|
|
<li>Shapes attached to the same rigid body will never generate collisions. You don’t have to worry about overlap when attaching multiple shapes to a rigid body.</li>
|
|
<li>The amount of elasticity applied during a collision is determined by multiplying the elasticity of both shapes together. The same is done for determining the friction.</li>
|
|
<li>Make sure you add both the body and it’s collision shapes to a space. The exception is when you want to have a static body or a body that you integrate yourself. In that case, only add the shape.</li>
|
|
</ul>
|
|
|
|
|
|
<h3>Chipmunk joints: <code>cpJoint</code></h3>
|
|
|
|
|
|
<p>There are currently 4 kinds of joints:</p>
|
|
|
|
|
|
<ul>
|
|
<li><strong>Pin Joints</strong> connect two rigid bodies with a solid pin or rod. It keeps the anchor points at a set distance from one another.</li>
|
|
<li><strong>Slide Joints</strong> are like pin joints, but have a minimum and maximum distance. A chain could be modeled using this joint. It keeps the anchor points from getting to far apart, but will allow them to get closer together.</li>
|
|
<li><strong>Pivot Joints</strong> simply allow two objects to pivot about a single point.</li>
|
|
<li><strong>Groove Joints</strong> attach a point on one body to a groove on the other. Think of it as a sliding pivot joint.</li>
|
|
</ul>
|
|
|
|
|
|
<pre><code>
|
|
void cpJointDestroy(cpJoint *joint)
|
|
void cpJointFree(cpJoint *joint)
|
|
</code></pre>
|
|
|
|
<p class="expl"><code>Destroy</code> and <code>Free</code> functions are shared by all joint types.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpPinJoint *cpPinJointAlloc(void)
|
|
cpPinJoint *cpPinJointInit(cpPinJoint *joint, cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2)
|
|
cpJoint *cpPinJointNew(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2)
|
|
</code></pre>
|
|
|
|
<p class="expl"><code>a</code> and <code>b</code> are the two bodies to connect, and <code>anchr1</code> and <code>anchr2</code> are the anchor points on those bodies.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpSlideJoint *cpSlideJointAlloc(void)
|
|
cpSlideJoint *cpSlideJointInit(cpSlideJoint *joint, cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat min, cpFloat max)
|
|
cpJoint *cpSlideJointNew(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat min, cpFloat max)
|
|
</code></pre>
|
|
|
|
<p class="expl"><code>a</code> and <code>b</code> are the two bodies to connect, <code>anchr1</code> and <code>anchr2</code> are the anchor points on those bodies, and <code>min</code> and <code>max</code> define the allowed distances of the anchor points.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpPivotJoint *cpPivotJointAlloc(void)
|
|
cpPivotJoint *cpPivotJointInit(cpPivotJoint *joint, cpBody *a, cpBody *b, cpVect pivot)
|
|
cpJoint *cpPivotJointNew(cpBody *a, cpBody *b, cpVect pivot)
|
|
</code></pre>
|
|
|
|
<p class="expl"><code>a</code> and <code>b</code> are the two bodies to connect, and <code>pivot</code> is the point in world coordinates of the pivot. Because the pivot location is given in world coordinates, you must have the bodies moved into the correct positions already.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpGrooveJoint *cpGrooveJointAlloc(void)
|
|
cpGrooveJoint *cpGrooveJointInit(cpGrooveJoint *joint, cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchr2)
|
|
cpJoint *cpGrooveJointNew(cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchr2)
|
|
</code></pre>
|
|
|
|
<p class="expl">The groove goes from <em>groov_a</em> to <em>groove_b</em> on body <em>a_, and the pivot is attached to _anchr2</em> on body <em>b</em>. All coordinates are body local.</p>
|
|
|
|
|
|
<p><strong>Notes:</strong></p>
|
|
|
|
|
|
<ul>
|
|
<li>You can add multiple joints between two bodies, but make sure that they don’t fight. It can cause the bodies to explode.</li>
|
|
<li>Make sure you add both of the connected bodies and the joint to a space.</li>
|
|
</ul>
|
|
|
|
|
|
<h3>Chipmunk spaces: <code>cpSpace</code></h3>
|
|
|
|
|
|
<p><strong>User accessible fields:</strong></p>
|
|
|
|
|
|
<pre><code>
|
|
typedef struct cpSpace{
|
|
int iterations;
|
|
|
|
cpVect gravity;
|
|
cpFloat damping;
|
|
|
|
int stamp;
|
|
} cpSpace;
|
|
</code></pre>
|
|
|
|
<ul>
|
|
<li><code>iterations</code>: The number of iterations to use when solving constraints (collisions and joints). Defaults to 10.</li>
|
|
<li><code>gravity</code>: The amount of gravity applied to the system.</li>
|
|
<li><code>damping</code>: The amount of viscous damping applied to the system.</li>
|
|
<li><code>stamp</code>: The tick stamp. Incremented every time <code>cpSpaceStep()</code> is called. <em>read only</em></li>
|
|
</ul>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceFreeChildren(cpSpace *space)
|
|
</code></pre>
|
|
|
|
<p class="expl">Frees all bodies, shapes and joints added to the system.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpSpace* cpSpaceAlloc(void)
|
|
cpSpace* cpSpaceInit(cpSpace *space, int iterations)
|
|
cpSpace* cpSpaceNew(int iterations)
|
|
|
|
void cpSpaceDestroy(cpSpace *space)
|
|
void cpSpaceFree(cpSpace *space)
|
|
</code></pre>
|
|
|
|
<p class="expl">More standard Chipmunk memory functions.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceFreeChildren(cpSpace *space)
|
|
</code></pre>
|
|
|
|
<p class="expl">This function will free all of the shapes, bodies and joints that have been added to <code>space</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceAddShape(cpSpace *space, cpShape *shape)
|
|
void cpSpaceAddStaticShape(cpSpace *space, cpShape *shape)
|
|
void cpSpaceAddBody(cpSpace *space, cpBody *body)
|
|
void cpSpaceAddJoint(cpSpace *space, cpJoint *joint)
|
|
|
|
void cpSpaceRemoveShape(cpSpace *space, cpShape *shape)
|
|
void cpSpaceRemoveStaticShape(cpSpace *space, cpShape *shape)
|
|
void cpSpaceRemoveBody(cpSpace *space, cpBody *body)
|
|
void cpSpaceRemoveJoint(cpSpace *space, cpJoint *joint)
|
|
</code></pre>
|
|
|
|
<p class="expl">These functions add and remove shapes, bodies and joints from <code>space</code>. Shapes added as static are assumed not to move. Static shapes should be be attached to a rigid body with an infinite mass and moment of inertia. Also, don’t add the rigid body used to the space, as that will cause it to fall under the effects of gravity.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceResizeStaticHash(cpSpace *space, cpFloat dim, int count)
|
|
void cpSpaceResizeActiveHash(cpSpace *space, cpFloat dim, int count)
|
|
</code></pre>
|
|
|
|
<p class="expl">The spatial hashes used by Chipmunk’s collision detection are fairly size sensitive. <code>dim</code> is the size of the hash cells. Setting <code>dim</code> to the average objects size is likely to give the best performance.</p>
|
|
|
|
|
|
<p class="expl"><code>count</code> is the suggested minimum number of cells in the hash table. Bigger is better, but only to a point. Setting <code>count</code> to ~10x the number of objects in the hash is probably a good starting point.</p>
|
|
|
|
|
|
<p class="expl">By default, <code>dim</code> is 100.0, and <code>count</code> is 1000.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceRehashStatic(cpSpace *space)
|
|
</code></pre>
|
|
|
|
<p class="expl">Rehashes the shapes in the static spatial hash. You only need to call this if you move one of the static shapes.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceStep(cpSpace *space, cpFloat dt)
|
|
</code></pre>
|
|
|
|
<p class="expl">Update the space for the given time step. Using a fixed time step is <em>highly</em> recommended. Doing so will increase the efficiency of the contact persistence, requiring an order of magnitude fewer iterations to resolve the collisions in the usual case.</p>
|
|
|
|
|
|
<p><strong>Notes:</strong></p>
|
|
|
|
|
|
<ul>
|
|
<li>When removing objects from the space, make sure you remove any other objects that reference it. For instance, when you remove a body, remove the joints and shapes attached to it.</li>
|
|
<li>The number of iterations, and the size of the time step determine the quality of the simulation. More iterations, or smaller time steps increase the quality.</li>
|
|
<li>Because static shapes are only rehashed when you request it, it’s possible to use a much higher <code>count</code> argument to <code>cpHashResizeStaticHash()</code> than to <code>cpHashResizeStaticHash()</code>. Doing so will use more memory though.</li>
|
|
</ul>
|
|
|
|
|
|
<h3>Miscellaneous.</h3>
|
|
|
|
|
|
<pre><code>
|
|
cpFloat cpMomentForCircle(cpFloat m, cpFloat r1, cpFloat r2, cpVect offset)
|
|
</code></pre>
|
|
|
|
<p class="expl">Calculate the moment of inertia for a circle. Arguments are similar to <code>cpCircleShapeInit()</code>. <em>m_ is the mass, _r1</em> and <em>r2</em> define an inner and outer radius, and <em>offset</em> is the offset of the center from the center of gravity of the rigid body.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpFloat cpMomentForPoly(cpFloat m, int numVerts, cpVect *verts, cpVect offset)
|
|
</code></pre>
|
|
|
|
<p class="expl">Calculate the moment of inertia for a poly. Arguments are similar to <code>cpPolyShapeInit()</code> <em>m_ is the mass, _numVerts</em> is the number of vertexes in <em>verts</em>, and <em>offset</em> is the offset of the poly coordinates from the center of gravity of the rigid body.</p>
|
|
|
|
|
|
<h3>Collision pair functions</h3>
|
|
|
|
|
|
<p>Collision pair functions allow you to add callbacks for certain collision events. Each <code>cpShape</code> structure has a user definable <code>collision_type</code> field that is used to identify its type. For instance, you could define an enumeration of collision types such as bullets and players, and then register a collision pair function to reduce the players health when a collision is found between the two.</p>
|
|
|
|
|
|
<p>Additionally, the return value of a collision pair function determines whether or not a collision will be processed. If the function returns false, the collision will be ignored. One use for this functionality is to allow a rock object to break a vase object. If the approximated energy of the collision is above a certain level, flag the vase to be removed from the space, apply an impulse to the rock to slow it down, and return false. After the <code>cpSpaceStep()</code> returns, remove the vase from the space.</p>
|
|
|
|
|
|
<p><strong><span class="caps">WARNING</span>:</strong> <em>It is not safe for collision pair functions to remove or free shapes or bodies from a space. Doing so will likely end in a segfault as an earlier collision may already be referencing the shape or body. You must wait until after the <code>cpSpaceStep()</code> function returns.</em></p>
|
|
|
|
|
|
<pre><code>
|
|
typedef struct cpContact{
|
|
cpVect p, n;
|
|
cpFloat dist;
|
|
|
|
cpFloat jnAcc, jtAcc;
|
|
} cpContact;
|
|
</code></pre>
|
|
|
|
<p class="expl">An array of <code>cpContact</code> structs are passed to collision pair functions. Some user accessible fields include:</p>
|
|
|
|
|
|
<ul>
|
|
<li><code>p</code>: position of the collision.</li>
|
|
<li><code>n</code>: normal of the collision.</li>
|
|
<li><code>dist</code>: penetration distance of the collision.</li>
|
|
<li><code>jnAcc</code> and <code>jtAcc</code>: The normal and tangential components of the accumulated (final) impulse applied to resolve the collision. Values will not be valid until after the call to <code>cpSpaceStep()</code> returns.</li>
|
|
</ul>
|
|
|
|
|
|
<pre><code>
|
|
typedef int (*cpCollFunc)(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data)
|
|
</code></pre>
|
|
|
|
<p class="expl">Prototype for a collision callback function. The two colliding shapes are passed as <code>a</code> and <code>b</code>, along with the contact points, and a user definable pointer are passed as arguments. The shapes are passed in the same order as their types were registered using <code>cpSpaceAddCollisionPairFunc()</code>. Because Chipmunk may swap the shapes to accommodate your collision pair function the normals may be backwards. Always multiply the normals by <em>normal_coef</em> before using the values. The <code>contacts</code> array may be freed on the next call to <code>cpSpaceStep()</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceAddCollisionPairFunc(cpSpace *space, unsigned long a, unsigned long b, cpCollFunc func, void *data)
|
|
</code></pre>
|
|
|
|
<p class="expl">Register <code>func</code> to be called when a collision is found between a shapes with <code>collision_type</code> fields that match <code>a</code> and <code>b</code>. <code>data</code> is passed to <code>func</code> as a parameter. The ordering of the collision types will match the ordering passed to the callback function.</p>
|
|
|
|
|
|
<p class="expl">Passing <code>NULL</code> for <code>func</code> will reject any collision with the given collision type pair.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceRemoveCollisionPairFunc(cpSpace *space, unsigned long a, unsigned long b)
|
|
</code></pre>
|
|
|
|
<p class="expl">Remove the function for the given collision type pair. The order of <code>a</code> and <code>b</code> must match the original order used with <code>cpSpaceAddCollisionPairFunc()</code>.</p>
|
|
|
|
|
|
<pre><code>
|
|
void cpSpaceSetDefaultCollisionPairFunc(cpSpace *space, cpCollFunc func, void *data)
|
|
</code></pre>
|
|
|
|
<p class="expl">The default function is called when no collision pair function is specified. By default, the default function simply accepts all collisions. Passing <code>NULL</code> for <code>func</code> will reset the default function back to the default. (You know what I mean.)</p>
|
|
|
|
|
|
<p class="expl">Passing <code>NULL</code> for <code>func</code> will reject collisions by default.</p>
|
|
|
|
|
|
<h2>Advanced topics</h2>
|
|
|
|
|
|
<h3>Advanced collision processing</h3>
|
|
|
|
|
|
<p>Using collision pair functions, it’s possible to get information about a collision such as the locations of the contact points and the normals, but you can’t get the impulse applied to each contact point at the time the callback is called. Because Chipmunk caches the impulses, it is possible to access them after the call to <code>cpSpaceStep()</code> returns.</p>
|
|
|
|
|
|
<p>The impulse information is stored in the <code>jnAcc</code> and <code>jtAcc</code> fields of the <code>cpContact</code> structures passed to the collision pair function. So you must store a reference to the <code>contacts</code> array in the collision pair function so that you can access it later once the impulses have been calculated.</p>
|
|
|
|
|
|
<pre><code>
|
|
cpVect cpContactsSumImpulses(cpContact *contacts, int numContacts);
|
|
cpVect cpContactsSumImpulsesWithFriction(cpContact *contacts, int numContacts);
|
|
</code></pre>
|
|
|
|
<p class="expl">Sums the impulses applied to the the given contact points. <code>cpContactsSumImpulses()</code> sums only the normal components, while <code>cpContactsSumImpulsesWithFriction()</code> sums the normal and tangential componets.</p>
|
|
|
|
|
|
<p><strong>Notes:</strong></p>
|
|
|
|
|
|
<ul>
|
|
<li>The <code>contact</code> array will either be destroyed or out of date after the next call to <code>cpSpaceStep()</code>. If you need to store the collision information permanently, you’ll have to copy it.</li>
|
|
</ul>
|
|
|
|
|
|
<h3>Collision pair functions, groups, and layers</h3>
|
|
|
|
|
|
<p>There are three ways that you can specify which shapes are allowed to collide in Chipmunk: collision pair functions, groups, and layers. What are their intended purposes?</p>
|
|
|
|
|
|
<p><strong>Collision pair functions:</strong> More than just a callback, collision pair functions can conditionally allow a collision between two shapes. This is the only choice if you need to perform some logic based on the shapes or contact information before deciding to allow the collision.</p>
|
|
|
|
|
|
<p><strong>Groups:</strong> Groups filter out collisions between objects in the same non-zero groups. A good example of when groups would be useful is to create a multi-body, multi-shape object such as a ragdoll. You don’t want the parts of the ragdoll to collide with itself, but you do want it to collide with other ragdolls.</p>
|
|
|
|
|
|
<p><strong>Layers:</strong> Layers are another way of grouping shapes. Collision between shapes that don’t occupy one or more of the same layers are filtered out. Layers are implemented using a bitmask on an unsigned long, so there are up to 32 layers available.</p>
|
|
|
|
|
|
<p>To be clear, for a collision to occur, all of the tests have to pass. Additionally, collisions between static shapes are not considered nor are collisions between shapes connected to the same rigid body.</p>
|
|
|
|
|
|
<h3>Mysteries of <code>cpSpaceStep()</code> explained.</h3>
|
|
|
|
|
|
<p>The <code>cpSpaceStep()</code> function is really the workhorse of Chipmunk. So what exactly does it do?</p>
|
|
|
|
|
|
<ul>
|
|
<li>Persistent contacts with a stamp that is out of date by more than <code>cp_contact_persistence</code> are filtered out.</li>
|
|
<li>Velocities of all rigid bodies in the space are integrated using <code>cpBodyUpdateVelocity()</code>.</li>
|
|
<li>All active shapes have their bounding boxes calculated and cached.</li>
|
|
<li>Collisions between the active shapes and static shapes are found. (collision pair functions are called)</li>
|
|
<li>Collisions between the active shapes are found. (collision pair functions are called)</li>
|
|
<li>Information about all constraints (collisions and joints) is pre-calculated.</li>
|
|
<li>Impulses are found and applied to solve all constraints (joints and collisions).</li>
|
|
<li>Positions of all rigid bodies in the space are integrated using <code>cpBodyUpdatePosition()</code>.</li>
|
|
</ul>
|
|
|
|
|
|
<p>Chipmunk does a lot of processing on the shapes in the system in order to provide robust and fast collision detection, but the same is not true of the rigid bodies. Really all <code>cpSpaceStep()</code> does to the bodies in the system is to integrate them and apply impulses to them.</p>
|
|
|
|
|
|
<p>The integration step is really just for convenience. If you integrate the velocity of your rigid bodies before calling <code>cpSpaceStep()</code> and integrate their positions afterward, you don’t have to add them to the space at all. In fact, there are good reasons for not doing so. All bodies in the space are integrated using the same gravity and damping, and this prevents you from providing interesting objects such as moving platforms.</p>
|
|
|
|
|
|
<p>If you are going to do the integration for your rigid bodies manually, I would highly recommend that you use <code>cpBodyUpdatePosition()</code> to integrate the position of the objects and only integrate the velocity. <code>cpBodyUpdatePosition()</code> is a required step for penetration resolution, and weird things could happen if it’s not called. Chipmunk uses Euler integration, so calculating the velocity required to move a body to a specific position is trivial if that is what you wish to do.</p>
|
|
|
|
|
|
<h3>Chipmunk globals.</h3>
|
|
|
|
|
|
<p>Chipmunk was designed with multithreading in mind, so very few globals are used. This shouldn’t be a problem in most cases as it’s likely you’ll only need to set them on a per application basis (if at all).</p>
|
|
|
|
|
|
<p><code>int cp_contact_persistence</code>: This determines how long contacts should persist. This number should be fairly small as the cached contacts will only be close for a short time. <code>cp_contact_persistence</code> defaults to 3 as it is large enough to help prevent oscillating contacts, but doesn’t allow stale contact information to be used.</p>
|
|
|
|
|
|
<p><code>cpFloat cp_collision_slop</code>: The amount that shapes are allowed to penetrate. Setting this to zero will work just fine, but using a small positive amount will help prevent oscillating contacts. <code>cp_collision_slop</code> defaults to 0.1.</p>
|
|
|
|
|
|
<p><code>cpFloat cp_bias_coef</code>: The amount of penetration to reduce in each step. Values should range from 0 to 1. Using large values will eliminate penetration in fewer steps, but can cause vibration. <code>cp_bias_coef</code> defaults to 0.1.</p>
|
|
|
|
|
|
<p><code>cpFloat cp_joint_bias_coef</code>: Similar to <code>cp_bias_coef</code>, but for joints. Defaults to 0.1. <em>In the future, joints might have their own bias coefficient instead.</em></p>
|
|
|
|
|
|
<h2>Known Problems</h2>
|
|
|
|
|
|
<ul>
|
|
<li>Fast moving objects sometimes pass right through one another. This is because I’m not doing swept volume collisions. Use smaller time steps if this is a problem.</li>
|
|
<li>Pointy or thin polygons aren’t handled well. Collision points are generated at the poly’s vertexes. I haven’t decided what to do about this yet.</li>
|
|
<li>Elastic shapes don’t stack well.</li>
|
|
</ul>
|
|
|
|
|
|
<h2>Performance/Quality tuning</h2>
|
|
|
|
|
|
<p>There are a number of things you can do to increase the performance of Chipmunk:</p>
|
|
|
|
|
|
<ul>
|
|
<li>Use simpler collision shapes when possible. It’s easier on the collision detection, you’ll generate fewer contact points, and require fewer iterations.</li>
|
|
<li>Make sure you have your spatial hashes tuned properly. It may take some experimenting to find the optimal dim/count parameters.</li>
|
|
<li>Use fewer iterations.</li>
|
|
<li>Use a larger time step.</li>
|
|
<li>Use a fixed time step. This allows contact persistence to work properly, reducing the number of iterations you need by an order of magnitude.</li>
|
|
</ul>
|
|
|
|
|
|
<p>If you have problems with jittery or vibrating objects, you need to do mostly the opposite:</p>
|
|
|
|
|
|
<ul>
|
|
<li>Use simpler shapes. Simpler shapes generate fewer contacts, increasing the efficiency of each iteration.</li>
|
|
<li>Use more iterations.</li>
|
|
<li>Use a smaller time step.</li>
|
|
<li>Use a fixed time step. By allowing the contact persistence to work properly, you can use more iterations without increasing the computational cost.</li>
|
|
</ul>
|
|
|
|
|
|
<h2>To do</h2>
|
|
|
|
|
|
There are a number of things left on my todo list:
|
|
<ul>
|
|
<li>More joint types.</li>
|
|
<li>Post collision queries. Being able to query for all the impulses that were applied to an object would be useful.</li>
|
|
<li>Python binding. (possibly others if I can get help)</li>
|
|
</ul>
|
|
|
|
|
|
<h2>Contact information</h2>
|
|
|
|
|
|
<p>Drop me a line. Tell me how great you think Chipmunk is or how terribly buggy it is. I’d love to hear about any project that people are using Chipmunk for.</p>
|
|
|
|
|
|
<p><strong><span class="caps">AIM</span>:</strong> slembcke@mac.com</p>
|
|
|
|
|
|
<p><strong>Email:</strong> lemb0029(at)morris(dot)umn(dot)edu or slembcke(at)gmail(dot)com</p>
|
|
|
|
|
|
<p>I can also be found lurking about the <a href="http://www.idevgames.com">iDevGames</a> forums.</p>
|
|
|
|
|
|
<h2>License</h2>
|
|
|
|
|
|
<p>Copyright© 2007 Scott Lembcke</p>
|
|
|
|
|
|
<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>
|
|
|
|
|
|
<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>
|
|
|
|
|
|
<span class="caps">THE SOFTWARE IS PROVIDED</span> “AS IS”, <span class="caps">WITHOUT WARRANTY OF ANY KIND</span>, EXPRESS <span class="caps">OR IMPLIED</span>, INCLUDING <span class="caps">BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY</span>, FITNESS <span class="caps">FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT</span>. IN <span class="caps">NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM</span>, DAMAGES <span class="caps">OR OTHER LIABILITY</span>, WHETHER <span class="caps">IN AN ACTION OF CONTRACT</span>, TORT <span class="caps">OR OTHERWISE</span>, ARISING <span class="caps">FROM</span>, OUT <span class="caps">OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE</span>.
|
|
|
|
</body>
|
|
</html>
|