The simulation advances in whole 50 Hz steps and the renderer draws whenever it can, so the drawn position only changed fifty times a second and was held for however many frames fell inside a step. That is visible as stepping, and it got WORSE the faster the machine: at 240 fps each position is held for nearly five frames, which is why a fast PC looked like a rabbit on crack while TARGETFPS=50 looked perfect. Matching the two rates hid it, but locking the frame rate to the physics rate throws away the entire point of having a fixed step. So drawing now blends. Entity keeps renderPreviousOrigin - its origin at the start of the step it is in, snapshotted by Mover::BeginStep - and renderStepFraction, how far through that step the frame falls, which is the leftover Entity::PerformAndWatch deliberately does not simulate. GetRenderToWorld blends the two with Origin::Lerp, which already did position and shortest-arc quaternion with normalisation. It is RENDER ONLY. localOrigin and localToWorld are untouched, so physics, collision, scoring, the nav map's queries and the network update records all still see exact stepped values. Three call sites. RootRenderable::Execute, which was the single place a vehicle's transform reached the matrix stack - the renderable already ran per frame and simply re-read a value that changed at the physics rate. The eye needed its gate widened as well: it rebuilt the view only when localToWorld CHANGED, so the world would have glided while the camera went on stepping and the judder would have moved rather than gone. And a teleport must stay a cut - that falls out free, because VTV::BeginStep applies a scheduled respawn and THEN calls Mover::BeginStep, so the snapshot lands post-teleport and the blend has nothing to travel. The picture trails the simulation by up to one step, 20 ms at 50 Hz. That is the standard price of interpolating rather than extrapolating, and much the lesser evil: guessing forward overshoots and shimmers every time the guess is corrected. RP412INTERP=0 turns it off so the stepping can be seen again without a rebuild. Determinism proved rather than asserted: a scripted lap (RP412INPUTSCRIPT, throttle and steering and pitch) at 240 fps with interpolation on and off, 90 PHYSTRACE samples over 22 seconds of driving, zero differ. Two earlier attempts at that comparison were invalid and both were my method - the first did not pin RP412SPAWNZONE so the runs began on different pads, and the second had no input script, so a joystick sitting on the desk drove the two runs differently. The template warns about the first of those in as many words. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2137 lines
52 KiB
C++
2137 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"
|
|
|
|
//#############################################################################
|
|
//############################### 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);
|
|
|
|
//
|
|
//------------------------------------------
|
|
// 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
|
|
//
|
|
// 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;
|
|
|
|
//
|
|
// The same snapshot again, for DRAWING. Deliberately not previousOrigin
|
|
// itself: that one belongs to the physics, and rendering must not be
|
|
// aliased to a member the simulation is free to repurpose.
|
|
//
|
|
// This runs AFTER a VTV's scheduled respawn has teleported localOrigin,
|
|
// because VTV::BeginStep applies the respawn and then calls us. So a
|
|
// respawn leaves the two equal and the blend has nothing to travel - a
|
|
// teleport stays a cut instead of becoming a 300-metre slide across the
|
|
// map. See entity.h.
|
|
//
|
|
renderPreviousOrigin = 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());
|
|
}
|