Hello :)
In this thread I'll be presenting my 3D simulator engine object, which I've been writing as a donation to the ObjAsm32 rapid program development package. I'll be providing all my current sourcecode, as well as describing both the details of the code and how the parts all fit together.
The engine still doesn't have a NAME, I'm open to your suggestions !!
I've been describing various parts of the engine in various other threads, but I wasn't prepared to provide the meat for those bones until I was satisfied that it was reasonably stable, and it seems to be.
The sourcecode is still being developed, and changes will be made without notice. Deal with it.

Where do I start?
The engine is OBJECT ORIENTED - it uses CLASSES, and OBJECTS and such.
There's the main class, whose name is D3D_PhysicsSimulator.
The code that drives all of the Bodies being simulated is in that class.
The main simulator class uses a handful of 'utility classes'.
Some of these it uses to represent our simulated Bodies, and others provide list-management functionality so we can keep lists of Bodies and other stuff.

Bodies are represented by a class called CollisionHull.
This class describes and provides functions to simulate one single physical body of unknown geometry - its a BASE CLASS.
Directly deriving from CollisionHull is a class called Sphere.
This class provides basic support for physics and collisions involving Spherical geometry.
So if we were to create an instance of Sphere, that sphere object inherits all the stuff thats in CollisionHull, and is our most primitive physical, collidable Body.
Directly deriving from Sphere is a class called Box.
It provides functionality for Box/Box and Box/Sphere collisions.
Note that when we create a Box, it inherits the Sphere stuff, a Box has a Sphere that completely surrounds and encloses it.
When simulating Boxes, we use the Sphere to accelerate collision testing.

Here's what I have in mind, eventually..

;CollisionHull
; Sphere
;  Box
;  Cylinder
;    Cone


In my next post, we'll start looking at these classes, but not in too much detail.
We'll just look at the 'class definitions' and see what we have in store for us.
Posted on 2008-05-24 23:11:48 by Homer

Let's get this show on the road :)


SimID equ 83453
Object D3D_PhysicsSimulator, SimID, Streamable

RedefineMethod Init, Pointer, real4
RedefineMethod Done
StaticMethod Add_Body_Body_Spring, Pointer,Pointer,Pointer,Pointer
StaticMethod Add_Body_World_Spring, Pointer,Pointer,Pointer
StaticMethod Add_Box, Pointer,Pointer,Pointer,Pointer,real4
    StaticMethod ToggleWorldSprings
    StaticMethod ToggleBodySprings
StaticMethod ToggleGravity
StaticMethod ToggleDamping
    StaticMethod Update, real8
   
    StaticMethod GetPtrToConfig, Pointer, dword
    StaticMethod ComputeForces
    StaticMethod Simulate, real8   
    StaticMethod Integrate, real8
    StaticMethod CheckCollisions, real8
    StaticMethod Sweep_Body_Against_World_Planes, Pointer
    StaticMethod FindExactCollisionTime, Pointer, Pointer, real8, real8, Pointer
    StaticMethod ResolveCollision_BodyBody, Pointer,Pointer

    DefineVariable SourceConfigurationIndex, dword,0
    DefineVariable TargetConfigurationIndex, dword,1
   
    DefineVariable DampingActive, BOOL,TRUE
    DefineVariable WorldSpringsActive, BOOL,FALSE
    DefineVariable BodySpringsActive, BOOL,FALSE
    DefineVariable GravityActive, BOOL,TRUE


    Embed Bodies, Collection
    Embed BodySprings, DataCollection
    Embed WorldSprings, DataCollection
    Embed Walls, DataCollection
ObjectEnd


This is the class definition for the main Simulator class.
Lets describe whats going on for those of you who are new to OOP, or at least to OA32.
SimID : Each class has a unique class id, I've chosen one at random.
Streamable : This class is derived from (inherits from) a class called 'Streamable', which provides support for streaming data to and from files (say, to load and save the state of the simulator).
Redefined Methods : These are methods which already exist in the ancestor class (Streamable) that we wish to OVERRIDE. Redefining of existing methods is just one of several ways to override methods when using OA32.
The INIT and DONE methods are very important, their C++ counterparts are the Constructor and Destructor methods.
Static Methods : These are standard methods, very similar to PROCEDURES.
Data Variables : You can think of these as fields of a Struct - when we create an instance of this class, the memory object we receive is basically a struct described by the data variables.
However unlike a struct, we're able to optionally specify the default data in each field, so new objects can be created with a mixture of initialized and uninitialized data fields.
Embedded Objects : These are like Friend Classes in C++ , each Embed statement inserts an entire copy of the Class Definition whose name appears in the statement.

We can see that there are four embedded objects, one of them is a Collection object, and the other three are DataCollection objects.
These two classes are part of OA32's ever-growing arsenal of hand-optimized plug-in code objects.
The Collection object manages an array of pointers to arbitrary OA32 class object instances.
Collection doesn't care what kind of objects they are, only that they provide a 'Done' method.
When the Collection object is 'destroyed', it will automatically call the Done method for each Object whose pointer is contained in the managed array, so each Object can thus perform its own cleanup, if required... and if an object doesn't need to clean any resources up, we don't need to provide a Done method at all, the call will be marshalled to the ancestor class.
DataCollection objects are a simple twist on the Collection object - they manage arrays of heap-allocated memory chunks (ie structs) of arbitrary size.
When a DataCollection is destroyed, each managed memory object is automatically released.

Thats enough ranting about the internals of OA32, this is not an OA32 thread.
But we need to understand the layout of OA32's class definitions so we can understand the other classes and how they fit in.
Next time we'll look at class definitions for the RigidBody utility classes, and then it will be time to begin wading through the Methods.
Posted on 2008-05-25 08:26:01 by Homer
The next class we'll look at is called CollisionHull.
Essentially, this class describes a Rigid Body of unknown geometry.
It provides some essential functionality that will be required regardless of the actual shape of the bodies we are working with, so I use it as a 'base class'.. then I go on to describe several other classes which all inherit the functionality of CollisionHull, but describe a specific geometric shape.


CollisionHullID equ 48838
Object CollisionHull,CollisionHullID,Streamable
RedefineMethod Init, Pointer
StaticMethod TransformVertices, Pointer, Pointer
StaticMethod Transform_Vec3_WorldToBody, Pointer,Pointer
StaticMethod Transform_Vec3_BodyToWorld, Pointer,Pointer
StaticMethod Get_Transform_Mat44, dword
StaticMethod ClassifyPlanePoint, Pointer,Pointer
StaticMethod Point_Sweep_Plane, Pointer,Pointer,Pointer,Pointer
StaticMethod Resolve_Collision_PointNormal, Pointer,Pointer,dword
StaticMethod Resolve_Collision_PointBodyFace, Pointer,Pointer,dword
StaticMethod Apply_Accumulated_Response, dword
StaticMethod Get_Closing_And_Relative_Velocities, Pointer,dword,dword
StaticMethod Try_To_Sleep, Pointer,Pointer
StaticMethod Integrate, Pointer,Pointer,real8

;Identifies which geometric primitive best describes this Body..
;Note that this field is NOT filled, YOU HAVE TO FILL IT when you create a primitive
DefineVariable dType, dword, NULL

;Mass properties of the Body
;See the 'Setup_Mass' methods in the derived Geometry classes
DefineVariable Mass,real4,0.0
    DefineVariable OneOverMass,real4,0.0 ;We use 1/Mass , where 0=infinite mass
    DefineVariable InverseBodyInertiaTensor,Mat33,{<>}
    ;Coefficient of Restitution, for absorbing Collision energy
    DefineVariable CoefficientOfRestitution,real4,1.0 
    ;Number of Vertices used to describe the Body's geometry (only valid for Cubes at the moment)
DefineVariable NumberOfBoundingVertices, dword, 0
;This array contains the BODYSPACE declaration of the bounding vertices.
;This is static data, we use it as a reference
DefineVariable  aBodyBoundingVertices, PointArray, {<>}
;This array contains the WORLDSPACE declaration of the bounding vertices.
;Its content depends on the current orientation and position of the Body
;and is found by tranforming the above array.
DefineVariable  aWorldBoundingVertices, PointArray, {<>}

;Bodies which have very low energy can be put to sleep.
DefineVariable bIsAwake, BOOL, TRUE

;We'll put the rendering matrix here
DefineVariable matRender, D3DXMATRIX, {<>}


;While checking a given body for collisions, we use this counter
;to remember the number of simultaneous collisions for this Body
DefineVariable dNumSimultaneousCollisions,dword,0
;The collision impulses are converted into delta 'change in velocity' (linear and angular)
;and they are accumulated in these variables, pending 'final resolution',
;where we take the Average of the deltas due to each simultaneous impulse.
;This could be improved to Weight the result by comparing the Impulse magnitudes
;or some other such scheme, but an Average will yield a 'mostly right' behaviour
;which is 'good enough for now', and certainly a lot faster.
DefineVariable vAccumulated_Delta_Velocity,Vec3,{<>}
DefineVariable vAccumulated_Delta_AngMomentum,Vec3,{<>}

;Bodies have two Physics States (Configurations)
;We switch the Source and Target for each Physics TimeStep
    DefineVariable aConfigurations,configuration,{<>}
    DefineVariable  _theres_two_,configuration,{<>}
ObjectEnd


I have nothing to say about this class right now, I'll edit this post later.
Note that pretty much all of the guts of the PHYSICS stuff is handled in this class.
The geometry-specific subclasses we'll look at next don't contain any PHYSICS stuff.
Ignoring constructor/destructors, they only contain COLLISION DETECTION stuff.

Posted on 2008-05-26 00:35:22 by Homer
I want to lay these classes down and move onto the actual code in the Methods as soon as possible, so I'm going to forge on and throw the next class at you, and hope that I can engage your interest long enough to bear with me, we're nearly there :)


;------------------------------------------------------------
;Spheres are derived from CollisionHull.
;The Sphere is the most primitive geometric shape,
;and is the fastest and cheapest for intersection tests.
SphereID equ 343455
Object Sphere, SphereID, CollisionHull
RedefineMethod Init, Pointer, Pointer, Pointer, real4
StaticMethod Sphere_versus_Sphere,Pointer,dword
StaticMethod Sphere_versus_Plane, Pointer,dword
StaticMethod Sphere_Sweep_Plane,Pointer,Pointer,Pointer,Pointer
StaticMethod Sphere_Sweep_Sphere,Pointer,dword,dword
StaticMethod Setup_Mass,real4
DefineVariable fRadius, real4, 0.0 ;Sphere's radius
ObjectEnd


This class describes a geometric Sphere, as you can see it inherits from CollisionHull.
Like I said, the collision-geometry classes only contain collision-detect methods.
Theres just one more class to go, then we can get our hands dirty.
Posted on 2008-05-26 00:48:13 by Homer
Here's the last class we'll be looking at in this thread.
Its a 3D box :)


BoxID equ 74435
Object Box, BoxID, Sphere
RedefineMethod Init, Pointer, Pointer, Pointer, Pointer, Pointer
RedefineMethod Done
RedefineMethod Setup_Mass, real4
StaticMethod IsPointInBox, Pointer,Pointer
StaticMethod Box_Find_Closest_Plane,Pointer,Pointer,Pointer
StaticMethod Box_versus_Plane, Pointer,Pointer
StaticMethod Box_versus_Sphere, Pointer,dword
StaticMethod Box_versus_Box, Pointer,dword
DefineVariable vRadius, Vec3, {<>} ;xyz axial half-lengths of unrotated box
DefineVariable vJointOffsetVector, Vec3, {<>}
DefineVariable pPlanes, Pointer, NULL ;array of 6 Planes
ObjectEnd


It is HIGHLY worth mentioning that the Box class inherits from the Sphere class !!!
EVERY BOX IS SURROUNDED BY A THEORETICAL SPHERE.

Oh whats this - JointOffsetVector?
We won't be using that thing any time soon, its for "articulated physics", where we start joining things together with hinges and other joints, to make machines and ragdolls and such.

Are we there yet? Are we there yet?
Why yes kids, that's the last of these odd-looking things I'll post, from now on we get to SEE SOME CODE and talk about code and get back onto coding topics and generally code ourselves into a corner. Yes, I will be picking up the pace now :)

Posted on 2008-05-26 01:06:17 by Homer
Lets start by looking at the constructor and destructor methods (Init and Done), because that will help people who don't really understand this whole oop thing yet.

Before we can run our simulation, we'll obviously need to create an instance of the simulator class... how do we do that?


.data
PtrToMySimulator dd NULL
.code
mov PtrToMySimulator, $New (D3D_PhysicsSimulator)
OCall PtrToMySimulator::D3D_PhysicsSimulator.Init, NULL, 100.0


OA32 lets you pass real4 (floating point) values directly, 100.0 is the half-size of my World, there will be bounding-planes placed at plus and minus 100 on the x,y and z axes.
When we create a new class object (instance) in OA32, we need to manually call the Init method, but generally, the Done method will be called automatically when the object is Destroyed.

So, we've created a new Simulator object, and stored its pointer.
Then, we've called the Init method APON THAT INSTANCE of the class.
That's where OOP methods differ from common procedures.
Methods are passed a pointer to the INSTANCE of the class apon which they should operate.
This pointer is passed silently, as an invisible parameter, which in a debugger, appears as the first param of the proc (last param pushed).
You may as well imagine that there is an invisible 'pThis' param which is the first param of any Method, because that is EXACTLY what is happening.. OA32 and C++ hide it from view, but both allow you to access it within a Method.
Let's take a look what's inside the D3D_PhysicsSimulator.Init method :)


;Sets up six collision planes at the extremes of the world
;fWorldSize is the axial half-length of the World Cube,
;ie the Distance from the World Origin to the six Planes.
Method D3D_PhysicsSimulator.Init,uses esi edi,pOwner:Pointer,fWorldSize:real4
SetObject esi

Acall esi.Init, pOwner

;Initialize Embeddeds
    OCall .Bodies::Collection.Init,esi,16,256,-1
    OCall .BodySprings::DataCollection.Init,esi,16,256,-1
    OCall .WorldSprings::DataCollection.Init,esi,16,256,-1
    OCall .Walls::DataCollection.Init,esi,16,256,-1
   
;Create 6 'Walls' on the borders of the World
;Theyre just the planes of an inward-facing cube

;Front and Back Planes
mov edi,$MemAlloc(sizeof D3DXPLANE)
mov .D3DXPLANE.x,0
mov .D3DXPLANE.y,0
fld1
fchs
fstp .D3DXPLANE.z
fld fWorldSize
fstp .D3DXPLANE.w
OCall .Walls::DataCollection.Insert,edi

mov edi,$MemAlloc(sizeof D3DXPLANE)
mov .D3DXPLANE.x,0
mov .D3DXPLANE.y,0
fld1
fstp .D3DXPLANE.z
fld fWorldSize
fstp .D3DXPLANE.w
OCall .Walls::DataCollection.Insert,edi

;Top and Bottom Planes
mov edi,$MemAlloc(sizeof D3DXPLANE)
mov .D3DXPLANE.x,0
mov .D3DXPLANE.z,0
fld1
fchs
fstp .D3DXPLANE.y
fld fWorldSize
fstp .D3DXPLANE.w
OCall .Walls::DataCollection.Insert,edi

mov edi,$MemAlloc(sizeof D3DXPLANE)
mov .D3DXPLANE.x,0
mov .D3DXPLANE.z,0
fld1
fstp .D3DXPLANE.y
fld fWorldSize
fstp .D3DXPLANE.w
OCall .Walls::DataCollection.Insert,edi


;Left and Right Planes
mov edi,$MemAlloc(sizeof D3DXPLANE)
mov .D3DXPLANE.y,0
mov .D3DXPLANE.z,0
fld1
fchs
fstp .D3DXPLANE.x
fld fWorldSize
fstp .D3DXPLANE.w
OCall .Walls::DataCollection.Insert,edi

mov edi,$MemAlloc(sizeof D3DXPLANE)
mov .D3DXPLANE.y,0
mov .D3DXPLANE.z,0
fld1
fstp .D3DXPLANE.x
fld fWorldSize
fstp .D3DXPLANE.w
OCall .Walls::DataCollection.Insert,edi
MethodEnd


The first line says "SetObject esi"
This sets esi to be a pointer to "This Object Instance", and also sets up a masm 'assume esi:ptr ThisClassName' so that we may address any of the Data Variable fields in this object instance via '.VariableName'

Then we see an 'ACall esi.Init, pOwner'
ACall = ANCESTOR CALL (my daddys class)
OCall = OBJECT CALL  (my own class)
The ACall is passing the call the the ANCESTOR CLASS's Init method.
If we want it, must manually pass the call on, or it wont happen.
These are the small things that make C++ and OA32 largely different - we can choose, and we can abuse, once we know whats going on... we have choices that are not available in C++.

Next, we see some calls to initialize the 'embedded objects' in the simulator class.
Embedded objects are created automagically, but you still need to initialize them, and unlike the objects you create yourself, you are responsible for destroying them too.
That means that the simulator class will also need a DONE method, whose job will be, at minimum, to call the Done method of each Embedded object.
For those of you who are new to OOP, please appreciate that Embedded objects are a rather advanced feature of OA32, and that theres other ways to get the same result.

Then we see some code which is defining the world bounding planes.
Its not very efficient but its a good OOP example.
I'm allocating six D3DXPLANE structs as memory objects, and storing their pointers in a DataCollection.
Each struct is being filled out with values that describe my six World Bounding Planes.

Now that we've been reminded that theres a DONE method, I better show you that too.

;We must clean up the Embedded classes by hand
Method D3D_PhysicsSimulator.Done,uses esi
SetObject esi
DbgWarning "Closing down"
    OCall .Bodies::Collection.Done
    OCall .BodySprings::DataCollection.Done
    OCall .WorldSprings::DataCollection.Done
    OCall .Walls::DataCollection.Done
MethodEnd


Since theyre Embeddeds, we must manually call their Done method when we're Done.
If thats an advanced feature, you should have no trouble with the simple ones :P

I'd appreciate some feedback, am I hitting this from the right angle? Is the informal discussion helpful or annoying? Let me know your thoughts :)

Posted on 2008-05-26 01:44:21 by Homer
Bodies, BodySprings, WorldSprings, Walls.

Our simulator looks after four lists of STUFF.
Notice how much I like Stuff? Who doesn't? Heh.

Bodies - list of objects that derive from CollisionHull (ie Spheres and/or Boxes)
BodySprings - list of structs that describe inter-body springs (covered later)
WorldSprings - list of structs that describe springs that tether bodies to worldspace (later)
Walls - our set of six World Bounding Planes, hey, its a start.

Well, you're still here, perhaps this is going to get interesting after all :)

So - we created an instance of the Simulator class, and we Initialized it.
Now what?
In my demo, I create one or more BOXES.
You'll notice that the Simulator class provides a method for this, whose name is, suprisingly, Add_Box :)

That will be the next exciting episode. Stay tuned! More after these infomercials.

Posted on 2008-05-26 02:00:28 by Homer
Now that we've created an instance of our Simulator, we'll want to create one or more Rigid Bodies. Here is the code for the Add_Box method, which creates a 3D Box to our specifications and stores it in the simulator's collection of Bodies...


;You must describe a new Box by the Worldspace Position of its Center of Mass,
;with the axial half-widths of the unrotated Box,
;and with the initial Orientation as a 3x3 rotation matrix (Mat33).
;pvJointOffset is OPTIONAL, it indicates that the Rotational origin
;of this Box is somewhere other than its center, useful for Articulated Bodies.
;If present, this vector's orientation will be tied to that of the Box,
;and the rotated vector can later be used to transform from box space to joint space.
Method D3D_PhysicsSimulator.Add_Box,uses esi, pvBoxOrigin_WorldSpace, pmatBoxOrientation, pvBoxRadius, pvJointOffset, fDensity:real4
LOCAL pbox
SetObject esi
mov pbox, $New(Box)
OCall pbox::Box.Init,esi,pvBoxOrigin_WorldSpace, pmatBoxOrientation, pvBoxRadius, pvJointOffset
mov eax,pbox
mov .Box.dType, BODY_TYPE_BOX
OCall pbox::Box.Setup_Mass, fDensity
OCall .Bodies::Collection.Insert, pbox
mov eax,pbox
MethodEnd


As you can see, we specify the size of the box we want and the density of the material from which it is made... the method creates the box, calculates its mass from its density and size, and adds it to the collection.
It's not really my place to describe what a 3x3 matrix is, just know that we can use one to describe an arbitrary 3D orientation for our Box. I'll probably be talking more about 'Mat33' later, just think of it as a math tool in the same way that a Vector is a math tool.

I won't bother showing the Box.Init code today, as that will require an entire post - its simple and small code, but I need to talk about inheritance and other oopish stuff in that context.
Would you like to see the code for the Box.Setup_Mass method? :)

;Set up the Mass and Inertia Tensor
;Since our Box is described in BodySpace, we can build a 'Diagonal' Inertia Tensor
Method Box.Setup_Mass, uses esi, fDensity:real4
LOCAL tempmat:Mat33

SetObject esi
DbgVec3 .vRadius, "Axial Half-Lengths of Box"
fld1

;Calculate volume = length*width*height
fld .vRadius.x
fadd st(0),st(0)
fld .vRadius.y
fadd st(0),st(0)
fmul
fld .vRadius.z
fadd st(0),st(0)
fmul
;Calculate Mass=Density*Volume
fmul fDensity
fst .Mass

;Calculate 1/Mass
fdiv
fstp .OneOverMass

;Calculate MOI Tensor for unrotated box

;InertiaTensor(0,0) = 1/12 * (Mass*(dY2*dY2+dZ2*dZ2))
;InertiaTensor(1,1) = 1/12 * (Mass*(dX2*dX2+dZ2*dZ2))
;InertiaTensor(2,2) = 1/12 * (Mass*(dX2*dX2+dY2*dY2))

; WE REALLY WANT THE INVERSE OF THIS TENSOR
; We never use the tensor, we only ever use the Inverse tensor
; We can find the inverse tensor directly (without needing to Invert the tensor)

;InverseBodyInertiaTensor(0,0) = 12 / (Mass*(dY2*dY2+dZ2*dZ2))
;InverseBodyInertiaTensor(1,1) = 12 / (Mass*(dX2*dX2+dZ2*dZ2))
;InverseBodyInertiaTensor(2,2) = 12 / (Mass*(dX2*dX2+dY2*dY2))

fld r4_12
fld  .vRadius.y
fadd st(0),st(0)
fmul st(0),st(0)
fld .vRadius.z
fadd st(0),st(0)
fmul st(0),st(0)
fadd
fmul .Mass
fdiv
fstp .InverseBodyInertiaTensor.m00
mov  .InverseBodyInertiaTensor.m01,0
mov  .InverseBodyInertiaTensor.m02,0
;
fld r4_12
mov .InverseBodyInertiaTensor.m10,0
fld  .vRadius.x
fadd st(0),st(0)
fmul st(0),st(0)
fld .vRadius.z
fadd st(0),st(0)
fmul st(0),st(0)
fadd
fmul .Mass
fdiv
fstp tempmat.m11
mov .InverseBodyInertiaTensor.m12,0
;
fld r4_12
mov  .InverseBodyInertiaTensor.m20,0
mov  .InverseBodyInertiaTensor.m21,0
fld  .vRadius.x
fadd st(0),st(0)
fmul st(0),st(0)
fld  .vRadius.y
fadd st(0),st(0)
fmul st(0),st(0)
fadd
fmul .Mass
fdiv
fstp .InverseBodyInertiaTensor.m22
      ;Later, we'll transform this BodySpace matrix into WorldSpace...
MethodEnd


OK, the calculating of Volume from dimensions of box, and Mass from volume and density, maybe some of you remember that stuff from school.
But the Inertia Tensor, you've maybe never heard of before.
The tensor describes how the mass of a body is distributed over its volume (its shape).
We can calculate the tensor for any arbitrary closed volume of any shape, but the tensors for a bunch of common geometries like boxes, spheres, cylinders etc is KNOWN, you can find tables of these on the web.

In the comments you'll see that we actually require not the Tensor itself, but its INVERSE.
Normally, inverting a matrix is an expensive operation.
Since the tensor is NOT a normal, orthographic matrix (unlike an orientation matrix), we cannot simply use the 'transpose trick' to avoid performing a full Inversion of the tensor.
We have to do it 'the long way', which is really expensive.
I've provided a Mat33_Inverse function which CAN do it.
But if we provide a bodyspace tensor which is DIAGONALIZED, we dont need to Invert it, because we know that if the tensor looks like this:

ixx 0 0
0 iyy 0
0 0 izz

then the inverse matrix must look like this:
1/ixx 0 0
0 1/iyy 0
0 0 1/izz

and consequently, if ixx = 1/12 * Mass * (y^2 + z^2)
then we know that 1/ixx = 12 / Mass * (y^2 + z^2)
YES? :)

noting that this is ONLY true if the matrix is nice and diagonal in the first place (only has values in the topleft to bottomright diagonals, and is otherwise full of zeroes, like Identity matrix)

So you can see now that I was able, in this case, to avoid the InvertMatrix.
Now we've created our simulator, created a box, and initialized its mass properties.
This object is essentially now ready to begin being simulated, but if we want to position the object somewhere specific in the world, now would be a great time to do it.
Yeah, we haven't actually set the initial Position yet !!
Theres also the initial Angular Momentum and Linear Velocity to set up if we choose to.
Or you can just start simulating right now, with the body at (0,0,0) in world space, no linear velocity, and no angular momentum (or velocity).

I think we're about ready to start looking at how we drive the simulator.
Theres a method called D3D_PhysicsSimulator.Update which you should be calling from your main program loop (eg from your 'windows message pump' loop).
This method has just one param, which is the amount of time that you wish to advance the simulation. Ideally, this should be the ELAPSED TIME between iterations of that driving loop.
The simulator will add this value to an internal 'time accumulator', and will make one call to the D3D_PhysicsSimulator.Simulate method, passing it the fixed timestep value of 0.100 seconds. This method will return a value that is either 0.100 (nothing happened) or less (something collided), and the returned value ('amount of time consumed this frame') is subtracted from the time accumulator. D3D_PhysicsSimulator.Update will continue making calls to D3D_PhysicsSimulator.Simulate until the time accumulator contains less than 0.100 (until theres not enough time for a full step). When that happens, it returns to the caller (your main loop code).

That pretty much takes care of describing the entrypoint code.
Its time to show you the D3D_PhysicsSimulator.Update method.
But you'll have to wait until the next post for that :)


Posted on 2008-05-27 01:19:29 by Homer
Lets take a look at the Collision class constructors:


Method CollisionHull.Init,uses esi, pOwner
SetObject esi
ACall esi.Init, pOwner
invoke RtlZeroMemory,addr .aConfigurations, sizeof configuration * 2
MethodEnd

Method Sphere.Init, uses esi, pOwner, pvSphereOrigin_WorldSpace, pmatSphereOrientation, fRadius:real4
SetObject esi
ACall esi.Init, pOwner
invoke RtlZeroMemory,addr .aConfigurations, sizeof configuration*2

m2m .fRadius, fRadius

mov eax,pvSphereOrigin_WorldSpace
m2m .aConfigurations.CMPosition.x, .Vec3.x
m2m .aConfigurations.CMPosition.y, .Vec3.y
m2m .aConfigurations.CMPosition.z, .Vec3.z

mov eax,pmatSphereOrientation
m2m .aConfigurations.Orientation.m00, .Mat33.m00
m2m .aConfigurations.Orientation.m01, .Mat33.m01
m2m .aConfigurations.Orientation.m02, .Mat33.m02
m2m .aConfigurations.Orientation.m10, .Mat33.m10
m2m .aConfigurations.Orientation.m11, .Mat33.m11
m2m .aConfigurations.Orientation.m12, .Mat33.m12
m2m .aConfigurations.Orientation.m20, .Mat33.m20
m2m .aConfigurations.Orientation.m21, .Mat33.m21
m2m .aConfigurations.Orientation.m22, .Mat33.m22
MethodEnd

Method Box.Init, uses edi esi, pOwner, pvBoxOrigin_WorldSpace, pmatBoxOrientation, pvBoxRadius, pvJointOffset
LOCAL fRadius
SetObject esi

;Find the radius of the box's Sphere
mov edx,pvBoxRadius
fld .Vec3.x
fmul st(0),st(0)
fld .Vec3.y
fmul st(0),st(0)
fld .Vec3.z
fmul st(0),st(0)
fadd
fadd
fsqrt
fstp fRadius
DbgFloat fRadius, "Theoretical BoundingSphere radius for this Box"

;Initialize the Ancestor (Sphere), which will record Position (of COM) and Orientation
ACall esi.Init,  pOwner, pvBoxOrigin_WorldSpace, pmatBoxOrientation, fRadius

;Copy box's axial half-widths
mov eax,pvBoxRadius
m2m .vRadius.x, .Vec3.x
m2m .vRadius.y, .Vec3.y
m2m .vRadius.z, .Vec3.z

;If theres a Joint Offset vector, grab it
.if pvJointOffset!=0
mov edx,pvJointOffset
m2m .vJointOffsetVector.x, .Vec3.x
m2m .vJointOffsetVector.y, .Vec3.y
m2m .vJointOffsetVector.z, .Vec3.z
.endif

;Set up the Box Points (in Box space)
mov .NumberOfBoundingVertices,8
lea edi,.aBodyBoundingVertices

mov eax,pvBoxRadius
;minminmin
fld .Vec3.x
fld .Vec3.y
fld .Vec3.z
fchs
fstp .Vec3.z
fchs
fstp .Vec3.y
fchs
fstp .Vec3.x
add edi,sizeof Vec3
;minminmax
fld .Vec3.x
fld .Vec3.y
m2m .Vec3.z,.Vec3.z
fchs
fstp .Vec3.y
fchs
fstp .Vec3.x
add edi,sizeof Vec3
;minmaxmin
fld .Vec3.x
m2m .Vec3.y,.Vec3.y
fld .Vec3.z
fchs
fstp .Vec3.z
fchs
fstp .Vec3.x
add edi,sizeof Vec3
;minmaxmax
fld .Vec3.x
m2m .Vec3.y,.Vec3.y
m2m .Vec3.z,.Vec3.z
fchs
fstp .Vec3.x
add edi,sizeof Vec3
;maxminmin
m2m .Vec3.x, .Vec3.x
fld .Vec3.y
fld .Vec3.z
fchs
fstp .Vec3.z
fchs
fstp .Vec3.y
add edi,sizeof Vec3
;maxminmax
m2m .Vec3.x, .Vec3.x
fld .Vec3.y
fchs
fstp .Vec3.y
m2m .Vec3.z, .Vec3.z
add edi,sizeof Vec3
;maxmaxmin
m2m .Vec3.x, .Vec3.x
m2m .Vec3.y, .Vec3.y
fld .Vec3.z
fchs
fstp .Vec3.z
add edi,sizeof Vec3
;maxmaxmax
m2m .Vec3.x, .Vec3.x
m2m .Vec3.y, .Vec3.y
m2m .Vec3.z, .Vec3.z


mov .pPlanes,$MemAlloc(6*sizeof Vec4)
mov edi,eax

;Left and Right Planes
mov .D3DXPLANE.y,0
mov .D3DXPLANE.z,0
fld1
fchs
fstp .D3DXPLANE.x
fld .vRadius.x
fstp .D3DXPLANE.w
add edi,sizeof Vec4

mov .D3DXPLANE.y,0
mov .D3DXPLANE.z,0
fld1
fstp .D3DXPLANE.x
fld .vRadius.x
fstp .D3DXPLANE.w
add edi,sizeof Vec4

;Top and Bottom Planes
mov .D3DXPLANE.x,0
mov .D3DXPLANE.z,0
fld1
fchs
fstp .D3DXPLANE.y
fld .vRadius.y
fstp .D3DXPLANE.w
add edi,sizeof Vec4

mov .D3DXPLANE.x,0
mov .D3DXPLANE.z,0
fld1
fstp .D3DXPLANE.y
fld .vRadius.y
fstp .D3DXPLANE.w
add edi,sizeof Vec4

;Front and Back Planes
mov .D3DXPLANE.x,0
mov .D3DXPLANE.y,0
fld1
fchs
fstp .D3DXPLANE.z
fld .vRadius.z
fstp .D3DXPLANE.w
add edi,sizeof Vec4

mov .D3DXPLANE.x,0
mov .D3DXPLANE.y,0
fld1
fstp .D3DXPLANE.z
fld .vRadius.z
fstp .D3DXPLANE.w
add edi,sizeof Vec4

MethodEnd


I'm going to leave it to you to examine whats happening in each of these, what I want to talk about is the hierarchical aspect here.
We're going to make a Box, whose class derives from Sphere.
Making a Box (via D3D_PhysicsSimulator.Add_Box) will cause Box.Init to be called.
Within this method is a line which performs "ACall Init", which actually is placing a call to the Init method in the ancestor class .. ie, Sphere.Init is being called.
The Sphere class derives from the CollisionHull class.
The Sphere.Init method contains a line which performs "ACall Init" - can you guess what this will do? Yes, it calls the ancestor class's Init method.. CollisionHull.Init is called.
That's all we really care about...
... but just to complete the story, CollisionHull.Init contains 'ACall Init' also. Who is CollisionHull's daddy? Its Streamable (who we haven't met, and won't be).
Streamable.Init contains an 'ACall Init' as well.
Streamable's daddy is called Primer, and Primer is the most primitive ObjAsm32 class, the root of all ObjAsm32 classes (like IUnknown is to COM interfaces).

When we make a class A, and derive from it a class B, then class B inherits ALL the stuff from class A... all of the methods, and all of the Data fields.
This means that our Box object contains all the data fields for Box, Sphere AND CollisionHull, and has access to all the methods of those classes.  *see footnote
In terms of collision detection, as mentioned in another thread, I wrap each Box with a theoretical Sphere, used to accelerate collision testing... this is how I achieved that.
Every box has data that describes a sphere which totally encloses that box, stored in the fields it inherited from the Sphere class.

Note that the Sphere class defines a method called "Setup_Mass".
Note that the Box class REDEFINES the method called "Setup_Mass".
If we call Setup_Mass apon a Box, we get Box.Setup_Mass.
And if we call it apon a Sphere, we get Sphere.Setup_Mass.
They won't both be called unless we deliberately do it.

So, now I've had my oopish rant, we can get back to the entrypoint code..


;Advances the physics simulation by one TimeStep for each TimeStep that has elapsed.
;This method decouples rendering from physics in regards to Time.
;Your application should call this from its main loop,
;it's your entrypoint to drive the simulation.
Method D3D_PhysicsSimulator.Update,uses esi,deltaTime:real8
.data
PhysicsTimeAccumulator real8 0.000f
PhysicsTimeStep real8 0.100f
.code
SetObject esi
.if .Bodies.dCount==0
ret
.endif
;Accumulator = Accumulator + deltaTime
    fld  deltaTime ;Add the deltaTime to the Accumulator
    fadd PhysicsTimeAccumulator
    fstp PhysicsTimeAccumulator
.repeat

fld  PhysicsTimeAccumulator
fsub  PhysicsTimeStep
fstpReg eax
.if eax!=0 && eax!=80000000h
    .ifBitSet eax,BIT31 ;accumulator < timestep
    ;DbgWarning "not time yet"    
    .break ;Do nothing, its not time yet
    .endif
.endif

OCall Simulate, PhysicsTimeStep ;Advance the simulation by one TimeStep 
fchs ;counter = counter - consumed
fadd PhysicsTimeAccumulator
fstp PhysicsTimeAccumulator

.until 0

MethodEnd


Again, I'll leave it to you to examine what is happening in there, but essentially we're making sure that the simulation is synchronized to realtime, while also (as much as possible) operating in discrete (fixed) timesteps.
If you have questions, ask them!


* except for PRIVATE methods, which OA32 does indeed support.

Posted on 2008-05-27 05:56:54 by Homer
The D3D_PhysicsSimulator.Update method (we just looked at) is chopping up the incoming 'elapsed time' into  'timesteps' of 0.100 seconds , and attempting to advance the simulation by 0.100 seconds for each timestep that is 'available'.
In order to attempt to advance the simulation, it makes calls to a method called D3D_PhysicsSimulator.Simulate, lets take a look at that:



;This method will attempt to advance the simulation by DeltaTime.
;However, if a Collision is detected DURING this timeframe,
;the simulation will be advanced to THAT time, and the collision resolved.
;Returns FPU value = amount of time 'consumed' (DeltaTime or less)

Method D3D_PhysicsSimulator.Simulate,uses esi, DeltaTime:real8
SetObject esi

;Apply any 'contact resolution' from the previous iteration, and then
;calculate Force and Torque (due to current linear/angular momentum, and gravity)
OCall esi.ComputeForces

;Integrate our system forward by 'remaining time' (from Source to Target configuration)
    OCall esi.Integrate, DeltaTime
   
    ;Perform sweep-testing of unique pairs of bodies, and also checking for body/Wall collisions
    OCall CheckCollisions, DeltaTime

;Swap the configurations: source becomes target, target becomes source
push .SourceConfigurationIndex
m2m  .SourceConfigurationIndex, .TargetConfigurationIndex
pop  .TargetConfigurationIndex
   
DbgWarning "Simulated One TimeStep"
MethodEnd


This simple method is the very heart of the physics simulator, and if this simulator was driving a game engine, we'd have to say that this piece of code is central to the entire game engine. Its that important.

1. Compute the current linear and angular Force of each Body
2. Integrate the entire system to the End of the current TimeStep
3. Check for and handle Collisions which occur during the current timestep
4. Swap the 'global config indices'

If we look back to the definition for the CollisionHull class, we can see that each Rigid Body in our simulation has TWO 'configuration' structs within its data fields.
One of these is the "current" physical state of the body, and the other is the "target" physical state at the end of the current timestep.
After each iteration of the simulator, these two are switched around so that the target state becomes the new source state.
But rather than copying/movng data around in each and every simulated body, I'm simply switching a couple of indexing fields, toggling their values between (0, 1) and (1,0).
These indexing fields affect all simulated bodies, so they are kept in the simulator class.

In the next post, we'll begin to get our hands dirty.

Posted on 2008-05-27 21:26:06 by Homer
This ones gonna be huge.
Here's the ComputeForces method.
Just ignore all the stuff regarding Springs if you like, I won't be discussing springs - but its interesting to note that springs directly mess with the force and torque at this early stage.
And for the moment, I will also disregard collision response.
We're really interested in how we get Force and Torque from the current state of a body plus external influences.


;Calculate instantaneous Force and Torque
Method D3D_PhysicsSimulator.ComputeForces,uses esi edi ebx
LOCAL ftemp,fmag
LOCAL pSpringStructure
LOCAL pBody0, pConfiguration0
LOCAL pBody1, pConfiguration1
LOCAL U0:Vec3, U1:Vec3
LOCAL VU0:Vec3, VU1:Vec3
LOCAL Position0:Vec3,Position1:Vec3
LOCAL RelativeVelocity:Vec3
LOCAL SpringVector:Vec3, Spring:Vec3
SetObject esi

;Calculate internal forces due to momentum
    xor ecx,ecx
    .while ecx<.Bodies.dCount
push ecx   

mov edx,.Bodies.pItems
mov edi,dword ptr ;edi = current body
         
;If this Body encountered COLLISIONS in the PREVIOUS iteration,
;the Sum of the Resolved Impulses is still waiting to be Applied...
;They were stored in the Target configuration, but we've swapped configs since then,
;so we can now find them in the Source configuration :)
.if .CollisionHull.dNumSimultaneousCollisions!=0
DbgWarning "Applying Accumulated Collision Response"
OCall edi::CollisionHull.Apply_Accumulated_Response, .SourceConfigurationIndex
.endif
       
;get ptr to current body's source config
mov eax,sizeof configuration
xor edx,edx
mul .SourceConfigurationIndex
lea edx,.CollisionHull.aConfigurations
add eax,edx ;eax = pconfiguration

;Set Force and Torque to zero
xor edx,edx
mov .configuration.Torque.x,edx
mov .configuration.Torque.y,edx
mov .configuration.Torque.z,edx
mov .configuration.CMForce.x,edx
mov .configuration.CMForce.y,edx
mov .configuration.CMForce.z,edx

;If Gravity is enabled, apply it to Force.Y
.if .GravityActive==TRUE   
fld Gravity
fmul .CollisionHull.Mass
fstp .configuration.CMForce.y
.endif

;Calculate Force due to Velocity, and Torque due to AngularVelocity
        .if .DampingActive==TRUE
;Configuration.CMForce += -Kdl * Configuration.CMVelocity
;Configuration.Torque += -Kda * Configuration.AngularVelocity           
__ScaleFloat3 .configuration.CMVelocity,mKdl
__AddToFloat3 .configuration.CMForce,  .configuration.CMForce
__ScaleFloat3 .configuration.AngularVelocity, mKda
__AddToFloat3 .configuration.Torque,.configuration.Torque
.else       
__ScaleFloat3 .configuration.CMVelocity,mNoKdl
__AddToFloat3 .configuration.CMForce,  .configuration.CMForce 
__ScaleFloat3 .configuration.AngularVelocity, mNoKda
__AddToFloat3 .configuration.Torque,.configuration.Torque
.endif       
    ;    lea edx,.configuration.CMVelocity       
    ;    DbgVec3 edx,"Velocity"
    ;    lea edx,.configuration.CMForce       
    ;    DbgVec3 edx,"Force"
       
pop ecx
inc ecx
.endw
   
;Calculate forces due to Body-World Springs
.if .WorldSpringsActive==TRUE  && .WorldSprings.dCount!=0 
xor ecx,ecx
.while ecx < .WorldSprings.dCount
push ecx
       
        ;Get ptr to affected Body from the Spring struct
mov eax,.WorldSprings.pItems
mov eax,
mov pSpringStructure,eax       
mov edx, .BodyWorldSpring.pBody
mov pBody0,edx
;Get ptr to affected Body's source config
mov eax,sizeof configuration
mul .SourceConfigurationIndex
mov edx,pBody0
lea edx,.CollisionHull.aConfigurations
add eax,edx
mov pConfiguration0, eax

;Transform the AnchorPoint (on the Body) from BodySpace to WorldSpace
mov ebx,pSpringStructure
lea edx, .BodyWorldSpring.vAnchorBody
lea ebx, .configuration.CMPosition
lea eax, .configuration.Orientation
Vec3_transform .Vec3, .Mat33, .Vec3
__StowFloat3 Position0

;Calculate the change in rest length (compressed or stretched spring),
;and also the Magnitude of that change
            Vec3_Mag2 Position0, .BodyWorldSpring.vAnchorWorld
fst fmag ; = current spring length squared

mov edx,pSpringStructure
fld .BodyWorldSpring.fRestLength
fmul .BodyWorldSpring.fRestLength ;*** avoid fsqrt penalty
fsub
fstp ftemp ; = deviation in spring length

.if ftemp==0 || ftemp==80000000h
;The spring is at rest length, no need for spring forces
.else
;Calculate WorldSpace Position of AnchorPoint (on the Body), relative to Body's COM
mov eax,pConfiguration0
__SubFloat3 Position0, .configuration.CMPosition
__StowFloat3 U0
;Calculate WorldSpace Velocity at AnchorPoint (on the Body)
CrossProduct .configuration.AngularVelocity, U0
__AddToFloat3 VU0, .configuration.CMVelocity
           
mov edx,pSpringStructure
__SubFloat3 Position0, .BodyWorldSpring.vAnchorWorld 
fld mKws
fmul ftemp
fmul st(3),st(0)           
fmul st(2),st(0)
fmul
__StowFloat3 Spring

           
; Project Velocity onto Spring to get damping vector... basically a Gram-Schmidt projection
; DampingForce = -Kwd*(DotProduct(VU,Spring)/DotProduct(Spring,Spring)) * Spring
; Spring += DampingForce
; Then  we can apply our Spring Force as follows:
; CMForce = CMForce + Spring
; Torque  = Torque + CrossProduct(U,Spring)
DotProduct VU0, Spring
DotProduct Spring,Spring
fabs
fdiv
fmul fmag
fmul mKwd
fstp ftemp                 
__ScaleFloat3 Spring, ftemp
__AddToFloat3 Spring,Spring
__ScaleFloat3 Spring, fmag
mov eax,pConfiguration0
__AddToFloat3 .configuration.CMForce,  .configuration.CMForce
CrossProduct U0, Spring
__AddToFloat3 .configuration.Torque, .configuration.Torque

.endif
pop ecx
inc ecx
.endw
.endif 

;Calculate forces due to Body-Body Springs
    .if .BodySpringsActive==TRUE && .BodySprings.dCount!=0
xor ecx,ecx
.while ecx < .BodySprings.dCount
push ecx
       
mov eax,.BodySprings.pItems
mov eax,
mov pSpringStructure,eax

; We need to transform the anchorpoints in the Spring
; from their respective bodyspaces into worldspace           
mov edx, .BodyBodySpring.pBody0
mov pBody0,edx
mov eax,sizeof configuration
mul .SourceConfigurationIndex
mov edx,pBody0
lea edx,.CollisionHull.aConfigurations
add eax,edx
mov pConfiguration0, eax
mov ebx,pSpringStructure
OCall pBody0::CollisionHull.Transform_Vec3_BodyToWorld,addr .BodyBodySpring.vBody0Anchor,eax
__StowFloat3 Position0
           
mov eax,pSpringStructure
mov edx, .BodyBodySpring.pBody1
mov pBody1,edx
mov eax,sizeof configuration
mul .SourceConfigurationIndex
mov edx,pBody1
lea edx,.CollisionHull.aConfigurations
add eax,edx
mov pConfiguration1, eax   
mov ebx,pSpringStructure
mov edx,pConfiguration1
OCall pBody1::CollisionHull.Transform_Vec3_BodyToWorld,addr .BodyBodySpring.vBody1Anchor, eax
__StowFloat3 Position1

;Calculate the change in rest length (compressed or stretched spring),
;and also the Magnitude of that change
mov edx,pSpringStructure
Vec3_Length  Position0, Position1
fst fmag ; = current spring length
mov edx,pSpringStructure
fsub .BodyBodySpring.fRestLength
fstp ftemp ; = deviation in spring length

.if ftemp==0 || ftemp==80000000h
;The spring is at rest length, no need for spring forces
.else
mov eax,pConfiguration0
__SubFloat3 Position0, .configuration.CMPosition
__StowFloat3 U0
CrossProduct .configuration.AngularVelocity, U0
__AddToFloat3 VU0, .configuration.CMVelocity
           
mov eax,pConfiguration1
__SubFloat3 Position1, .configuration.CMPosition
__StowFloat3 U1
CrossProduct .configuration.AngularVelocity, U1
__AddToFloat3 VU1, .configuration.CMVelocity

__SubFloat3  VU0, VU1
__StowFloat3 RelativeVelocity
__SubFloat3  Position0, Position1
__StowFloat3 SpringVector
invoke D3DXVec3Normalize, addr SpringVector,addr SpringVector

; Spring = -K * SpringVector * deviation * magnitude
__LoadFloat3 SpringVector   
fld mKbs
fmul ftemp
fmul st(3),st(0)           
fmul st(2),st(0)
fmul
__StowFloat3 Spring
     
DotProduct VU0, Spring ;Perform a Gram-Smidt Projection
DotProduct Spring, Spring
fdiv
fmul mKbd ; * Dampening
fmul fmag ; * Magnitude
fstp ftemp
__ScaleFloat3 Spring, ftemp
__AddToFloat3 Spring, Spring 

mov eax,pConfiguration0
__LoadFloat3 Spring
__AddToFloat3 .configuration.CMForce, .configuration.CMForce
mov eax,pConfiguration1
__LoadFloat3 Spring
__SubFromFloat3 .configuration.CMForce, .configuration.CMForce

CrossProduct U0, Spring 
mov eax,pConfiguration0
__AddToFloat3 .configuration.Torque, .configuration.Torque 
CrossProduct U1, Spring
mov eax,pConfiguration1
__SubFromFloat3 .configuration.Torque, .configuration.Torque
.endif
       
pop ecx
inc ecx
.endw
.endif
MethodEnd


It wouldn't be fair to throw all those math macros at you without a source of reference.
Attached is a file containing a whole plethora of useful and relevant math functions, many of them in macro form.

In the next post, we'll be looking at the simulator's Integrate method, which takes the force and torque, and applies them for some Time, to calculate the Target configs.
Attachments:
Posted on 2008-05-27 22:32:51 by Homer
Here is the simulator's Integrate method.
As you can see, its really just a wrapper function that calls the CollisionHull.Integrate method owned by each simulated Body... the D3D_PhysicsSimulator.Integrate method is responsible for calculating the Target configurations of ALL bodies in the simulation, based on their Source (current) configurations, the current Forces, and the amount of Time.


;Calculate the Target configurations based on the Source configurations and DeltaTime,
;and Transform the Bounding Vertices to their new Orientation and Position in WorldSpace
Method D3D_PhysicsSimulator.Integrate,uses esi edi, DeltaTime:real8
LOCAL psrc,ptgt
SetObject esi
    xor ecx,ecx
    .while ecx<.Bodies.dCount
    push ecx
   
    mov eax,.Bodies.pItems
    mov edi, dword ptr ;ptr to nth Body
    mov psrc,$OCall (GetPtrToConfig,edi,.SourceConfigurationIndex)
    mov ptgt,$OCall (GetPtrToConfig,edi,.TargetConfigurationIndex)
   
    ;Integrate the Physics state (from Source to Target configuration)
    OCall edi::CollisionHull.Integrate,psrc,ptgt,DeltaTime
   
    mov edx,ptgt
    lea edx,.configuration.CMPosition
    DbgVec3 edx,"Integrated Position (Target config)"

    pop ecx
    inc ecx
    .endw
MethodEnd


and since that was just a tease, I'll give you the real deal too.
The CollisionHull.Integrate method is responsible for calculating the Target configuration of one Body, based on its source config, its current forces, and Time.


;Updates the Target configuration based on the Source configuration and DeltaTime
Method CollisionHull.Integrate,uses esi ebx, pSource, pTarget, DeltaTime:real8
LOCAL matTemp:Mat33
LOCAL matTemp2:Mat33
LOCAL ftemp


SetObject esi
;Only bodies which are Awake need to be Integrated
.if .bIsAwake==TRUE

;Check if this Awake body should be put to sleep
OCall esi.Try_To_Sleep, pSource, pTarget
.if eax==TRUE
ExitMethod
.endif

DbgFloat DeltaTime

    ; New Position is integrated from Old Linear Velocity, applied for some Time:
    ; Target.CMPosition = Source.CMPosition + DeltaTime * Source.CMVelocity
    mov edx,pSource
    mov eax,pTarget
    __ScaleFloat3 .configuration.CMVelocity, DeltaTime
__AddToFloat3 .configuration.CMPosition, .configuration.CMPosition

    ; New Velocity is integrated from Linear Force, applied for some Time:
    ;Target.CMVelocity = Source.CMVelocity + (Body.OneOverMass * Source.CMForce * DeltaTime)
    mov edx,pSource
__LoadFloat3 .configuration.CMForce
fld .OneOverMass
fmul DeltaTime
fmul st(3),st(0)
fmul st(2),st(0)
fmul
__StowFloat3  .configuration.CMVelocityChange
__AddFloat3   .configuration.CMVelocity, .configuration.CMVelocityChange
__AddToFloat3 .configuration.CMVelocity, .configuration.CMVelocity


    ;Thats it for Linear stuff, time to do the Angular stuff...    
    ;First we'll calculate Angular Momentum because its kinda easy..
; Angular Momentum is integrated from Torque Force, applied for some Time:

    ;Target.AngularMomentum = Source.AngularMomentum + (DeltaTime * Source.Torque)    
    mov eax,pSource
    mov edx,pTarget
__ScaleFloat3 .configuration.Torque, DeltaTime
__AddToFloat3 .configuration.AngularMomentum, .configuration.AngularMomentum    
   
    ;If theres no Source angular velocity, then Orientation cannot change
    ;and we can avoid a whole lot of calculations
;     mov edx,pSource
;     fld .configuration.AngularVelocity.x
;     fabs
;     fld .configuration.AngularVelocity.y
;     fabs
;     fld .configuration.AngularVelocity.z
;     fabs
;     fadd
;     fadd
;     fstpReg eax
; .if eax==0
; ;No source velocity, no need to change the orientation, or the World InvInertiaTensor
; mov eax,pSource
; mov edx,pTarget
; invoke RtlMoveMemory,addr .configuration.Orientation,addr .configuration.Orientation,sizeof Mat33
;    
;     mov eax,pSource
;     mov edx,pTarget
;     invoke RtlMoveMemory,addr .configuration.InverseWorldInertiaTensor,addr .configuration.InverseWorldInertiaTensor,sizeof Mat33
;    
;     .else
   
    ;In order to find the new Orientation, we will need to apply angular velocity for some Time..
    ;We'll be using a 'skew'symmetric matrix', which is built from our angular velocity vector...
    ;That matrix will be multiplied by the Body's Orientation, and scaled by DeltaTime.
    ;This gives us an 'offset rotation matrix' and so finally,
    ;the original Orientation matrix will be Added:
    ; OffsetOrientation = (DeltaTime * SkewSymmetric(Source.AngularVelocity) * Source.Orientation)
    ; Target.Orientation = Source.Orientation + OffsetOrientation
    ;We can create a SkewSymmetric Mat33 from a Vec3 using the STAR or TILDE Matrix Operator.
    ;I've provided a Mat33_star macro for this purpose.    
   
    ;Prepare the SkewSymmetric matrix, and Scale it by Time
mov edx,pSource
Mat33_star matTemp, .configuration.AngularVelocity
lea eax,matTemp
Mat33_mult_Scalar eax, DeltaTime
   
;Apply the scaled SS matrix to the Orientation to find Offset Rotation
mov edx,pSource
invoke Mat33_multiply,addr matTemp2, addr .configuration.Orientation, addr matTemp
   
;Adding the Source and Offset Orientations
mov eax,pSource
lea edx,.configuration.Orientation ;Source.Orientation
mov ebx,pTarget
lea ebx,.configuration.Orientation ;Target.Orientation
lea ecx,matTemp2 ;Offset.Orientation
Mat33_Add  ebx, ecx, edx   
   
;Orthogonalizing the result to prevent Skew
mov edx,pTarget
invoke OrthonormalizeOrientation,addr .configuration.Orientation
   
;Since the Orientation has changed, we need to update the Worldspace InverseInertiaTensor as well

; Transform the InvInertiaTensor from BSpace into WSpace via a Similarity transform:
; Target.InverseWorldInertiaTensor = Target.Orientation * Body.InverseBodyInertiaTensor * Transpose(Target.Orientation)
mov edx,pTarget
invoke RtlMoveMemory,addr matTemp,addr .configuration.Orientation,sizeof Mat33
Mat33_transpose matTemp
;
mov edx,pTarget
invoke Mat33_multiply,addr matTemp2, addr .InverseBodyInertiaTensor,  addr matTemp
;
mov edx,pTarget
invoke Mat33_multiply,addr .configuration.InverseWorldInertiaTensor,addr matTemp2,addr .configuration.Orientation
; .endif

   
;If theres ANY target angular momentum, we'll extract the target angular velocity from it
mov edx,pTarget
fld .configuration.AngularMomentum.x
fabs
fld .configuration.AngularMomentum.y
fabs
fld .configuration.AngularMomentum.z
fabs
fadd
fadd
fstpReg eax
.if eax==0
;There's no angular momentum, so we dont need to derive the angular velocity - its zero
mov .configuration.AngularVelocity.x,eax
mov .configuration.AngularVelocity.y,eax
mov .configuration.AngularVelocity.z,eax
.else 
; Target.AngularVelocity = Target.InverseWorldInertiaTensor * Target.AngularMomentum
mov eax,edx
Vec3_mult_Mat33 .configuration.InverseWorldInertiaTensor, .configuration.AngularMomentum
__StowFloat3 .configuration.AngularVelocity
.endif

  ;Update the WorldSpace vertices for new orientation and position
  mov edx,pTarget
  OCall TransformVertices, addr .configuration.CMPosition, addr .configuration.Orientation
.endif

MethodEnd



Thats a lot to take in.
I'm going camping and hiking in the mountains for a couple of days, that should give you time to read over these posts and perhaps develop some questions.

Posted on 2008-05-28 19:56:05 by Homer
Near the start of the CollisionHull.Integrate method is a call to 'TryToSleep'.
The comments should explain clearly enough whats happening here.
You'll see a reference to 'TransformVertices' - if we put a body to sleep, we have to manually perform this call - otherwise the Integrate method will do it  (scroll down for more info).


;This method will put a body to sleep if it isn't moving.
;Sleeping bodies don't need to be Integrated,
;and they cannot be the CAUSE of any collisions.
Method CollisionHull.Try_To_Sleep,uses esi,pSource, pTarget
SetObject esi
    mov edx,pSource 
    ; integrate primary quantities... First, we'll do the Linear stuff...
Vec3Dot .configuration.CMForce, .configuration.CMForce
fsub fEpsilon
fstpReg eax
.ifBitSet eax,BIT31
;We have very low Linear velocity - what about Angular Momentum?
Vec3Dot .configuration.AngularMomentum, .configuration.AngularMomentum
fsub fEpsilon
fstpReg eax
.ifBitSet eax,BIT31
;This body is very low in energy, so it can 'sleep' from now on
mov .bIsAwake,FALSE
DbgWarning "Body has low energy - Putting body to sleep"
;Clone the source config into target config
invoke RtlMoveMemory,pTarget,pSource,sizeof configuration
mov edx,pSource
;Ensure that our worldspace vertex array is up to date
OCall esi.TransformVertices,addr .configuration.CMPosition, addr .configuration.Orientation
mov eax,TRUE
.endif
.endif
MethodEnd


Near the end of the CollisionHull.Integrate method is a call to 'TransformVertices', which transforms our set of (unrotated) bodyspace boundary points (vertices) into worldspace, according to the TARGET configuration.
It's this set of TRANSFORMED points, representing our Oriented and Positioned body, which we will use for Collision Testing - we must think of the Target configuration of a body as describing a 'proposed future state' which may or may not be acceptable due to collisions.


;Transform all the hull's bounding vertices from BodySpace to WorldSpace
Method CollisionHull.TransformVertices,uses ebx esi,pvPosition, pmatOrientation

SetObject esi
    ;Transform each point from bodyspace into worldspace
    xor ecx,ecx
    .while ecx<.NumberOfBoundingVertices
push ecx

mov eax,sizeof Vec3
mul ecx
push eax
lea edx,.aBodyBoundingVertices
add eax,edx ;edi=ptr to source point in bodyspace

        ;worldpoint = transform (bodypoint,rotation,translation)
        mov ebx,pmatOrientation
        mov edx,pvPosition
        Vec3_transform .Vec3, .Mat33, .Vec3
        lea eax,.aWorldBoundingVertices ;target vertices (WorldSpace)
        pop edx
        add eax,edx       
        __StowFloat3 .Vec3
       
    pop ecx
    inc ecx
    .endw   
MethodEnd


Next we'll cover my existing collision detection code in some detail - but I won't be explaining the algorithms, as I have done that here : http://www.asmcommunity.net/board/index.php?topic=29056.0
Posted on 2008-05-31 23:34:25 by Homer

Before we delve into the wonderful world of collision handling, lets review one of the very first methods that we looked at:


;This method will attempt to advance the simulation by DeltaTime.
;However, if a Collision is detected DURING this timeframe,
;the simulation will be advanced to THAT time, and the collision resolved.
;Returns FPU value = amount of time 'consumed' (DeltaTime or less)

Method D3D_PhysicsSimulator.Simulate,uses esi, DeltaTime:real8
SetObject esi

;Apply any 'contact resolution' from the previous iteration, and then
;calculate Force and Torque (due to current linear/angular momentum, and gravity)
OCall esi.ComputeForces

;Integrate our system forward by 'remaining time' (from Source to Target configuration)
OCall esi.Integrate, DeltaTime
 
;Perform sweep-testing of unique pairs of bodies, and also checking for body/Wall collisions
OCall CheckCollisions, DeltaTime

;Swap the configurations: source becomes target, target becomes source
push .SourceConfigurationIndex
m2m  .SourceConfigurationIndex, .TargetConfigurationIndex
pop  .TargetConfigurationIndex
   
DbgWarning "Simulated One TimeStep"
MethodEnd


We've covered what happens inside the Integrate method.... it calculated the set of Target configurations at DeltaTime - it figured out where our Bodies will be at "now+deltatime".
Then we see a call to the CheckCollisions method.... that's our next stop.
This method will return a float on the fpu stack - if theres no collisions, the value will be DeltaTime... but if theres a collision, the value will be "time until collision" - somewhat less than DeltaTime, and greater or equal to zero.
The returned value is telling us how far we ended up advancing the system, ie, it tells us how much of the timestep was actually consumed.. when we return to the Update method, that much time will be subtracted from the 'Time Accumulator".
This means that any "unused time" (due to collision) is accumulated and spent in the next iteration (of the Update method's timestepping loop).

I will now show you the CheckCollisions method - but I warn you, this method is quite likely to change without notice as I am still far from happy with it.... and, its a big one.

; This method advances the System to fCurrentDeltaTime or the earliest collision, whichever is first.
; Returns the "used" amount of FrameTime on the fpu (fCurrentDeltaTime or less)
Method D3D_PhysicsSimulator.CheckCollisions,uses esi edi, fCurrentDeltaTime:real8
LOCAL CollisionState
LOCAL pBody,pConfiguration
LOCAL pWall
LOCAL pOtherBody
local pt:Vec3
LOCAL ftemp:real8
LOCAL results[8]:dword
LOCAL fLastClearTime:real8
LOCAL fFirstCollisionTime:real8
LOCAL fbig:real8
LOCAL ptgtA
LOCAL vtemp:Vec3
LOCAL templist
LOCAL iAggressor, cnt



SetObject esi
mov templist, $New(DwordCollection,Init,0,16,256,-1)

mov CollisionState,Clear ;Be optimistic
fldz
fstp fLastClearTime
fld1
fstp fFirstCollisionTime


;---------------------------------------------------------------------------------------------------------
; Part One - Find the EARLIEST COLLISION TIME, and Collect all Bodies which Collide at that Time
; We'll use a two-stage filter that handles body/body and body/plane collisions

mov dword ptr fFirstCollisionTime[0],-1

mov pOtherBody,0

;For each Body in the Simulation:
xor ecx,ecx
mov iAggressor,ecx
.while ecx<.Bodies.dCount && CollisionState!=Penetrating

mov eax,.Bodies.pItems
mov eax,dword ptr
        mov pBody,eax
        mov edi,eax
               
        mov eax,sizeof configuration
        mul .TargetConfigurationIndex
        mov edx,pBody
        lea edx,.CollisionHull.aConfigurations
        add eax,edx
        mov pConfiguration,eax
       
        ;------------------------------------------------------------------------
        ;Test the current Body against the World Bounding Planes
OCall Sweep_Body_Against_World_Planes,pBody
mov pWall,edx
.if eax==Colliding
fstp ftemp
.endif
.if eax==Penetrating
;The Body's Sphere is penetrating a World Plane at the start of the timestep
;We'll need to perform an instantaneous check of the Body geometry NOW
;This is going to get messy since we can only test the TARGET config,
;and so we'll need to integrate this body BACK to where it started from..
OCall esi.GetPtrToConfig,pBody,.SourceConfigurationIndex
push eax
OCall esi.GetPtrToConfig,pBody,.TargetConfigurationIndex
pop ebx
OCall pBody::CollisionHull.Integrate, ebx,eax,r8_0
;OK, now we can do our instantaneousintersection test
;REPLACE THIS CODE , IT MAY NOT BE A BOX
OCall pBody::Box.Box_versus_Plane,addr results,pWall
fUnload
.if eax==Penetrating
;Its a HUGE problem - the Body is intersecting the plane at the START of timestep
DbgWarning "Error - Body intersects World Plane at start of timestep"
int 3
;fld fCurrentDeltaTime
;fmul r4_Half
;fstp fCurrentDeltaTime
;OCall templist::DwordCollection.DisposeAll
;pop ecx
;jmp @rep
.elseif eax==Colliding
DbgWarning "Body and Plane are Colliding NOW"
fldz
fld fCurrentDeltaTime
BitToggle pWall,BIT31 ;mark 2nd body as being a plane
jmp @F
.else
;A collision is imminent - use a smaller search window
DbgWarning "We're CLEAR again, but not for long..."
fld fCurrentDeltaTime
fmul r4_Quarter
fmul r4_Half
fstp ftemp
BitToggle pWall,BIT31 ;mark 2nd body as being a plane
OCall FindExactCollisionTime,pBody,pWall,r8_0,ftemp,addr results
jmp @f
.endif

.elseif eax==Colliding
;This Body is quite close to the Plane.
;Let's perform a Binary Search for the 'EXACT' Body/Plane collision time
DbgWarning "Finding exact collision for PRE-COLLIDING sphere/plane"
BitToggle pWall,BIT31 ;mark 2nd body as being a plane
OCall FindExactCollisionTime,pBody,pWall,fLastClearTime,fCurrentDeltaTime,addr results
@@: fstp fbig
fstp ftemp
.if eax==Colliding ;result = Colliding/Clear
;is it the earliest time?
fld fbig
fsub fFirstCollisionTime
fstpReg eax
.if eax==0 || eax==80000000h
;its equal to the earliest time, so collect it
OCall templist::DwordCollection.Insert, pBody
OCall templist::DwordCollection.Insert, pWall
.else
.ifBitSet eax,BIT31
;its the earliest time so far - toss out our old list
OCall templist::DwordCollection.DisposeAll
fld  ftemp
fstp fFirstCollisionTime
;Collect it
OCall templist::DwordCollection.Insert, pBody
OCall templist::DwordCollection.Insert, pWall
.endif
.endif
.endif
.endif

;------------------------------------------------------------------------
;Test (every Body further in the list) against the current Body
mov ecx,iAggressor
inc ecx
.while ecx<.Bodies.dCount
push ecx

mov eax,.Bodies.pItems
mov eax,dword ptr
mov pOtherBody,eax
;Coarse testing
OCall edi::Sphere.Sphere_Sweep_Sphere, pOtherBody, .SourceConfigurationIndex,.TargetConfigurationIndex
.if eax==Penetrating
fld1
fldz
jmp @Collision 
.elseif eax==Colliding
@Collision: fmul fCurrentDeltaTime
fstp fbig ;first penetrating time
fmul fCurrentDeltaTime
fstp ftemp ;last clear time
;Fine testing
OCall FindExactCollisionTime, pBody, pOtherBody, ftemp, fbig, addr results
fstp fbig
fstp ftemp
.if eax==Colliding ;result = Colliding/Clear
;is it the earliest time?
fld ftemp
fsub fFirstCollisionTime
fstpReg eax
.if eax==0 || eax==80000000h
;its equal to the earliest time, so collect it
OCall templist::DwordCollection.Insert, pBody
OCall templist::DwordCollection.Insert, pOtherBody
.else
.ifBitSet eax,BIT31
;its the earliest time so far - toss out our old list
OCall templist::DwordCollection.DisposeAll
fld  ftemp
fstp fFirstCollisionTime
fld fbig
fstp fLastClearTime
;Collect it
OCall templist::DwordCollection.Insert, pBody
OCall templist::DwordCollection.Insert, pOtherBody
.endif
.endif
.endif
.endif

pop ecx
inc ecx
.endw ;each body at a greater index than Aggressor (ie each unique body pair)
                       
        inc iAggressor
        mov ecx,iAggressor
    .endw ;each Aggressor Body in the Simulation


;----------------------------------------------------------------------------------------------------------------------
    ;At this point, we have identified the EXACT time that the first collision will occur during this frame,
    ;AND, we have made a list of all the Pairs of bodies which collide simultaneously.
;----------------------------------------------------------------------------------------------------------------------

;Quick check to see if the list of potential collisions is empty
mov edx,templist
.if .DwordCollection.dCount==0
DbgWarning "NO COLLISIONS DETECTED"
Destroy templist
fld fCurrentDeltaTime
mov eax,Clear
ExitMethod
.endif

;Goody, we have 'potential' collisions to process
DbgFloat fLastClearTime
DbgFloat fFirstCollisionTime
DbgDec .DwordCollection.dCount
   
;wind the entire simulation to the exact impact time
DbgWarning "Winding entire simulation to IMPACT time"    
OCall Integrate, fFirstCollisionTime    
;    



;------------------------------------------------------------------------
; Part Two - Process the set of simultaneous collisions

 
;Loop for all Pairs of potential colliders
xor ecx,ecx
mov edx,templist
mov eax,.DwordCollection.dCount
shr eax,1 ;divide by two for #Pairs
mov cnt,eax
.while ecx<cnt
push ecx

mov pBody, $OCall (templist::DwordCollection.DeleteAt,0)
mov pOtherBody, $OCall (templist::DwordCollection.DeleteAt,0)

;We're finally ready to resolve the current Collision, but before we do,
;it would be a good idea to determine what KIND of collision it was
;since we support two kinds : body/body, and body/worldplane
.ifBitSet pOtherBody, BIT31
;Since the high bit is set, BodyB is actually a WORLD PLANE, and not a Body at all.
BitToggle pOtherBody, BIT31 ;get rid of that flag bit

;Grab a pointer to Body A's Target config
OCall esi.GetPtrToConfig,pBody,.TargetConfigurationIndex
mov ptgtA, eax

OCall pBody::Box.Box_versus_Plane,addr results,pOtherBody
;We now have a LIST of which points of Body collided with the Plane !!!
xor ecx,ecx
mov edx,pBody
.while ecx<.CollisionHull.NumberOfBoundingVertices
push ecx
mov eax, results
DbgDec  eax,"POINTCOLLIDER RESULT"
.if results==Colliding
DbgWarning "RESOLVING COLLISION - BODYPOINT AND WALL PLANE"
mov eax,sizeof Vec3
mul ecx
mov edx,pBody
lea edx,.CollisionHull.aWorldBoundingVertices
add eax,edx
OCall pBody::CollisionHull.Resolve_Collision_PointNormal, eax, pOtherBody,ptgtA
mov edx,pBody
.endif
pop ecx
  inc ecx
.endw
;;
.else
;Its a Body/Body collision... we have a method just for this
OCall ResolveCollision_BodyBody, pBody,pOtherBody
.endif

pop ecx
inc ecx
.endw
Destroy templist
mov eax,TRUE
fld fFirstCollisionTime ;Return the USED amount of time (one partial timestep)

MethodEnd


I am pretty sure you'll have questions about this method.... and it's exposed a bunch more methods that we'll need to look at too.
Basically what's going on here is that we have two loops, one nested within the other.
The outer loop checks every Body A in the simulator...
First, it checks for collision of the Body A with World Bounding Planes.
Then we get to the inner loop, which checks every simulated Body B at a higher index than the current Body A.... and so we compare each unique pair of Bodies against one another.
The collision detection begins with Sphere tests, and then drops to fine geometric tests.
Is there anybody out there?

Posted on 2008-06-02 02:58:08 by Homer
Yes, I'm here. I'm just reading, accumulating knowledge - but I don't have enough to chime-in and start a debate on this topic. I've been keeping my peripheral vision on dynamics for quite some time, but never found it usable or beneficial for my works - so I haven't focused on it for more than a day.
The ways to do the broad-phase collision and idea for per-object rest/active state are obvious and already mentioned. It's just tiny clever optimizations that can be done there (which actually may speed it all up by a few orders of magnitude). The narrow-phase - seems like it should be done slowly like all existing dynamics engines do it.

Just one idea - about "waking" objects from sleep: Let's say we have a tower, made of bricks, that is currently in rest. When we hit a brick in the middle, we activate it. On activation, an object should wake-up all inactive objects, whose sphere intersects the current object's sphere, and are placed _above_ the current object. The bad alternative would be to keep a list of depending inactive objects. Also, depending on an object's mass, only forces above a given magnitude should activate this object.
Posted on 2008-06-02 05:02:18 by Ultrano
Ultrano - I'm yet to properly implement resting contact - I intend to do that after I add friction - but I've put a little thought toward the stacking problem too.
It occurs to me that when the middle brick is struck (and thus woken), it will immediately cause and detect collisions with the bricks above and below it, waking them both.
If there is insufficient force to move the lower brick due to low friction, that body will immediately go back to sleep - but the one above it - and those in turn above that one - will remain awake as they will begin to fall under gravity.
Posted on 2008-06-02 09:25:45 by Homer
funny I have made an animation, just ran some bricks in a physics simulationmode in Carrara, few months ago

now when you where talking about thresholds and make use of OOP, why couldnt we have all kinds of thresholds in the class BRICK ?like thresholds for if they gonna start to tilt and fall down because of underlying brick has moved so its not supporting it enough, could have a threshold on how much difference between positions between two bricks before we need to start a calculation of possible, how much area that supports upholding is just a simple fmul the rectangle you can get out of the differences, the absolute difference in x,z you use to subtract xsize,zsize to get the support area+ checks threshold for not too much over 49% difference of position in one of the axis
couldnt you kinda make something similar to BSPtree for a brickwall, building up a tree on how brick 1 is affecting brick 2 and 3 above itand in turn how brick 2 and 3 is in turn affecting other bricks, so at runtime its just run thru this tree when doing physics, instead of mass checking all bricks?
Posted on 2008-06-05 01:08:42 by daydreamer
Simulation of physics without consideration of Mass is as believable as Donkey Kong.
Thresholds are not desirable, we only use them where we absolutely must as maintaining them is an overhead with no other benefit or use.
The simulation of equilibrium at the center of mass is an automatic feature of a good simulator.
Posted on 2008-06-07 09:16:49 by Homer
I've implemented much of the new collision response code that I've been posting... to be precise, I've enabled the Frictionless impulse code.
With a couple of bugfixes and some screwing around, I now have a WONDERFUL stable simulation, but my stupid Body won't go to sleep when it settles down on the floor.
I'm getting microcollisions that ensure the Body never truly sleeps... at least its not falling through the floor :P
Somehow I'll need to check if Gravity is the key player and put the bugger to sleep if thats the case.
Posted on 2008-06-08 05:04:26 by Homer
I'm just now uploading a small movie (5 meg avi) showing the latest incarnation of the engine - a box falling with some initial rotation onto a frictionless surface, bouncing about, and settling to a stop.

http://stig.servehttp.com/homer/tiger.avi
Posted on 2008-06-08 10:09:15 by Homer