Files
BT411/engine/MUNGA/MOVER.cpp
T
arcattackandClaude Opus 4.8 a8eb8a427f MP: FIX peer spin hang/divergence -- exact quaternion integration + frequent orientation (task #50)
Answers 'how did the original handle this?' from the decomp (subagent hunt):
the 1995 binary's replicant reckoner (FUN_004ab1c8 -> FUN_00409f58, part_000.c:9359)
integrates heading EXACTLY: build a unit axis-angle rotation quaternion from
angularVelocity*dt ({axis*sin(t/2), cos(t/2)}) and Hamilton-multiply it onto the
heading (FUN_00409d9c) -- exact for any timestep, stays on the unit sphere.  It
further carries the full orientation quaternion in the FREQUENT pose record
(FUN_0040a938, 7-float pose), so the dead-reckon gap stays tiny.

Our reconstruction diverged two ways, both fixed:
 1. ReconQuatIntegrate (mechrecon.hpp) -- the reconstruction of FUN_00409f58 -- was
    STUBBED as , a crude small-angle VECTOR add.  Restored to
    the real exact axis-angle composition.  (A 'no stand-ins' violation: the comment
    even wrongly claimed Quaternion::Add == FUN_00409f58.)
 2. The engine Mover reckoner (MOVER.cpp AcceleratedDeadReckoner/LinearDeadReckoner)
    also did the vector Add on projectedOrigin.angularPosition -> over a long peer
    record gap it diverged to ~180deg then snapped (the reported spin HANG/hesitation).
    Routed both through a new ExactAngularProject() helper (same exact math).
 3. Orientation only rode the sparse type-4 resync; during a PURE spin the linear
    dense-send never fires (not translating), so the gap ballooned (~1.6s) and the now-
    exact projection sat far ahead -> the slerp jumped.  Added an ANGULAR dense-send
    (resync every frame while |yawRate|>0.1), mirroring the original's frequent-
    orientation model -> gap stays tiny -> smooth.

Verified live-autonomous (BT_AUTODRIVE+BT_FORCE_TURN + BT_RENDHDG render-rate probe):
the ~180deg divergence + multi-radian snaps are GONE (rendered maxStep 0.05-0.10 rad,
no jumps).  User confirms: no frame hang/hesitation.  MOVER.cpp change is strictly
more correct (exact==crude for the small per-frame master case; only large-gap peer
extrapolation changes), so walking is unaffected.

KNOWN REMAINING (separate, smaller): the turn-STEP leg animation (trn clip, mech2.cpp
advance_normally) runs at a FIXED idleStrideScale cadence that does not scale with the
rotation rate, so the legs lag/skip vs the (now-correct) body rotation.  Next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:05:16 -05:00

2109 lines
52 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "mover.h"
#include "player.h"
#include "boxsolid.h"
#include "interest.h"
#include "collasst.h"
#include "doorfram.h"
#include "door.h"
#include "line.h"
#include "app.h"
#include "notation.h"
#include <math.h>
//
// EXACT axis-angle rotation composition -- matches the 1995 BT binary's angular
// integrator (FUN_00409f58): build a unit rotation quaternion from the rotation
// VECTOR `rotVec` (angle = |rotVec|, axis = rotVec/angle) as { axis*sin(angle/2),
// cos(angle/2) } and Hamilton-multiply it onto `base`. The dead-reckoner previously
// did `out.Add(base, rotVec)` -- adding a scaled angular-velocity vector to the heading
// quaternion. That is only a small-angle approximation: fine per-frame (tiny angle),
// but over a long replicant dead-reckon gap it DIVERGES (the heading drifts to ~180deg
// then snaps -- the spinning-peer hesitation). This composition is exact for any angle
// and stays on the unit sphere.
//
static void ExactAngularProject(Quaternion &out, const Quaternion &base, const Vector3D &rotVec)
{
const Scalar ang = rotVec.Length();
if (ang > 1.0e-6f)
{
const Scalar h = 0.5f * (Scalar)fmodf((float)ang, 6.2831853f); // half of angle mod 2pi
const Scalar s = (Scalar)(sinf((float)h) / ang); // sin(angle/2)/angle
const Quaternion dq(rotVec.x * s, rotVec.y * s, rotVec.z * s, (Scalar)cosf((float)h));
out.Multiply(base, dq); // base (X) dq
}
else
{
out = base;
}
}
//#############################################################################
//############################### Mover #################################
//#############################################################################
//#############################################################################
// Shared Data Support
//
Derivation* Mover::GetClassDerivations()
{
static Derivation classDerivations(Entity::GetClassDerivations(), "Mover");
return &classDerivations;
}
Mover::SharedData
Mover::DefaultData(
Mover::GetClassDerivations(),
Mover::GetMessageHandlers(),
Mover::GetAttributeIndex(),
Mover::StateCount,
(Entity::MakeHandler)Mover::Make
);
//#############################################################################
// Message Support
//
#if 0
const Receiver::HandlerEntry
Mover::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(Mover, Update)
};
Entity::MessageHandlerSet
Mover::MessageHandlers(
ELEMENTS(Mover::MessageHandlerEntries),
Mover::MessageHandlerEntries,
Entity::GetMessageHandlers()
);
#endif
//#############################################################################
// Attribute Support
//
const Mover::IndexEntry
Mover::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Mover, LocalVelocity, localVelocity),
ATTRIBUTE_ENTRY(Mover, LocalAcceleration, localAcceleration),
ATTRIBUTE_ENTRY(Mover, WorldLinearVelocity, worldLinearVelocity),
ATTRIBUTE_ENTRY(Mover, WorldLinearAcceleration, worldLinearAcceleration),
ATTRIBUTE_ENTRY(Mover, MoverMass, moverMass),
ATTRIBUTE_ENTRY(Mover, MomentOfInertia, momentOfInertia),
ATTRIBUTE_ENTRY(
Mover,
PositiveLinearDragCoefficients,
positiveLinearDragCoefficients
),
ATTRIBUTE_ENTRY(
Mover,
NegativeLinearDragCoefficients,
negativeLinearDragCoefficients
),
ATTRIBUTE_ENTRY(Mover, AngularDragCoefficients, angularDragCoefficients),
ATTRIBUTE_ENTRY(Mover, FrictionCoefficient, frictionCoefficient),
ATTRIBUTE_ENTRY(Mover, ElasticityCoefficient, elasticityCoefficient),
ATTRIBUTE_ENTRY(Mover, MinimumBounceSpeed, minimumBounceSpeed)
};
Mover::AttributeIndexSet& Mover::GetAttributeIndex()
{
static Mover::AttributeIndexSet attributeIndex(ELEMENTS(Mover::AttributePointers),
Mover::AttributePointers,
Entity::GetAttributeIndex()
);
return attributeIndex;
}
//#############################################################################
// Model Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::UpdateWorldMotion()
{
Check(this);
//
//---------------------------------------------------
// Move the accelerations back into world coordinates
//---------------------------------------------------
//
worldLinearAcceleration.Multiply(
localAcceleration.linearMotion,
localToWorld
);
worldLinearVelocity.Multiply(
localVelocity.linearMotion,
localToWorld
);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::UpdateLocalMotion()
{
Check(this);
localVelocity.linearMotion.MultiplyByInverse(
worldLinearVelocity,
localToWorld
);
localAcceleration.linearMotion.MultiplyByInverse(
worldLinearAcceleration,
localToWorld
);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::ApplyWorldAccelerations(Scalar time_slice)
{
Check(this);
Verify(time_slice > 0.0f);
//
//--------------------------------------------------
// Calculate the new position as p += v*t + a*.5*t*t
//--------------------------------------------------
//
Scalar
half_t_squared = 0.5f * time_slice * time_slice;
Vector3D
position_delta;
position_delta.Multiply(worldLinearAcceleration, half_t_squared);
Check_Fpu();
position_delta.AddScaled(
position_delta,
worldLinearVelocity,
time_slice
);
Check_Fpu();
localOrigin.linearPosition.Add(localOrigin.linearPosition, position_delta);
Check_Fpu();
position_delta.Multiply(localAcceleration.angularMotion, half_t_squared);
Check_Fpu();
position_delta.AddScaled(
position_delta,
localVelocity.angularMotion,
time_slice
);
Check_Fpu();
Quaternion
old_position = localOrigin.angularPosition;
localOrigin.angularPosition.Add(old_position, position_delta);
Check_Fpu();
//
//-----------------------------------
// Calculate our velocity as v += a*t
//-----------------------------------
//
worldLinearVelocity.AddScaled(
worldLinearVelocity,
worldLinearAcceleration,
time_slice
);
Check_Fpu();
localVelocity.angularMotion.AddScaled(
localVelocity.angularMotion,
localAcceleration.angularMotion,
time_slice
);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::CalculateDrag(
Vector3D *drag,
const Vector3D &velocity,
const Vector3D &positive_CODs,
const Vector3D &negative_CODs,
Scalar power
)
{
Environment *air = GetEnvironment();
Check(air);
Vector3D temp,temp2;
temp.MultiplyByInverse(air->GetWindVelocity(), localToWorld);
temp += velocity;
if (temp.x < 0.0f)
{
drag->x = negative_CODs.x;
temp2.x = Power(-temp.x, power);
Check_Fpu();
}
else
{
drag->x = -positive_CODs.x;
temp2.x = Power(temp.x, power);
Check_Fpu();
}
if (temp.y < 0.0f)
{
drag->y = negative_CODs.y;
temp2.y = Power(-temp.y, power);
Check_Fpu();
}
else
{
drag->y = -positive_CODs.y;
temp2.y = Power(temp.y, power);
Check_Fpu();
}
if (temp.z < 0.0f)
{
drag->z = negative_CODs.z;
temp2.z = Power(-temp.z, power);
Check_Fpu();
}
else
{
drag->z = -positive_CODs.z;
temp2.z = Power(temp.z, power);
Check_Fpu();
}
*drag *= air->airDensity;
drag->Multiply(*drag, temp2);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::ApplyAirResistanceAndGravity(Scalar power)
{
Check(this);
//
//-------------------------------------------------------------------------
// Apply drag to the system, allowing for different drag numbers based upon
// the direction of motion along the axis
//-------------------------------------------------------------------------
//
Vector3D acceleration;
CalculateDrag(
&acceleration,
localVelocity.linearMotion,
positiveLinearDragCoefficients,
negativeLinearDragCoefficients,
power
);
localAcceleration.linearMotion += acceleration;
acceleration.Multiply(angularDragCoefficients, localVelocity.angularMotion);
localAcceleration.angularMotion -= acceleration;
//
//---------------------------
// Apply gravity to the craft
//---------------------------
//
UpdateWorldMotion();
worldLinearAcceleration.y -= GetEnvironment()->gravityConstant;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::ApplyLocalForce(
const Vector3D &force,
const Vector3D &moment
)
{
Check(this);
Check(&force);
Check(&moment);
Vector3D acceleration;
Verify(!Small_Enough(moverMass));
acceleration.Divide(force, moverMass);
ApplyLocalAcceleration(acceleration, moment);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::ApplyLocalAcceleration(
const Vector3D &acceleration,
const Vector3D &moment
)
{
Check(this);
Check(&acceleration);
Check(&moment);
localAcceleration.linearMotion += acceleration;
Vector3D torque;
torque.Cross(moment, acceleration);
torque *= momentOfInertia;
localAcceleration.angularMotion += torque;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
Mover::NoDeadReckoner()
{
Check(this);
//
//-------------------------------------------------------------------------
// If we are the replicant instance and we are not yet past the anticipated
// time for the next event, project out to the next event
//-------------------------------------------------------------------------
//
projectedOrigin = updateOrigin;
Check_Fpu();
return False;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
Mover::LinearDeadReckoner()
{
Check(this);
Logical lerp_mode;
Scalar time_slice;
//
//-------------------------------------------------------------------------
// If we are the replicant instance and we are not yet past the anticipated
// time for the next event, project out to the next event
//-------------------------------------------------------------------------
//
if (GetInstance() == ReplicantInstance && lastPerformance < nextUpdate)
{
time_slice = nextUpdate - lastUpdate;
lerp_mode = True;
}
else
{
time_slice = lastPerformance - lastUpdate;
lerp_mode = False;
}
//
//---------------------------------------
// Calculate the new position as p += v*t
//---------------------------------------
//
Vector3D position_delta;
position_delta.Multiply(updateVelocity.linearMotion, time_slice);
projectedOrigin.linearPosition.Add(
updateOrigin.linearPosition,
position_delta
);
//
//-------------------------------
// Handle projecting the rotation
//-------------------------------
//
position_delta.Multiply(updateVelocity.angularMotion, time_slice);
ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add)
updateOrigin.angularPosition, position_delta);
projectedVelocity = updateVelocity;
Check_Fpu();
return lerp_mode;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
Mover::AcceleratedDeadReckoner()
{
Check(this);
Logical lerp_mode;
Scalar time_slice;
//
//-------------------------------------------------------------------------
// If we are the replicant instance and we are not yet past the anticipated
// time for the next event, project out to the next event
//-------------------------------------------------------------------------
//
if (GetInstance() == ReplicantInstance && lastPerformance < nextUpdate)
{
time_slice = nextUpdate - lastUpdate;
lerp_mode = True;
}
else
{
time_slice = lastPerformance - lastUpdate;
lerp_mode = False;
}
//
//--------------------------------------------------
// Calculate the new position as p += v*t + a*.5*t*t
//--------------------------------------------------
//
Scalar half_t_squared = 0.5f * time_slice * time_slice;
Vector3D position_delta;
position_delta.Multiply(updateAcceleration.linearMotion, half_t_squared);
position_delta.AddScaled(
position_delta,
updateVelocity.linearMotion,
time_slice
);
projectedOrigin.linearPosition.Add(
updateOrigin.linearPosition,
position_delta
);
//
//-------------------------------
// Handle projecting the rotation
//-------------------------------
//
position_delta.Multiply(updateAcceleration.angularMotion, half_t_squared);
position_delta.AddScaled(
position_delta,
updateVelocity.angularMotion,
time_slice
);
ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add)
updateOrigin.angularPosition, position_delta);
//
//-----------------------------------
// Calculate our velocity as v += a*t
//-----------------------------------
//
if (GetInstance() == ReplicantInstance)
{
projectedVelocity.linearMotion.AddScaled(
updateVelocity.linearMotion,
worldLinearAcceleration,
time_slice
);
projectedVelocity.angularMotion.AddScaled(
updateVelocity.angularMotion,
localAcceleration.angularMotion,
time_slice
);
}
Check_Fpu();
return lerp_mode;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::DeadReckon(Scalar time_slice)
{
Check(this);
//
//------------------------------
// Run the chosen dead reckoning
//------------------------------
//
Verify(GetInstance() == ReplicantInstance);
if (deadReckoner)
{
//
//---------------------------------------------------------------------
// Merge the projected origin with the current origin if we are in lerp
// mode. If not, just copy the projected origin into the local origin
//---------------------------------------------------------------------
//
if ((this->*deadReckoner)())
{
Scalar percent =
time_slice / ((nextUpdate - lastPerformance) + time_slice);
//
//------------------------------------------
// Do a spherical lerp on the angular motion
//------------------------------------------
//
localOrigin.angularPosition.Lerp(
localOrigin.angularPosition,
projectedOrigin.angularPosition,
percent
);
localVelocity.angularMotion.Lerp(
localVelocity.angularMotion,
projectedVelocity.angularMotion,
percent
);
//
//-------------------------
// Spline the linear motion
//-------------------------
//
#if 0
CubicCurve
spline(
localOrigin.linearPosition,
worldLinearVelocity,
projectedOrigin.linearPosition,
projectedVelocity.linearMotion
);
spline.Evaluate(
percent,
&localOrigin.linearPosition,
&worldLinearVelocity
);
#else
localOrigin.linearPosition.Lerp(
localOrigin.linearPosition,
projectedOrigin.linearPosition,
percent
);
worldLinearVelocity.Lerp(
worldLinearVelocity,
projectedVelocity.linearMotion,
percent
);
#endif
}
else
{
localOrigin = projectedOrigin;
worldLinearVelocity = projectedVelocity.linearMotion;
localVelocity.angularMotion = projectedVelocity.angularMotion;
}
//
//----------------------------------------------------
// Update the collision volume and the local variables
//----------------------------------------------------
//
if (IsCollisionVolume())
{
MoveCollisionVolume();
}
else
{
localToWorld = localOrigin;
}
UpdateLocalMotion();
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::PerformAndWatch(
const Time &till,
MemoryStream *update_stream
)
{
Check(this);
Check(&till);
int i;
//
//--------------------------------------------------------------------------
// Make sure that the time into the simulations is stable. If a half-second
// delay occurs, or we are in stasis, just bring everything up to date
//--------------------------------------------------------------------------
//
Scalar time_slice = till - lastPerformance;
if (time_slice < SMALL)
{
Tell("No time!\n");
Bye_Bye:
WriteSimulationUpdate(update_stream);
return;
}
if (GetSimulationState() == StasisState || time_slice > 0.5f)
{
lastPerformance = till;
if (GetSimulationState() == StasisState)
{
lastUpdate = till;
}
if (subsystemArray)
{
Check_Pointer(subsystemArray);
for (i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Check(subsystemArray[i]);
subsystemArray[i]->SetLastPerformance(till);
}
}
}
//SetSimulationState(DefaultState);
goto Bye_Bye;
}
//
//------------------------------------
// Set up for local motion calculation
//------------------------------------
//
localVelocity.linearMotion.MultiplyByInverse(
worldLinearVelocity,
localToWorld
);
localAcceleration = Motion::Identity;
previousOrigin = localOrigin;
//
//-----------------------
// Process the subsystems
//-----------------------
//
Entity::PerformAndWatch(till, update_stream);
//
//-----------------------------------------------
// Make sure the position quaternion stays stable
//-----------------------------------------------
//
if (++normalizeCount == 20)
{
localOrigin.angularPosition.Normalize();
normalizeCount = 0;
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::ReadUpdateRecord(Simulation::UpdateRecord *record)
{
Check(this);
Check_Pointer(record);
switch (record->recordID)
{
case DefaultUpdateModelBit:
{
//
//-------------------------------------------
// HACK - precalculation for next update time
//-------------------------------------------
//
nextUpdate = Now();
Scalar diff = nextUpdate - lastUpdate;
if (diff < 10.0f)
{
nextUpdate.ticks += nextUpdate.ticks - lastUpdate.ticks;
}
//
//---------------------------------------
// Handle updating the entity information
//---------------------------------------
//
Entity::ReadUpdateRecord(record);
//
//-----------------------
// Update the motion data
//-----------------------
//
UpdateRecord *update = (UpdateRecord*)record;
localAcceleration = update->localAcceleration;
worldLinearAcceleration = update->worldLinearAcceleration;
updateVelocity.linearMotion = update->worldLinearVelocity;
updateVelocity.angularMotion = update->localVelocity.angularMotion;
updateAcceleration.linearMotion = update->worldLinearAcceleration;
updateAcceleration.angularMotion = update->localAcceleration.angularMotion;
//
//-----------------------------------------
// Update the collision volume if necessary
//-----------------------------------------
//
if (IsCollisionVolume())
{
MoveCollisionVolume();
}
}
break;
default:
Entity::ReadUpdateRecord(record);
break;
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::WriteUpdateRecord(
Simulation::UpdateRecord *record,
int update_model
)
{
Check(this);
Check_Pointer(record);
switch (update_model)
{
case DefaultUpdateModelBit:
{
Entity::WriteUpdateRecord(record, update_model);
UpdateRecord *update = (UpdateRecord*)record;
update->recordLength = sizeof(*update);
update->localVelocity = localVelocity;
update->localAcceleration = localAcceleration;
update->worldLinearVelocity = worldLinearVelocity;
update->worldLinearAcceleration = worldLinearAcceleration;
updateVelocity.linearMotion = worldLinearVelocity;
updateVelocity.angularMotion = localVelocity.angularMotion;
updateAcceleration.linearMotion = worldLinearAcceleration;
updateAcceleration.angularMotion = localAcceleration.angularMotion;
}
break;
default:
Entity::WriteUpdateRecord(record, update_model);
break;
}
Check_Fpu();
}
//#############################################################################
// Collision support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::MoveCollisionVolume()
{
//
//---------------------------------------------------
// Make sure that there is a collision volume to move
//---------------------------------------------------
//
Check(this);
if (!collisionVolumeCount)
{
Check_Fpu();
return;
}
//
//------------------------------------------------------------------------
// Set up the extents of the collision volume from the template and the
// current position. We must find the center point of the template volume
// and rotate it about the y axis
//------------------------------------------------------------------------
//
Check(collisionTemplate);
Check(collisionVolume);
Verify(collisionVolumeCount == 1);
Verify(collisionTemplate->solidType == BoxedSolid::YAxisCylinderType);
localToWorld = localOrigin;
Point3D centerPoint;
centerPoint.x = (collisionTemplate->minX + collisionTemplate->maxX) * 0.5f;
centerPoint.y = (collisionTemplate->minY + collisionTemplate->maxY) * 0.5f;
centerPoint.z = (collisionTemplate->minZ + collisionTemplate->maxZ) * 0.5f;
Vector3D radius;
radius.x = collisionTemplate->maxX - centerPoint.x;
radius.y = collisionTemplate->maxY - centerPoint.y;
radius.z = collisionTemplate->maxZ - centerPoint.z;
Point3D rotated;
rotated.Multiply(centerPoint, localToWorld);
collisionVolume->minX = rotated.x - radius.x;
collisionVolume->maxX = rotated.x + radius.x;
collisionVolume->minY = rotated.y - radius.y;
collisionVolume->maxY = rotated.y + radius.y;
collisionVolume->minZ = rotated.z - radius.z;
collisionVolume->maxZ = rotated.z + radius.z;
//
//------------------------------------------------------------
// Now, Find the smallest node containing our collision column
//------------------------------------------------------------
//
if (GetInstance() != ReplicantInstance)
{
InterestManager *interest_mgr =
application->GetInterestManager();
Check(interest_mgr);
InterestZone *zone =
interest_mgr->GetInterestZone(interestZoneID);
Check(zone);
BoxedSolidTree* tree = zone->GetCollisionRoot();
Check(tree);
containedByNode =
tree->FindSmallestNodeContainingColumn(*collisionVolume);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolidCollisionList*
Mover::AllocateCollisionList()
{
Check(this);
//
//-----------------------------------------------------------
// Find the correct collision list to use, and reset to empty
//-----------------------------------------------------------
//
BoxedSolidCollisionList *collision_list;
if (lastCollisionList == collisionLists)
{
collision_list = &collisionLists[1];
}
else
{
collision_list = collisionLists;
}
Check(collision_list);
collision_list->Reset();
return collision_list;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolidCollisionList*
Mover::GetCurrentCollisions(BoxedSolidCollisionList *collision_list)
{
Check(this);
if (!collision_list)
{
collision_list = AllocateCollisionList();
}
Check(collision_list);
//
//---------------------------------
// Test against the tangible movers
//---------------------------------
//
Check(collisionAssistant);
CollisionAssistant::MovingEntityIterator iterator(collisionAssistant);
Entity *entity;
Check(collisionVolume);
while ((entity = iterator.ReadAndNext()) != NULL)
{
//
//------------------------------------------------------------------
// If we are checking against ourselves, or something more than 50m
// away, skip it
//------------------------------------------------------------------
//
Check(entity);
if (entity == this)
{
continue;
}
Vector3D delta;
delta.Subtract(
entity->localOrigin.linearPosition,
localOrigin.linearPosition
);
if (delta.LengthSquared() > 2500.0f)
{
continue;
}
//
//--------------------------------------------------
// If we have a mover class object, check against it
//--------------------------------------------------
//
if (entity->IsDerivedFrom(*Mover::GetClassDerivations()))
{
Mover *mover = (Mover*)entity;
Check(mover);
Check(mover->collisionVolume);
CheckAgainstBoxedSolidChain(collision_list, mover->collisionVolume);
}
//
//-----------------------------------------------------------------------
// If we have a door, check against its subsystems if we are close enough
// for it to matter
//-----------------------------------------------------------------------
//
else if (entity->IsDerivedFrom(*DoorFrame::GetClassDerivations()))
{
DoorFrame *door_frame = (DoorFrame*)entity;
Check(door_frame);
for (int i=0; i<door_frame->GetSubsystemCount(); ++i)
{
Door *door = (Door*)door_frame->GetSubsystem(i);
CheckAgainstBoxedSolidChain(
collision_list,
door->GetFirstBoxedSolid()
);
}
}
}
//
//------------------------------
// Test against the static world
//------------------------------
//
containedByNode->FindBoundingBoxesContaining(
collisionVolume,
*collisionVolume,
*collision_list
);
Check_Fpu();
return collision_list;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolid*
Mover::FindBoxedSolidHitBy(
Line *line,
Entity *except
)
{
Check(this);
Check(line);
//
//-------------------------------------------------------------------------
// Calculate the midpoint of the line, and sweep a sphere out around the
// line from that point, including an extra 50 meters. This extra distance
// takes into account the doors...
//-------------------------------------------------------------------------
//
Point3D center;
Scalar radius = line->length * 0.5f;
line->Project(radius, &center);
//
//---------------------------------
// Test against the tangible movers
//---------------------------------
//
Check(collisionAssistant);
CollisionAssistant::MovingEntityIterator iterator(collisionAssistant);
Entity *entity;
BoxedSolid
*solid = NULL,
*result;
while ((entity = iterator.ReadAndNext()) != NULL)
{
//
//---------------------------------------------------------------
// If we are checking against ourselves or the exception, skip it
//---------------------------------------------------------------
//
Check(entity);
if (entity == this || except && except == entity)
{
continue;
}
//
//-------------------------------------------------------------------
// If we have a mover class object, check against it. If we have no
// collision volume, we are just using a line, so just run it against
// the collision volume chain
//-------------------------------------------------------------------
//
if (entity->IsDerivedFrom(*Mover::GetClassDerivations()))
{
Mover *mover = (Mover*)entity;
Check(mover);
Check(mover->collisionVolume);
//
//-----------------------------------------------------------------
// If the mover is close enough to the radius of the line, check it
//-----------------------------------------------------------------
//
Vector3D delta;
delta.Subtract(entity->localOrigin.linearPosition, center);
Scalar r2 =
mover->collisionVolume->maxX - mover->collisionVolume->minX;
r2 += mover->collisionVolume->maxY - mover->collisionVolume->minY;
r2 *= 0.5f;
r2 += radius;
if (delta.LengthSquared() > r2*r2)
{
continue;
}
result =
CheckLineAgainstBoxedSolidChain(line, mover->collisionVolume);
if (result)
{
Check(result);
solid = result;
}
}
//
//-----------------------------------------------------------------------
// If we have a door, check against its subsystems if we are close enough
// for it to matter
//-----------------------------------------------------------------------
//
else if (entity->IsDerivedFrom(*DoorFrame::GetClassDerivations()))
{
//
//-----------------------------------------------------------------
// If the mover is close enough to the radius of the line, check it
//-----------------------------------------------------------------
//
Vector3D delta;
delta.Subtract(entity->localOrigin.linearPosition, center);
Scalar r2 = radius + 50.0f;
if (delta.LengthSquared() > r2*r2)
{
continue;
}
DoorFrame *door_frame = (DoorFrame*)entity;
Check(door_frame);
for (int i=0; i<door_frame->GetSubsystemCount(); ++i)
{
Door *door = (Door*)door_frame->GetSubsystem(i);
result =
CheckLineAgainstBoxedSolidChain(
line,
door->GetFirstBoxedSolid()
);
if (result)
{
Check(result);
solid = result;
}
}
}
}
//
//------------------------------
// Test against the static world
//------------------------------
//
InterestManager *interest_mgr = application->GetInterestManager();
Check(interest_mgr);
InterestZone *zone = interest_mgr->GetInterestZone(interestZoneID);
Check(zone);
BoxedSolidTree* tree = zone->GetCollisionRoot();
Check(tree);
result = (BoxedSolid*)tree->FindBoundingBoxHitBy(line);
if (result)
{
Check(result);
solid = result;
}
Check_Fpu();
return solid;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolidCollisionList*
Mover::CollideCenterOfMotion(
Line *line,
BoxedSolidCollisionList *list
)
{
Check(this);
Check(line);
//
//-----------------------------------------------------------
// Find the correct collision list to use, and reset to empty
//-----------------------------------------------------------
//
if (!list)
{
list = AllocateCollisionList();
}
Check(list);
//
//------------------------------------------------------------------------
// If the length of the line has changed, we must reposition the collision
// volume appropriately
//------------------------------------------------------------------------
//
BoxedSolid *solid = FindBoxedSolidHitBy(line, NULL);
if (solid && IsCollisionVolume())
{
line->FindEnd(&localOrigin.linearPosition);
MoveCollisionVolume();
ExtentBox slice;
slice.Intersect(*collisionVolume, *solid);
Verify(list->GetCollisionsLeft());
list->AddCollisionToList(solid, slice);
}
Check_Fpu();
return list;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::ProcessCollisionList(
BoxedSolidCollisionList *collisions,
Scalar time_slice,
const Point3D &old_position,
Damage *damage
)
{
Check(this);
Check(collisions);
Verify(time_slice > 0.0f);
Check(&old_position);
Check_Pointer(damage);
damage->damageAmount = 0.0f;
damage->damageType = Damage::CollisionDamageType;
damage->impactPoint = Point3D::Identity;
if (collisions->GetCollisionCount())
{
//
//------------------------------------------------------------------
// Reduce the number of collisions we have to play with based on our
// velocity
//------------------------------------------------------------------
//
collisions->ReduceCollisionList(worldLinearVelocity);
//
//-----------------------------------------------------------------
// Setup up the totaling variables to handle averaging out multiple
// collisions
//-----------------------------------------------------------------
//
int total_collisions = 0;
Vector3D resultant_velocity = Vector3D::Identity;
Point3D resultant_position = Point3D::Identity;
Vector3D resultant_normal = Vector3D::Identity;
Vector3D initial_velocity = worldLinearVelocity;
Vector3D initial_position = localOrigin.linearPosition;
Scalar total_damage = 0.0f;
//
//---------------------------------------------------------------------
// For each hit in the list, process it, and if the collision is
// determined to be valid, bounce it and add the result into the others
//---------------------------------------------------------------------
//
for (int i=0; i<collisions->GetRealCollisions(); ++i)
{
//
//----------------------------------------------------------
// Make sure to bounce the vehicle from the correct location
//----------------------------------------------------------
//
worldLinearVelocity = initial_velocity;
localOrigin.linearPosition = initial_position;
damage->damageAmount = 0.0f;
ProcessCollision(
time_slice,
(*collisions)[i],
old_position,
damage
);
if (damage->damageAmount > 0.0f)
{
++total_collisions;
resultant_velocity += worldLinearVelocity;
resultant_position += localOrigin.linearPosition;
resultant_normal += damage->surfaceNormal;
total_damage += damage->damageAmount;
ExtentBox *box = &(*collisions)[i].collisionSlice;
damage->impactPoint.x += 0.5 *
(
box->minX + box->maxX
- (collisionVolume->minX - collisionVolume->maxX)
);
damage->impactPoint.y += 0.5 *
(
box->minY + box->maxY
- (collisionVolume->minY - collisionVolume->maxY)
);
damage->impactPoint.z += 0.5 *
(
box->minZ + box->maxZ
- (collisionVolume->minZ - collisionVolume->maxZ)
);
}
}
//
//-----------------------------------------------------------------
// If we collided with more than one thing, average out the results
//-----------------------------------------------------------------
//
if (total_collisions > 1)
{
worldLinearVelocity.Divide(resultant_velocity, total_collisions);
localOrigin.linearPosition.Divide(
resultant_position,
total_collisions
);
damage->surfaceNormal.Vector3D::Divide(
resultant_normal,
total_collisions
);
goto Figure_Normal;
}
//
//------------------------------------------------------
// Otherwise, just set up the positions from the results
//------------------------------------------------------
//
else if (total_collisions == 1)
{
worldLinearVelocity = resultant_velocity;
localOrigin.linearPosition = resultant_position;
damage->surfaceNormal.operator=(resultant_normal);
//
//---------------------------------------------------------
// Figure out the normal, and calculate the collision force
//---------------------------------------------------------
//
Figure_Normal:
if (Small_Enough(damage->surfaceNormal.LengthSquared()))
{
damage->surfaceNormal.x = 0.0f;
damage->surfaceNormal.y = 1.0f;
damage->surfaceNormal.z = 0.0f;
}
else
{
damage->surfaceNormal.Normalize(damage->surfaceNormal);
}
MoveCollisionVolume();
damage->damageAmount = total_damage;
damage->damageForce.Subtract(worldLinearVelocity, initial_velocity);
}
lastCollisionList = collisions;
}
else
{
lastCollisionList = NULL;
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::ProcessCollision(
Scalar time_slice,
BoxedSolidCollision &collision,
const Point3D &old_position,
Damage *damage
)
{
Check(this);
Verify(time_slice > 0.0f);
Check(&collision);
Check(&old_position);
Check_Pointer(damage);
Scalar penetration;
//
//------------------------------------------------------------------------
// If we really have a collision, do a static bounce off of the normal
// generated. This is default behavior, and any derived class should make
// sure to handle any handshaking that needs to be done
//------------------------------------------------------------------------
//
if (
collisionVolume->ProcessCollision(
collision,
worldLinearVelocity,
lastCollisionList,
&damage->surfaceNormal,
&penetration
)
)
{
Max_Clamp(penetration, time_slice);
Scalar r = penetration / time_slice;
Scalar elasticity = elasticityCoefficient;
Scalar friction = frictionCoefficient;
damage->damageAmount =
StaticBounce(
old_position,
time_slice,
r,
damage->surfaceNormal,
&elasticity,
minimumBounceSpeed,
&friction
);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::StartCollisionAssistant()
{
Check(this);
Verify(collisionAssistant == NULL);
collisionAssistant = CollisionAssistant::Make(this);
Register_Object(collisionAssistant);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
Mover::StaticBounce(
const Point3D &, //old_position,
Scalar delta_t,
Scalar penetration,
const Normal &normal,
Scalar *elasticity,
Scalar bounce_min,
Scalar *friction
)
{
Check(this);
Check(&normal);
Check_Pointer(elasticity);
Check_Pointer(friction);
Verify(penetration >= 0.0f && penetration <= 1.0f);
Verify(*elasticity >= 0.0f && *elasticity <= 1.0f);
Verify(*friction >= 0.0f);
Verify(delta_t > SMALL);
// penetration = 1.0f; // HACK - should keep stuff from going through the floor
//
//-----------------------------------------------------------------------
// Calculate the impact speed and vectors. If we didn't hit fast enough,
// don't do any bounce
//-----------------------------------------------------------------------
//
Scalar impact = worldLinearVelocity * normal;
Vector3D vn,vp;
vn.Multiply(normal, impact);
vp.Subtract(worldLinearVelocity, vn);
if (impact > 0.0f)
{
Check_Fpu();
return 0.0f;
}
if (-impact <= bounce_min * delta_t)
{
*elasticity = 0.0f;
}
//
//--------------------------------------
// Calculate the energy lost to friction
//--------------------------------------
//
Scalar resistance = vp.Length();
if (Small_Enough(resistance))
{
*friction = resistance = 0.0f;
}
else
{
resistance =
1.0f + *friction * (1.0f + *elasticity) * impact / resistance;
if (resistance < 0.0f)
{
*friction = resistance = 0.0f;
}
}
//
//----------------------------------------------------
// Compute the velocity delta created by the collision
//----------------------------------------------------
//
Scalar temp = resistance - 1.0f;
Vector3D delta_v;
delta_v.Multiply(worldLinearVelocity, temp);
temp = resistance + *elasticity;
delta_v.AddScaled(delta_v, vn, -temp);
//
//------------------------------------
// Figure out the kinetic energy stuff
//------------------------------------
//
temp = -1.0f - *elasticity;
vn *= temp;
vp.AddScaled(vn, worldLinearVelocity, 2.0f);
//
// Reflect the velocity vector
//
worldLinearVelocity += delta_v;
temp = penetration * delta_t;
delta_v *= temp;
localOrigin.linearPosition += delta_v;
//
// Compute the kinetic energy loss
//
Check_Fpu();
return -0.0005 * (vn * vp) * moverMass;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
Mover::DynamicBounce(
Mover *other,
Scalar delta_t,
Scalar penetration,
const Normal &normal,
Scalar *elasticity
)
{
Check(this);
Check(other);
Check(&normal);
Check_Pointer(elasticity);
Verify(penetration >= 0.0f && penetration <= 1.0f);
Verify(*elasticity >= 0.0f && *elasticity <= 1.0f);
Verify(delta_t > SMALL);
//
//------------------------------------------------------------------------
// Get the relative velocity of the other guy, and figure out the velocity
// delta along the normal
//------------------------------------------------------------------------
//
Scalar k1 = worldLinearVelocity.LengthSquared();
Scalar k2 = other->worldLinearVelocity.LengthSquared();
Scalar mass_ratio = other->moverMass / (moverMass + other->moverMass);
Check_Fpu();
Vector3D v;
v.Subtract(other->worldLinearVelocity, worldLinearVelocity);
Scalar temp = (1.0f + *elasticity) * (v*normal);
Vector3D delta_v;
delta_v.Multiply(normal, temp);
//
//-------------------------------------------------------------------------
// Figure out the kinetic energy loss in kilojoules, and bounce the primary
// mover
//
// There was an additional multiplication by mass ratio in system 3 code...
// we should make sure it is really needed...
//-------------------------------------------------------------------------
//
v.AddScaled(delta_v, v, -2.0f);
worldLinearVelocity.AddScaled(
worldLinearVelocity,
delta_v,
mass_ratio
);
localOrigin.linearPosition.AddScaled(
localOrigin.linearPosition,
delta_v,
delta_t * penetration
);
//
//----------------------------------------------------------
// Bounce the second object, and reset it's update values...
//----------------------------------------------------------
//
other->worldLinearVelocity.AddScaled(
other->worldLinearVelocity,
delta_v,
mass_ratio - 1.0f
);
other->localOrigin.linearPosition.AddScaled(
other->localOrigin.linearPosition,
delta_v,
delta_t
);
other->updateVelocity.linearMotion = other->worldLinearVelocity;
other->updateOrigin.linearPosition = other->localOrigin.linearPosition;
other->lastUpdate = Now();
//
//--------------------------------
// Return the result in kilojoules
//--------------------------------
//
k1 -= worldLinearVelocity.LengthSquared();
k2 -= other->worldLinearVelocity.LengthSquared();
Check_Fpu();
return 0.0005f * mass_ratio * (moverMass * k1 + other->moverMass * k2);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::CheckAgainstBoxedSolidChain(
BoxedSolidCollisionList *collisions,
BoxedSolid *chain
)
{
Check(this);
Check(collisions);
//
//----------------------------------------------------------------------
// If the two movers collided against with each other, add the result to
// the collision list
//----------------------------------------------------------------------
//
while (chain)
{
Check(chain);
ExtentBox slice;
if (chain->Intersects(*collisionVolume, &slice))
{
Verify(collisions->GetCollisionsLeft());
collisions->AddCollisionToList(chain, slice);
if (!collisions->GetCollisionsLeft())
{
return;
}
}
chain = chain->GetNextSolid();
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Mover::CheckVolumeAgainstBoxedSolidChain(
BoxedSolidCollisionList *collisions,
BoxedSolid *chain
)
{
Check(this);
Check(collisions);
//
//----------------------------------------------------------------------
// If the two movers collided against with each other, add the result to
// the collision list
//----------------------------------------------------------------------
//
while (chain)
{
Check(chain);
ExtentBox slice;
if (chain->Intersects(*collisionVolume, &slice))
{
Verify(collisions->GetCollisionsLeft());
collisions->AddCollisionToList(collisionVolume, slice);
if (!collisions->GetCollisionsLeft())
{
return;
}
}
chain = chain->GetNextSolid();
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolid*
Mover::CheckLineAgainstBoxedSolidChain(
Line *line,
BoxedSolid *chain
)
{
Check(this);
Check(line);
//
//----------------------------------------------------------------------
// If the two movers collided against with each other, add the result to
// the collision list
//----------------------------------------------------------------------
//
BoxedSolid *result = NULL;
while (chain)
{
Check(chain);
if (chain->HitBy(line))
{
result = chain;
}
chain = chain->GetNextSolid();
}
Check_Fpu();
return result;
}
//#############################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Mover::Mover(
Mover::MakeMessage *creation_message,
Mover::SharedData &virtual_data
):
Entity(creation_message, virtual_data)
{
Check_Pointer(this);
Check(creation_message);
Check(application);
ResourceFile *res_file = application->GetResourceFile();
Check(res_file);
//
//------------------------------
// Initialize the motion vectors
//------------------------------
//
localVelocity = creation_message->localVelocity;
localAcceleration = creation_message->localAcceleration;
worldLinearAcceleration.Multiply(
localAcceleration.linearMotion,
localToWorld
);
worldLinearVelocity.Multiply(
localVelocity.linearMotion,
localToWorld
);
updateVelocity.linearMotion = worldLinearVelocity;
updateVelocity.angularMotion = localVelocity.linearMotion;
updateAcceleration.linearMotion = worldLinearAcceleration;
updateAcceleration.angularMotion = localAcceleration.linearMotion;
nextUpdate = lastUpdate;
normalizeCount = 0;
if (IsInitialStasis())
{
SetSimulationState(StasisState);
}
collisionVolume = NULL;
collisionTemplate = NULL;
containedByNode = NULL;
collisionLists = NULL;
lastCollisionList = NULL;
collisionAssistant = NULL;
deadReckoner = NULL;
collisionVolumeCount = 0;
ResourceDescription *res =
res_file->SearchList(
resourceID,
ResourceDescription::GameModelResourceType
);
Check(res);
res->Lock();
ModelResource* model = (ModelResource*)res->resourceAddress;
Check_Pointer(model);
moverMass = model->moverMass;
Verify(!Small_Enough(model->momentOfInertia.x));
momentOfInertia.x = 1.0f/model->momentOfInertia.x;
Verify(!Small_Enough(model->momentOfInertia.y));
momentOfInertia.y = 1.0f/model->momentOfInertia.y;
Verify(!Small_Enough(model->momentOfInertia.z));
momentOfInertia.z = 1.0f/model->momentOfInertia.z;
positiveLinearDragCoefficients = model->positiveLinearDragCoefficients;
negativeLinearDragCoefficients = model->negativeLinearDragCoefficients;
angularDragCoefficients = model->angularDragCoefficients;
frictionCoefficient = model->frictionCoefficient;
elasticityCoefficient = model->elasticityCoefficient;
minimumBounceSpeed = model->minimumBounceSpeed;
//
//--------------------------------------------------------------------
// Read the collision information from the resource file, but for now,
// assume a VTV
//--------------------------------------------------------------------
//
collisionLists = new BoxedSolidCollisionList[2];
Register_Pointer(collisionLists);
res->Unlock();
if (IsCollisionVolume())
{
res =
res_file->SearchList(
resourceID,
ResourceDescription::BoxedSolidStreamResourceType
);
Check(res);
res->Lock();
BoxedSolidResource* box = (BoxedSolidResource*)res->resourceAddress;
Check_Pointer(box);
collisionVolumeCount = res->resourceSize / sizeof(BoxedSolidResource);
for (int i=0; i<collisionVolumeCount; ++i)
{
collisionTemplate =
BoxedSolid::MakeBoxedSolid(box, this, collisionTemplate);
Register_Object(collisionTemplate);
collisionVolume =
BoxedSolid::MakeBoxedSolid(box, this, collisionVolume);
Register_Object(collisionVolume);
++box;
}
res->Unlock();
MoveCollisionVolume();
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
Mover::CreateMakeMessage(
MakeMessage *creation_message,
NotationFile *model_file,
const ResourceDirectories *directories
)
{
Check(creation_message);
Check(model_file);
if (!Entity::CreateMakeMessage(creation_message, model_file, directories))
{
return False;
}
creation_message->messageLength = sizeof(Mover::MakeMessage);
creation_message->classToCreate = RegisteredClass::TrivialMoverClassID;
// creation_message->instanceFlags = DefaultFlags;
creation_message->localVelocity = Motion::Identity;
creation_message->localAcceleration = Motion::Identity;
Check_Fpu();
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription::ResourceID
Mover::CreateModelResource(
ResourceFile *resource_file,
const char* model_name,
NotationFile *model_file,
const ResourceDirectories *,//directories,
ModelResource *model
)
{
Check(resource_file);
Check_Pointer(model_name);
Check(model_file);
//
//-----------------------------------------------------------------------
// If we were not provided a buffer to write the model data into, we must
// create it ourselves
//-----------------------------------------------------------------------
//
ModelResource *local_model = model;
if (!local_model)
{
local_model = new ModelResource;
Register_Pointer(local_model);
}
//
//-----------------
// Read in the mass
//-----------------
//
if (!model_file->GetEntry("gamedata", "MoverMass", &local_model->moverMass))
{
std::cerr << model_name << " missing MoverMass!\n";
Dump_And_Die:
if (!model)
{
Unregister_Pointer(local_model);
delete local_model;
}
Check_Fpu();
return -1;
}
//
//------------------------------
// Read in the moment of inertia
//------------------------------
//
const char* entry;
if (
!model_file->GetEntry(
"gamedata",
"MomentOfInertia",
&entry
)
)
{
std::cerr << model_name << " missing MomentOfInertia!\n";
goto Dump_And_Die;
}
sscanf(
entry,
"%f %f %f",
&local_model->momentOfInertia.x,
&local_model->momentOfInertia.y,
&local_model->momentOfInertia.z
);
//
//------------------------------
// Read in the drag coefficients
//------------------------------
//
if (
!model_file->GetEntry(
"gamedata",
"PositiveLinearDragCoefficients",
&entry
)
)
{
std::cerr << model_name << " missing PositiveLinearDragCoefficients!\n";
goto Dump_And_Die;
}
sscanf(
entry,
"%f %f %f",
&local_model->positiveLinearDragCoefficients.x,
&local_model->positiveLinearDragCoefficients.y,
&local_model->positiveLinearDragCoefficients.z
);
if (
!model_file->GetEntry(
"gamedata",
"NegativeLinearDragCoefficients",
&entry
)
)
{
std::cerr << model_name << " missing NegativeLinearDragCoefficients!\n";
goto Dump_And_Die;
}
sscanf(
entry,
"%f %f %f",
&local_model->negativeLinearDragCoefficients.x,
&local_model->negativeLinearDragCoefficients.y,
&local_model->negativeLinearDragCoefficients.z
);
//
//-------------------------
// Read in the angular drag
//-------------------------
//
if (
!model_file->GetEntry(
"gamedata",
"AngularDragCoefficients",
&entry
)
)
{
std::cerr << model_name << " missing AngularDragCoefficients!\n";
goto Dump_And_Die;
}
sscanf(
entry,
"%f %f %f",
&local_model->angularDragCoefficients.x,
&local_model->angularDragCoefficients.y,
&local_model->angularDragCoefficients.z
);
//
//---------------------
// Read in the friction
//---------------------
//
if (
!model_file->GetEntry(
"gamedata",
"FrictionCoefficient",
&local_model->frictionCoefficient
)
)
{
std::cerr << model_name << " missing FrictionCoefficient!\n";
goto Dump_And_Die;
}
//
//-----------------------
// Read in the elasticity
//-----------------------
//
if (
!model_file->GetEntry(
"gamedata",
"ElasticityCoefficient",
&local_model->elasticityCoefficient
)
)
{
std::cerr << model_name << " missing ElasticityCoefficient!\n";
goto Dump_And_Die;
}
//
//---------------------------------
// Read in the minimum bounce speed
//---------------------------------
//
if (
!model_file->GetEntry(
"gamedata",
"MinimumBounceSpeed",
&local_model->minimumBounceSpeed
)
)
{
std::cerr << model_name << " missing MinimumBounceSpeed!\n";
goto Dump_And_Die;
}
//
//-------------------------------------------------------------------------
// If we created the model buffer, then we have the responsibility to write
// it out to the resource file
//-------------------------------------------------------------------------
//
if (!model)
{
ResourceDescription *new_res =
resource_file->AddResource(
model_name,
ResourceDescription::GameModelResourceType,
1,
ResourceDescription::Preload,
local_model,
sizeof(*local_model)
);
Unregister_Pointer(local_model);
delete local_model;
Check(new_res);
Check_Fpu();
return new_res->resourceID;
}
else
{
Check_Fpu();
return 0;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Mover*
Mover::Make(Mover::MakeMessage *creation_message)
{
return new Mover(creation_message, DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Mover::~Mover()
{
Unregister_Pointer(collisionLists);
delete[] collisionLists;
if (IsCollisionVolume())
{
BoxedSolid *box = collisionTemplate;
while (box)
{
BoxedSolid *next_box = box->GetNextSolid();
Unregister_Object(box);
delete box;
box = next_box;
}
box = collisionVolume;
while (box)
{
BoxedSolid *next_box = box->GetNextSolid();
Unregister_Object(box);
delete box;
box = next_box;
}
}
if (collisionAssistant)
{
Unregister_Object(collisionAssistant);
delete collisionAssistant;
}
Check_Fpu();
}
Logical
Mover::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}