#include "munga.h" #pragma hdrstop #include "mover.h" #include "player.h" #include "boxsolid.h" #include "interest.h" #include "collasst.h" #include "doorfram.h" #include "door.h" #include "line.h" #include "app.h" #include "notation.h" #include // // EXACT axis-angle rotation composition -- matches the 1995 BT binary's angular // integrator (FUN_00409f58): build a unit rotation quaternion from the rotation // VECTOR `rotVec` (angle = |rotVec|, axis = rotVec/angle) as { axis*sin(angle/2), // cos(angle/2) } and Hamilton-multiply it onto `base`. The dead-reckoner previously // did `out.Add(base, rotVec)` -- adding a scaled angular-velocity vector to the heading // quaternion. That is only a small-angle approximation: fine per-frame (tiny angle), // but over a long replicant dead-reckon gap it DIVERGES (the heading drifts to ~180deg // then snaps -- the spinning-peer hesitation). This composition is exact for any angle // and stays on the unit sphere. // static void ExactAngularProject(Quaternion &out, const Quaternion &base, const Vector3D &rotVec) { const Scalar ang = rotVec.Length(); if (ang > 1.0e-6f) { const Scalar h = 0.5f * (Scalar)fmodf((float)ang, 6.2831853f); // half of angle mod 2pi const Scalar s = (Scalar)(sinf((float)h) / ang); // sin(angle/2)/angle const Quaternion dq(rotVec.x * s, rotVec.y * s, rotVec.z * s, (Scalar)cosf((float)h)); out.Multiply(base, dq); // base (X) dq } else { out = base; } } //############################################################################# //############################### Mover ################################# //############################################################################# //############################################################################# // Shared Data Support // Derivation* Mover::GetClassDerivations() { static Derivation classDerivations(Entity::GetClassDerivations(), "Mover"); return &classDerivations; } Mover::SharedData Mover::DefaultData( Mover::GetClassDerivations(), Mover::GetMessageHandlers(), Mover::GetAttributeIndex(), Mover::StateCount, (Entity::MakeHandler)Mover::Make ); //############################################################################# // Message Support // #if 0 const Receiver::HandlerEntry Mover::MessageHandlerEntries[]= { MESSAGE_ENTRY(Mover, Update) }; Entity::MessageHandlerSet Mover::MessageHandlers( ELEMENTS(Mover::MessageHandlerEntries), Mover::MessageHandlerEntries, Entity::GetMessageHandlers() ); #endif //############################################################################# // Attribute Support // const Mover::IndexEntry Mover::AttributePointers[]= { ATTRIBUTE_ENTRY(Mover, LocalVelocity, localVelocity), ATTRIBUTE_ENTRY(Mover, LocalAcceleration, localAcceleration), ATTRIBUTE_ENTRY(Mover, WorldLinearVelocity, worldLinearVelocity), ATTRIBUTE_ENTRY(Mover, WorldLinearAcceleration, worldLinearAcceleration), ATTRIBUTE_ENTRY(Mover, MoverMass, moverMass), ATTRIBUTE_ENTRY(Mover, MomentOfInertia, momentOfInertia), ATTRIBUTE_ENTRY( Mover, PositiveLinearDragCoefficients, positiveLinearDragCoefficients ), ATTRIBUTE_ENTRY( Mover, NegativeLinearDragCoefficients, negativeLinearDragCoefficients ), ATTRIBUTE_ENTRY(Mover, AngularDragCoefficients, angularDragCoefficients), ATTRIBUTE_ENTRY(Mover, FrictionCoefficient, frictionCoefficient), ATTRIBUTE_ENTRY(Mover, ElasticityCoefficient, elasticityCoefficient), ATTRIBUTE_ENTRY(Mover, MinimumBounceSpeed, minimumBounceSpeed) }; Mover::AttributeIndexSet& Mover::GetAttributeIndex() { static Mover::AttributeIndexSet attributeIndex(ELEMENTS(Mover::AttributePointers), Mover::AttributePointers, Entity::GetAttributeIndex() ); return attributeIndex; } //############################################################################# // Model Support // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::UpdateWorldMotion() { Check(this); // //--------------------------------------------------- // Move the accelerations back into world coordinates //--------------------------------------------------- // worldLinearAcceleration.Multiply( localAcceleration.linearMotion, localToWorld ); worldLinearVelocity.Multiply( localVelocity.linearMotion, localToWorld ); Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::UpdateLocalMotion() { Check(this); localVelocity.linearMotion.MultiplyByInverse( worldLinearVelocity, localToWorld ); localAcceleration.linearMotion.MultiplyByInverse( worldLinearAcceleration, localToWorld ); Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::ApplyWorldAccelerations(Scalar time_slice) { Check(this); Verify(time_slice > 0.0f); // //-------------------------------------------------- // Calculate the new position as p += v*t + a*.5*t*t //-------------------------------------------------- // Scalar half_t_squared = 0.5f * time_slice * time_slice; Vector3D position_delta; position_delta.Multiply(worldLinearAcceleration, half_t_squared); Check_Fpu(); position_delta.AddScaled( position_delta, worldLinearVelocity, time_slice ); Check_Fpu(); localOrigin.linearPosition.Add(localOrigin.linearPosition, position_delta); Check_Fpu(); position_delta.Multiply(localAcceleration.angularMotion, half_t_squared); Check_Fpu(); position_delta.AddScaled( position_delta, localVelocity.angularMotion, time_slice ); Check_Fpu(); Quaternion old_position = localOrigin.angularPosition; localOrigin.angularPosition.Add(old_position, position_delta); Check_Fpu(); // //----------------------------------- // Calculate our velocity as v += a*t //----------------------------------- // worldLinearVelocity.AddScaled( worldLinearVelocity, worldLinearAcceleration, time_slice ); Check_Fpu(); localVelocity.angularMotion.AddScaled( localVelocity.angularMotion, localAcceleration.angularMotion, time_slice ); Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::CalculateDrag( Vector3D *drag, const Vector3D &velocity, const Vector3D &positive_CODs, const Vector3D &negative_CODs, Scalar power ) { Environment *air = GetEnvironment(); Check(air); Vector3D temp,temp2; temp.MultiplyByInverse(air->GetWindVelocity(), localToWorld); temp += velocity; if (temp.x < 0.0f) { drag->x = negative_CODs.x; temp2.x = Power(-temp.x, power); Check_Fpu(); } else { drag->x = -positive_CODs.x; temp2.x = Power(temp.x, power); Check_Fpu(); } if (temp.y < 0.0f) { drag->y = negative_CODs.y; temp2.y = Power(-temp.y, power); Check_Fpu(); } else { drag->y = -positive_CODs.y; temp2.y = Power(temp.y, power); Check_Fpu(); } if (temp.z < 0.0f) { drag->z = negative_CODs.z; temp2.z = Power(-temp.z, power); Check_Fpu(); } else { drag->z = -positive_CODs.z; temp2.z = Power(temp.z, power); Check_Fpu(); } *drag *= air->airDensity; drag->Multiply(*drag, temp2); Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::ApplyAirResistanceAndGravity(Scalar power) { Check(this); // //------------------------------------------------------------------------- // Apply drag to the system, allowing for different drag numbers based upon // the direction of motion along the axis //------------------------------------------------------------------------- // Vector3D acceleration; CalculateDrag( &acceleration, localVelocity.linearMotion, positiveLinearDragCoefficients, negativeLinearDragCoefficients, power ); localAcceleration.linearMotion += acceleration; acceleration.Multiply(angularDragCoefficients, localVelocity.angularMotion); localAcceleration.angularMotion -= acceleration; // //--------------------------- // Apply gravity to the craft //--------------------------- // UpdateWorldMotion(); worldLinearAcceleration.y -= GetEnvironment()->gravityConstant; Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::ApplyLocalForce( const Vector3D &force, const Vector3D &moment ) { Check(this); Check(&force); Check(&moment); Vector3D acceleration; Verify(!Small_Enough(moverMass)); acceleration.Divide(force, moverMass); ApplyLocalAcceleration(acceleration, moment); Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::ApplyLocalAcceleration( const Vector3D &acceleration, const Vector3D &moment ) { Check(this); Check(&acceleration); Check(&moment); localAcceleration.linearMotion += acceleration; Vector3D torque; torque.Cross(moment, acceleration); torque *= momentOfInertia; localAcceleration.angularMotion += torque; Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Logical Mover::NoDeadReckoner() { Check(this); // //------------------------------------------------------------------------- // If we are the replicant instance and we are not yet past the anticipated // time for the next event, project out to the next event //------------------------------------------------------------------------- // projectedOrigin = updateOrigin; Check_Fpu(); return False; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Logical Mover::LinearDeadReckoner() { Check(this); Logical lerp_mode; Scalar time_slice; // //------------------------------------------------------------------------- // If we are the replicant instance and we are not yet past the anticipated // time for the next event, project out to the next event //------------------------------------------------------------------------- // if (GetInstance() == ReplicantInstance && lastPerformance < nextUpdate) { time_slice = nextUpdate - lastUpdate; lerp_mode = True; } else { time_slice = lastPerformance - lastUpdate; lerp_mode = False; } // //--------------------------------------- // Calculate the new position as p += v*t //--------------------------------------- // Vector3D position_delta; position_delta.Multiply(updateVelocity.linearMotion, time_slice); projectedOrigin.linearPosition.Add( updateOrigin.linearPosition, position_delta ); // //------------------------------- // Handle projecting the rotation //------------------------------- // position_delta.Multiply(updateVelocity.angularMotion, time_slice); ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add) updateOrigin.angularPosition, position_delta); projectedVelocity = updateVelocity; Check_Fpu(); return lerp_mode; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Logical Mover::AcceleratedDeadReckoner() { Check(this); Logical lerp_mode; Scalar time_slice; // //------------------------------------------------------------------------- // If we are the replicant instance and we are not yet past the anticipated // time for the next event, project out to the next event //------------------------------------------------------------------------- // if (GetInstance() == ReplicantInstance && lastPerformance < nextUpdate) { time_slice = nextUpdate - lastUpdate; lerp_mode = True; } else { time_slice = lastPerformance - lastUpdate; lerp_mode = False; } // //-------------------------------------------------- // Calculate the new position as p += v*t + a*.5*t*t //-------------------------------------------------- // Scalar half_t_squared = 0.5f * time_slice * time_slice; Vector3D position_delta; position_delta.Multiply(updateAcceleration.linearMotion, half_t_squared); position_delta.AddScaled( position_delta, updateVelocity.linearMotion, time_slice ); projectedOrigin.linearPosition.Add( updateOrigin.linearPosition, position_delta ); // //------------------------------- // Handle projecting the rotation //------------------------------- // position_delta.Multiply(updateAcceleration.angularMotion, half_t_squared); position_delta.AddScaled( position_delta, updateVelocity.angularMotion, time_slice ); ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add) updateOrigin.angularPosition, position_delta); // //----------------------------------- // Calculate our velocity as v += a*t //----------------------------------- // if (GetInstance() == ReplicantInstance) { projectedVelocity.linearMotion.AddScaled( updateVelocity.linearMotion, worldLinearAcceleration, time_slice ); projectedVelocity.angularMotion.AddScaled( updateVelocity.angularMotion, localAcceleration.angularMotion, time_slice ); } Check_Fpu(); return lerp_mode; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::DeadReckon(Scalar time_slice) { Check(this); // //------------------------------ // Run the chosen dead reckoning //------------------------------ // Verify(GetInstance() == ReplicantInstance); if (deadReckoner) { // //--------------------------------------------------------------------- // Merge the projected origin with the current origin if we are in lerp // mode. If not, just copy the projected origin into the local origin //--------------------------------------------------------------------- // if ((this->*deadReckoner)()) { Scalar percent = time_slice / ((nextUpdate - lastPerformance) + time_slice); // //------------------------------------------ // Do a spherical lerp on the angular motion //------------------------------------------ // localOrigin.angularPosition.Lerp( localOrigin.angularPosition, projectedOrigin.angularPosition, percent ); localVelocity.angularMotion.Lerp( localVelocity.angularMotion, projectedVelocity.angularMotion, percent ); // //------------------------- // Spline the linear motion //------------------------- // #if 0 CubicCurve spline( localOrigin.linearPosition, worldLinearVelocity, projectedOrigin.linearPosition, projectedVelocity.linearMotion ); spline.Evaluate( percent, &localOrigin.linearPosition, &worldLinearVelocity ); #else localOrigin.linearPosition.Lerp( localOrigin.linearPosition, projectedOrigin.linearPosition, percent ); worldLinearVelocity.Lerp( worldLinearVelocity, projectedVelocity.linearMotion, percent ); #endif } else { localOrigin = projectedOrigin; worldLinearVelocity = projectedVelocity.linearMotion; localVelocity.angularMotion = projectedVelocity.angularMotion; } // //---------------------------------------------------- // Update the collision volume and the local variables //---------------------------------------------------- // if (IsCollisionVolume()) { MoveCollisionVolume(); } else { localToWorld = localOrigin; } UpdateLocalMotion(); } Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::PerformAndWatch( const Time &till, MemoryStream *update_stream ) { Check(this); Check(&till); int i; // //-------------------------------------------------------------------------- // Make sure that the time into the simulations is stable. If a half-second // delay occurs, or we are in stasis, just bring everything up to date //-------------------------------------------------------------------------- // Scalar time_slice = till - lastPerformance; if (time_slice < SMALL) { Tell("No time!\n"); Bye_Bye: WriteSimulationUpdate(update_stream); return; } if (GetSimulationState() == StasisState || time_slice > 0.5f) { lastPerformance = till; if (GetSimulationState() == StasisState) { lastUpdate = till; } if (subsystemArray) { Check_Pointer(subsystemArray); for (i=0; iSetLastPerformance(till); } } } //SetSimulationState(DefaultState); goto Bye_Bye; } // //------------------------------------ // Set up for local motion calculation //------------------------------------ // localVelocity.linearMotion.MultiplyByInverse( worldLinearVelocity, localToWorld ); localAcceleration = Motion::Identity; previousOrigin = localOrigin; // //----------------------- // Process the subsystems //----------------------- // Entity::PerformAndWatch(till, update_stream); // //----------------------------------------------- // Make sure the position quaternion stays stable //----------------------------------------------- // if (++normalizeCount == 20) { localOrigin.angularPosition.Normalize(); normalizeCount = 0; } Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::ReadUpdateRecord(Simulation::UpdateRecord *record) { Check(this); Check_Pointer(record); switch (record->recordID) { case DefaultUpdateModelBit: { // //------------------------------------------- // HACK - precalculation for next update time //------------------------------------------- // nextUpdate = Now(); Scalar diff = nextUpdate - lastUpdate; if (diff < 10.0f) { nextUpdate.ticks += nextUpdate.ticks - lastUpdate.ticks; } // //--------------------------------------- // Handle updating the entity information //--------------------------------------- // Entity::ReadUpdateRecord(record); // //----------------------- // Update the motion data //----------------------- // UpdateRecord *update = (UpdateRecord*)record; localAcceleration = update->localAcceleration; worldLinearAcceleration = update->worldLinearAcceleration; updateVelocity.linearMotion = update->worldLinearVelocity; updateVelocity.angularMotion = update->localVelocity.angularMotion; updateAcceleration.linearMotion = update->worldLinearAcceleration; updateAcceleration.angularMotion = update->localAcceleration.angularMotion; // //----------------------------------------- // Update the collision volume if necessary //----------------------------------------- // if (IsCollisionVolume()) { MoveCollisionVolume(); } } break; default: Entity::ReadUpdateRecord(record); break; } Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::WriteUpdateRecord( Simulation::UpdateRecord *record, int update_model ) { Check(this); Check_Pointer(record); switch (update_model) { case DefaultUpdateModelBit: { Entity::WriteUpdateRecord(record, update_model); UpdateRecord *update = (UpdateRecord*)record; update->recordLength = sizeof(*update); update->localVelocity = localVelocity; update->localAcceleration = localAcceleration; update->worldLinearVelocity = worldLinearVelocity; update->worldLinearAcceleration = worldLinearAcceleration; updateVelocity.linearMotion = worldLinearVelocity; updateVelocity.angularMotion = localVelocity.angularMotion; updateAcceleration.linearMotion = worldLinearAcceleration; updateAcceleration.angularMotion = localAcceleration.angularMotion; } break; default: Entity::WriteUpdateRecord(record, update_model); break; } Check_Fpu(); } //############################################################################# // Collision support // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Mover::MoveCollisionVolume() { // //--------------------------------------------------- // Make sure that there is a collision volume to move //--------------------------------------------------- // Check(this); if (!collisionVolumeCount) { Check_Fpu(); return; } // //------------------------------------------------------------------------ // Set up the extents of the collision volume from the template and the // current position. We must find the center point of the template volume // and rotate it about the y axis //------------------------------------------------------------------------ // Check(collisionTemplate); Check(collisionVolume); Verify(collisionVolumeCount == 1); Verify(collisionTemplate->solidType == BoxedSolid::YAxisCylinderType); localToWorld = localOrigin; Point3D centerPoint; centerPoint.x = (collisionTemplate->minX + collisionTemplate->maxX) * 0.5f; centerPoint.y = (collisionTemplate->minY + collisionTemplate->maxY) * 0.5f; centerPoint.z = (collisionTemplate->minZ + collisionTemplate->maxZ) * 0.5f; Vector3D radius; radius.x = collisionTemplate->maxX - centerPoint.x; radius.y = collisionTemplate->maxY - centerPoint.y; radius.z = collisionTemplate->maxZ - centerPoint.z; Point3D rotated; rotated.Multiply(centerPoint, localToWorld); collisionVolume->minX = rotated.x - radius.x; collisionVolume->maxX = rotated.x + radius.x; collisionVolume->minY = rotated.y - radius.y; collisionVolume->maxY = rotated.y + radius.y; collisionVolume->minZ = rotated.z - radius.z; collisionVolume->maxZ = rotated.z + radius.z; // //------------------------------------------------------------ // Now, Find the smallest node containing our collision column //------------------------------------------------------------ // if (GetInstance() != ReplicantInstance) { InterestManager *interest_mgr = application->GetInterestManager(); Check(interest_mgr); InterestZone *zone = interest_mgr->GetInterestZone(interestZoneID); Check(zone); BoxedSolidTree* tree = zone->GetCollisionRoot(); Check(tree); containedByNode = tree->FindSmallestNodeContainingColumn(*collisionVolume); } Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BoxedSolidCollisionList* Mover::AllocateCollisionList() { Check(this); // //----------------------------------------------------------- // Find the correct collision list to use, and reset to empty //----------------------------------------------------------- // BoxedSolidCollisionList *collision_list; if (lastCollisionList == collisionLists) { collision_list = &collisionLists[1]; } else { collision_list = collisionLists; } Check(collision_list); collision_list->Reset(); return collision_list; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BoxedSolidCollisionList* Mover::GetCurrentCollisions(BoxedSolidCollisionList *collision_list) { Check(this); if (!collision_list) { collision_list = AllocateCollisionList(); } Check(collision_list); // //--------------------------------- // Test against the tangible movers //--------------------------------- // Check(collisionAssistant); CollisionAssistant::MovingEntityIterator iterator(collisionAssistant); Entity *entity; Check(collisionVolume); while ((entity = iterator.ReadAndNext()) != NULL) { // //------------------------------------------------------------------ // If we are checking against ourselves, or something more than 50m // away, skip it //------------------------------------------------------------------ // Check(entity); if (entity == this) { continue; } Vector3D delta; delta.Subtract( entity->localOrigin.linearPosition, localOrigin.linearPosition ); if (delta.LengthSquared() > 2500.0f) { continue; } // //-------------------------------------------------- // If we have a mover class object, check against it //-------------------------------------------------- // if (entity->IsDerivedFrom(*Mover::GetClassDerivations())) { Mover *mover = (Mover*)entity; Check(mover); Check(mover->collisionVolume); CheckAgainstBoxedSolidChain(collision_list, mover->collisionVolume); } // //----------------------------------------------------------------------- // If we have a door, check against its subsystems if we are close enough // for it to matter //----------------------------------------------------------------------- // else if (entity->IsDerivedFrom(*DoorFrame::GetClassDerivations())) { DoorFrame *door_frame = (DoorFrame*)entity; Check(door_frame); for (int i=0; iGetSubsystemCount(); ++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; iGetSubsystemCount(); ++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; iGetRealCollisions(); ++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; iUnlock(); 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()); }