Three previous attempts at "is the watched pod's motion uniform" all
sampled it from somewhere else - the camera's step grid, or an arriving
packet's timestamp - and two independent clocks alias against each other
whatever the game is doing. Those numbers could never separate a real
hitch from the measurement's own beat, and each of them cost a deploy to
find that out.
This one has a single frame of reference: consecutive steps of the entity
being asked about, taken inside Mover::DeadReckon, which IS a replicant's
own per-step performance.
It also reports the mechanism rather than only the symptom. percent is how
far each step moves toward the projected position:
percent = time_slice / ((nextUpdate - lastPerformance) + time_slice)
so it depends on nextUpdate being a decent guess at when the next packet
lands. A poor guess makes the fraction swing, and a swinging fraction is
uneven motion however clean the packets were. The trace reports its range,
how many steps blended rather than snapped, and spikes and stalls against
the entity's own running mean.
One entity only, the first replicant seen, because the counters are shared
and a full grid would blend into noise.
What confirms: spikes or stalls in the entity's OWN steps, or a percent
range that swings. Either is real, because there is no second clock here to
blame. What refutes: uniform steps and a steady percent - then a replicant's
motion is fine and the tick is in presentation, and the remaining suspects
are frame delivery and the map raster.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2237 lines
55 KiB
C++
2237 lines
55 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"
|
|
|
|
//
|
|
// The blend fraction the last dead-reckoned step used, and whether it
|
|
// blended at all rather than snapping. Only read by the RP412CAMLOG trace
|
|
// in Mover::DeadReckon, which needs them from the branch that computes
|
|
// them a few lines earlier.
|
|
//
|
|
static Logical gLastLerpUsed = False;
|
|
static Scalar gLastPercent = 0.0f;
|
|
|
|
//#############################################################################
|
|
//############################### 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);
|
|
projectedOrigin.angularPosition.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
|
|
);
|
|
projectedOrigin.angularPosition.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);
|
|
|
|
// for the RP412CAMLOG trace at the end of this function
|
|
gLastLerpUsed = True;
|
|
gLastPercent = percent;
|
|
|
|
//
|
|
//------------------------------------------
|
|
// 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
|
|
{
|
|
gLastLerpUsed = False; // snapped, not blended
|
|
localOrigin = projectedOrigin;
|
|
worldLinearVelocity = projectedVelocity.linearMotion;
|
|
localVelocity.angularMotion = projectedVelocity.angularMotion;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------
|
|
// Update the collision volume and the local variables
|
|
//----------------------------------------------------
|
|
//
|
|
if (IsCollisionVolume())
|
|
{
|
|
MoveCollisionVolume();
|
|
}
|
|
else
|
|
{
|
|
localToWorld = localOrigin;
|
|
}
|
|
UpdateLocalMotion();
|
|
|
|
//
|
|
// RP412CAMLOG: is a replicant's motion actually uniform?
|
|
//
|
|
// Measured HERE, in the replicant's own step, and nowhere else.
|
|
// Every previous attempt at this question sampled from another
|
|
// clock - the camera's step grid, or an arriving packet's
|
|
// timestamp - and two independent clocks alias against each other
|
|
// whatever the game is doing, so those numbers could never
|
|
// separate a real hitch from the measurement's own beat. This one
|
|
// has a single frame of reference: consecutive steps of the entity
|
|
// being asked about.
|
|
//
|
|
// percent is the whole mechanism above: it is how far this step
|
|
// moves toward the projected position, and it depends on
|
|
// nextUpdate being a decent guess at when the next packet lands.
|
|
// If that guess is poor the fraction swings, and swinging fraction
|
|
// is uneven motion no matter how clean the packets were.
|
|
//
|
|
// One entity only - the first replicant seen - because these
|
|
// counters are shared and a grid full of pods would blend into
|
|
// noise.
|
|
//
|
|
if (RPCameraLog())
|
|
{
|
|
static EntityID watched = EntityID::Null;
|
|
static Logical latched = False;
|
|
|
|
if (!latched)
|
|
{
|
|
latched = True;
|
|
watched = GetEntityID();
|
|
}
|
|
if (watched == GetEntityID())
|
|
{
|
|
static Scalar next_say = 0.0f;
|
|
static Point3D last_pos(0.0f, 0.0f, 0.0f);
|
|
static Logical have_last = False;
|
|
static int steps = 0;
|
|
static int spikes = 0;
|
|
static int stalls = 0;
|
|
static int lerped = 0;
|
|
static Scalar mean_step = 0.0f;
|
|
static Scalar min_percent = 1.0f;
|
|
static Scalar max_percent = 0.0f;
|
|
|
|
++steps;
|
|
if (have_last)
|
|
{
|
|
Vector3D moved;
|
|
moved.Subtract(localOrigin.linearPosition, last_pos);
|
|
Scalar distance = moved.Length();
|
|
|
|
if (distance > 50.0f)
|
|
{
|
|
mean_step = 0.0f; // respawn, not motion
|
|
}
|
|
else if (mean_step > 0.01f)
|
|
{
|
|
Scalar ratio = distance / mean_step;
|
|
if (ratio > 2.5f) { ++spikes; }
|
|
else if (ratio < 0.4f) { ++stalls; }
|
|
mean_step = mean_step * 0.9f + distance * 0.1f;
|
|
}
|
|
else
|
|
{
|
|
mean_step = distance;
|
|
}
|
|
}
|
|
last_pos = localOrigin.linearPosition;
|
|
have_last = True;
|
|
|
|
if ((Scalar) Now() >= next_say)
|
|
{
|
|
if (next_say > 0.0f)
|
|
{
|
|
DEBUG_STREAM << "CamLog: replicant motion - " << steps
|
|
<< " own steps, " << spikes << " spike(s), "
|
|
<< stalls << " stall(s), " << lerped
|
|
<< " lerped, percent " << min_percent << ".."
|
|
<< max_percent << ", mean step " << mean_step
|
|
<< "m\n" << std::flush;
|
|
}
|
|
next_say = ((Scalar) Now()) + 5.0f;
|
|
steps = 0;
|
|
spikes = 0;
|
|
stalls = 0;
|
|
lerped = 0;
|
|
min_percent = 1.0f;
|
|
max_percent = 0.0f;
|
|
}
|
|
if (gLastLerpUsed)
|
|
{
|
|
++lerped;
|
|
if (gLastPercent < min_percent) { min_percent = gLastPercent; }
|
|
if (gLastPercent > max_percent) { max_percent = gLastPercent; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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
|
|
//
|
|
// Frame-counting, so it only runs on the frame-coupled path - fixed
|
|
// steps do the same thing in BeginStep, counted in STEPS, because
|
|
// "every 20 frames" lands at a different point of the step sequence
|
|
// on every machine and rounding at different points is drift.
|
|
//-----------------------------------------------
|
|
//
|
|
if (Simulation::FixedStep() <= (Scalar) 0 && ++normalizeCount >= 20)
|
|
{
|
|
localOrigin.angularPosition.Normalize();
|
|
normalizeCount = 0;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// The per-STEP set-up. This is the same work Mover::PerformAndWatch does
|
|
// once per frame above - and once per frame is exactly wrong under fixed
|
|
// stepping: the thrusters ADD their forces into localAcceleration every
|
|
// step, so an accumulator cleared per frame carries step one's thrust
|
|
// into step two whenever a frame holds two steps. How many steps a frame
|
|
// holds depends on wall-clock jitter, which made identical runs diverge
|
|
// by a quarter of a metre while sitting still on the pad.
|
|
//
|
|
// Idempotent on purpose: the frame-level copy still runs first on every
|
|
// path, and repeating this at each step start is a recompute from
|
|
// current state, not an accumulation.
|
|
//
|
|
void
|
|
Mover::BeginStep()
|
|
{
|
|
Check(this);
|
|
|
|
localVelocity.linearMotion.MultiplyByInverse(
|
|
worldLinearVelocity,
|
|
localToWorld
|
|
);
|
|
localAcceleration = Motion::Identity;
|
|
previousOrigin = localOrigin;
|
|
|
|
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, ¢er);
|
|
|
|
//
|
|
//---------------------------------
|
|
// 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());
|
|
}
|