The drawn pod stalled about fifteen times in 293 frames while the
simulation stepped perfectly smoothly through the same window - and 15 of
293 is 5%, which is exactly the count of frames the earlier trace found
sitting at a render fraction of zero. That was the whole clue.
GetRenderToWorld tested the fraction to decide whether to interpolate at
all:
if (renderStepFraction <= 0 || !RenderInterpolationEnabled())
*out = localToWorld;
Drawing at fraction f means drawing at the start of the step plus f of it,
so f = 0 means the START of the step. localToWorld is its END. The two are
a whole step apart, about a metre at racing speed.
behind is a whole number of milliseconds against a 20ms step, so it lands
on exactly zero roughly one frame in twenty. On those frames the pod was
drawn a full step ahead of itself and snapped back on the next one. Three
times a second at 59fps, regular because the beat between frame rate and
step rate is regular, and worst when a pod crosses the view quickly -
which is the symptom as it was first described, and it took this long to
find because every simulation trace was right. Only the drawing was wrong.
Ask renderStepTaken instead, which is what the condition meant all along.
Interpolating at f = 0 is continuous with its neighbours: each frame
advances the drawn position by frame_time / step whether or not a step
boundary falls between the two, which is the entire point.
The same mistake was in DPLEyeRenderable's rebuild gate, using the
fraction as a proxy for whether interpolation was running. Same fix.
renderStepTaken is cleared where localOrigin is assigned outside the step
loop, so a stale snapshot is never blended from.
Render path only - localOrigin is untouched, so physics, collisions and
determinism are unaffected.
The foreign-eye rejection added in the previous build turned out not to be
the cause: it rejected between zero and three samples per window against
stall counts in the twenties. Keeping it, since sampling one viewpoint
against another was still wrong, but it was not this.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1868 lines
47 KiB
C++
1868 lines
47 KiB
C++
#include "munga.h"
|
|
#pragma hdrstop
|
|
|
|
#include "entity.h"
|
|
#include "interest.h"
|
|
#include "exptbl.h"
|
|
#include "hostmgr.h"
|
|
#include "player.h"
|
|
#include "audio.h"
|
|
#include "fileutil.h"
|
|
#include "registry.h"
|
|
#include "event.h"
|
|
#include "app.h"
|
|
#include "nttmgr.h"
|
|
#include "subsystm.h"
|
|
#include "namelist.h"
|
|
#include "notation.h"
|
|
|
|
#if defined(TRACE_EXECUTE_ENTITY)
|
|
static BitTrace Execute_Entity("Execute Entity");
|
|
#define SET_EXECUTE_ENTITY() Execute_Entity.Set()
|
|
#define CLEAR_EXECUTE_ENTITY() Execute_Entity.Clear()
|
|
#else
|
|
#define SET_EXECUTE_ENTITY()
|
|
#define CLEAR_EXECUTE_ENTITY()
|
|
#endif
|
|
|
|
#if defined(TRACE_PERFORM_ENTITY)
|
|
static BitTrace Perform_Entity("Perform Entity");
|
|
#define SET_PERFORM_ENTITY() Perform_Entity.Set()
|
|
#define CLEAR_PERFORM_ENTITY() Perform_Entity.Clear()
|
|
#else
|
|
#define SET_PERFORM_ENTITY()
|
|
#define CLEAR_PERFORM_ENTITY()
|
|
#endif
|
|
|
|
#if defined(TRACE_PERFORM_SUBSYSTEMS)
|
|
static BitTrace Perform_Subsystems("Perform Subsystems");
|
|
#define SET_PERFORM_SUBSYSTEMS() Perform_Subsystems.Set()
|
|
#define CLEAR_PERFORM_SUBSYSTEMS() Perform_Subsystems.Clear()
|
|
#else
|
|
#define SET_PERFORM_SUBSYSTEMS()
|
|
#define CLEAR_PERFORM_SUBSYSTEMS()
|
|
#endif
|
|
|
|
//#############################################################################
|
|
//############################## Entity #################################
|
|
//#############################################################################
|
|
|
|
//#############################################################################
|
|
// Virtual Data support
|
|
//
|
|
Derivation* Entity::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(Simulation::GetClassDerivations(), "Entity");
|
|
return &classDerivations;
|
|
}
|
|
|
|
Entity::SharedData
|
|
Entity::DefaultData(
|
|
Entity::GetClassDerivations(),
|
|
Entity::GetMessageHandlers(),
|
|
Entity::GetAttributeIndex(),
|
|
Entity::StateCount,
|
|
Entity::Make
|
|
);
|
|
|
|
//#############################################################################
|
|
// Message Support
|
|
//
|
|
const Receiver::HandlerEntry
|
|
Entity::MessageHandlerEntries[]=
|
|
{
|
|
{
|
|
Entity::MakeMessageID,
|
|
"Make",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
{
|
|
Entity::MakeReadyMessageID,
|
|
"MakeReady",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
{
|
|
Entity::RemakeEntityMessageID,
|
|
"RemakeEntity",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
{
|
|
Entity::RemakeReadyMessageID,
|
|
"RemakeReady",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
{
|
|
Entity::RemakeCompleteMessageID,
|
|
"RemakeComplete",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
{
|
|
Entity::RemakeIncompleteMessageID,
|
|
"RemakeIncomplete",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
MESSAGE_ENTRY(Entity, DestroyEntity),
|
|
{
|
|
Entity::TransferEntityMessageID,
|
|
"TransferEntity",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
{
|
|
Entity::TransferCompleteMessageID,
|
|
"TransferComplete",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
{
|
|
Entity::TransferIncompleteMessageID,
|
|
"TransferIncomplete",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
MESSAGE_ENTRY(Entity, BecomeInteresting),
|
|
MESSAGE_ENTRY(Entity, BecomeUninteresting),
|
|
MESSAGE_ENTRY(Entity, Update),
|
|
{
|
|
Entity::SubscribeReplicantMessageID,
|
|
"SubscribeReplicant",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
{
|
|
Entity::UnsubscribeReplicantMessageID,
|
|
"UnsubscribeReplicant",
|
|
(Entity::Handler)&Entity::DefaultMessageHandler
|
|
},
|
|
MESSAGE_ENTRY(Entity, TakeDamage),
|
|
MESSAGE_ENTRY(Entity, PlayerLink),
|
|
MESSAGE_ENTRY(Entity, TakeDamageStream)
|
|
};
|
|
|
|
Entity::MessageHandlerSet& Entity::GetMessageHandlers()
|
|
{
|
|
static Entity::MessageHandlerSet messageHandlers(ELEMENTS(Entity::MessageHandlerEntries), Entity::MessageHandlerEntries, Receiver::GetMessageHandlers());
|
|
return messageHandlers;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::Receive(Event* event)
|
|
{
|
|
Check(this);
|
|
Check(event);
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// If the entity is being transferred, post the message to the new owner of
|
|
// the entity
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If the object is not valid, filter the message to determine which ones to
|
|
// defer and which ones to immediately process
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (!IsValid())
|
|
{
|
|
//switch case commented out by Ryan Bunker on 1/6/07
|
|
//switch (event->messageToSend->messageID)
|
|
{
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// The messages which should be allowed through should just break out of
|
|
// this switch
|
|
//----------------------------------------------------------------------
|
|
//
|
|
|
|
//default:
|
|
//
|
|
//-------------------
|
|
// Defer the messages
|
|
//-------------------
|
|
//
|
|
event->Defer();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------
|
|
// Go ahead and dispatch the message to the entity
|
|
//------------------------------------------------
|
|
//
|
|
Receiver::Receive(event);
|
|
|
|
#if defined(USE_FPU_ANALYSIS)
|
|
if (!Fpu_Ok())
|
|
{
|
|
Registry *registry = application->GetRegistry();
|
|
Receiver::SharedData *shared_data =
|
|
registry->GetStaticData(GetClassID());
|
|
Derivation *derivation = shared_data->derivedClasses;
|
|
Receiver::MessageHandlerSet *handlers =
|
|
shared_data->activeMessageHandlers;
|
|
DEBUG_STREAM << derivation->className << ',' << GetInstance() << endl << std::flush;
|
|
DEBUG_STREAM << handlers->GetName(event->messageToSend->messageID)
|
|
<< endl;
|
|
Fail("Event corrupted FPU!");
|
|
}
|
|
#else
|
|
Check_Fpu();
|
|
#endif
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::Dispatch(Receiver::Message* incoming)
|
|
{
|
|
Message *message = (Message*) incoming;
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
// Call Receiver if it is not entity aware!!!!!
|
|
//
|
|
if (message->messageID < Receiver::NextMessageID)
|
|
{
|
|
Receiver::Dispatch(message);
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------
|
|
// Initialize the entity information fields
|
|
//-----------------------------------------
|
|
//
|
|
message->entityID = entityID;
|
|
message->interestZoneID = interestZoneID;
|
|
|
|
//
|
|
//-------------------------------------------------------------------
|
|
// If this is a replicant instance, reroute the message to the master
|
|
//-------------------------------------------------------------------
|
|
//
|
|
if (GetInstance() == ReplicantInstance)
|
|
{
|
|
application->SendMessage(
|
|
ownerID,
|
|
EntityManager::EntityManagerClientID,
|
|
message
|
|
);
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------
|
|
// If this entity is invalid, post the message as an event
|
|
//--------------------------------------------------------
|
|
//
|
|
else if (!IsValid())
|
|
{
|
|
// ECH 1/28/96 - application->Post(DefaultEventPriority, this, message);
|
|
application->Post(EntityInvalidEventPriority, this, message);
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------
|
|
// Otherwise, go ahead and receive the message
|
|
//--------------------------------------------
|
|
//
|
|
else
|
|
{
|
|
Receiver::Receive(message);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::DispatchToReplicants(Message *what)
|
|
{
|
|
Check(this);
|
|
Check(what);
|
|
|
|
what->entityID = entityID;
|
|
what->interestZoneID = interestZoneID;
|
|
Check(application);
|
|
InterestManager *int_mgr = application->GetInterestManager();
|
|
Check(int_mgr);
|
|
int_mgr->EntityBroadcastToReplicants(this, what);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// Attribute Support
|
|
//
|
|
const Entity::IndexEntry
|
|
Entity::AttributePointers[]=
|
|
{
|
|
ATTRIBUTE_ENTRY(Entity, LocalToWorld, localToWorld),
|
|
ATTRIBUTE_ENTRY(Entity, LocalOrigin, localOrigin),
|
|
ATTRIBUTE_ENTRY(Entity, DamageZoneCount, damageZoneCount),
|
|
ATTRIBUTE_ENTRY(Entity, DamageZones, damageZones)
|
|
};
|
|
|
|
Entity::AttributeIndexSet& Entity::GetAttributeIndex()
|
|
{
|
|
static Entity::AttributeIndexSet attributeIndex(ELEMENTS(Entity::AttributePointers),
|
|
Entity::AttributePointers,
|
|
Simulation::GetAttributeIndex()
|
|
);
|
|
return attributeIndex;
|
|
}
|
|
|
|
//#############################################################################
|
|
// Model Support
|
|
//
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::WriteUpdateRecord(
|
|
Simulation::UpdateRecord *record,
|
|
int update_model
|
|
)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(record);
|
|
|
|
switch (update_model)
|
|
{
|
|
case DefaultUpdateModelBit:
|
|
{
|
|
Simulation::WriteUpdateRecord(record, update_model);
|
|
|
|
//
|
|
//----------------------------
|
|
// Write out the record update
|
|
//----------------------------
|
|
//
|
|
UpdateRecord *update = (UpdateRecord*)record;
|
|
update->localOrigin = localOrigin;
|
|
updateOrigin = localOrigin;
|
|
update->recordLength = sizeof(*update);
|
|
}
|
|
break;
|
|
case DamageZoneUpdateModelBit:
|
|
record->timeStamp = lastPerformance;
|
|
record->recordID = (Word)update_model;
|
|
WriteDamageUpdateRecord(record);
|
|
break;
|
|
|
|
default:
|
|
Simulation::WriteUpdateRecord(record, update_model);
|
|
break;
|
|
}
|
|
record->subsystemID = (Word)(EntitySubsystemID + 1);
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::UpdateMessageHandler(UpdateMessage *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
//------------------------------------------------------
|
|
// Position to the beginning of the update record blocks
|
|
//------------------------------------------------------
|
|
//
|
|
MemoryStream stream(message, message->messageLength, sizeof(*message));
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Step through each block until there are no more remaining, and send the
|
|
// update out the the simulation indicated by the subsystemID
|
|
//
|
|
// This is the only point on the receive path that knows WHOSE update
|
|
// this is - the records themselves carry a timestamp but not an owner -
|
|
// so the sender is published here for the net clock to align against.
|
|
// Every record in the message, and the damage zones nested inside them,
|
|
// came from the same machine in the same frame.
|
|
//------------------------------------------------------------------------
|
|
//
|
|
//
|
|
// Only for an entity somebody else owns. Our own clock needs no
|
|
// aligning, and an update we somehow handed ourselves would otherwise
|
|
// drag lastUpdate back by a frame for no reason.
|
|
//
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
Logical remote_owner =
|
|
GetOwnerID() != application->GetHostManager()->GetLocalHostID();
|
|
if (remote_owner)
|
|
{
|
|
NetClock_BeginUpdate(GetOwnerID());
|
|
}
|
|
|
|
while (stream.GetBytesRemaining())
|
|
{
|
|
Simulation::UpdateRecord *update =
|
|
(Simulation::UpdateRecord*)stream.GetPointer();
|
|
Check_Pointer(update);
|
|
Simulation *simulation = GetSimulation((int)update->subsystemID-1);
|
|
Check(simulation);
|
|
simulation->ReadUpdateRecord(update);
|
|
stream.AdvancePointer(update->recordLength);
|
|
}
|
|
|
|
if (remote_owner)
|
|
{
|
|
NetClock_EndUpdate();
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::ReadUpdateRecord(Simulation::UpdateRecord *record)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(record);
|
|
|
|
switch(record->recordID)
|
|
{
|
|
case DefaultUpdateModelBit:
|
|
{
|
|
//
|
|
//-----------------------
|
|
// Read the record update
|
|
//-----------------------
|
|
//
|
|
UpdateRecord *update = (UpdateRecord*)record;
|
|
|
|
//
|
|
// RP412CAMLOG: how hard does an arriving update MOVE this
|
|
// entity, and how often?
|
|
//
|
|
// Between updates a replicant is dead-reckoned from
|
|
// updateOrigin over (lastPerformance - lastUpdate). When the
|
|
// next one lands the basis is replaced, so the drawn position
|
|
// jumps by however far the prediction had drifted. Render
|
|
// interpolation cannot hide that: it smooths within a step,
|
|
// and this is a discontinuity in the stepped values
|
|
// themselves. If the interval is a few per second and the
|
|
// correction is tens of centimetres, that is a tick a few
|
|
// times a second - which is the symptom being chased.
|
|
//
|
|
// Reported per entity, on a five second clock, so a busy race
|
|
// does not bury the log.
|
|
//
|
|
if (RPCameraLog() && GetInstance() == ReplicantInstance)
|
|
{
|
|
static Scalar next_say = 0.0f;
|
|
static int corrections = 0;
|
|
static Scalar worst = 0.0f;
|
|
static Scalar total = 0.0f;
|
|
|
|
Vector3D drift;
|
|
drift.Subtract(
|
|
update->localOrigin.linearPosition,
|
|
localOrigin.linearPosition
|
|
);
|
|
Scalar distance = drift.Length();
|
|
++corrections;
|
|
total += distance;
|
|
if (distance > worst)
|
|
{
|
|
worst = distance;
|
|
}
|
|
|
|
if ((Scalar) Now() >= next_say)
|
|
{
|
|
if (next_say > 0.0f && corrections > 0)
|
|
{
|
|
DEBUG_STREAM << "CamLog: replicant corrections - "
|
|
<< corrections << " in 5s ("
|
|
<< (corrections / 5) << "/s), mean "
|
|
<< (total / corrections) << "m, worst "
|
|
<< worst << "m\n" << std::flush;
|
|
}
|
|
next_say = ((Scalar) Now()) + 5.0f;
|
|
corrections = 0;
|
|
worst = 0.0f;
|
|
total = 0.0f;
|
|
}
|
|
}
|
|
|
|
updateOrigin = update->localOrigin;
|
|
|
|
//
|
|
//------------------------------------------------------------------
|
|
// If we aren't able to run yet, we must update local origin as well
|
|
//------------------------------------------------------------------
|
|
//
|
|
Check(application);
|
|
int app_state = application->GetApplicationState();
|
|
if (
|
|
app_state < Application::RunningMission
|
|
|| app_state == Application::CreatingMission
|
|
)
|
|
{
|
|
localOrigin = updateOrigin;
|
|
localToWorld = localOrigin;
|
|
|
|
//
|
|
// Set outside the step loop, so renderPreviousOrigin now
|
|
// describes a step that never happened. Draw plainly until
|
|
// a real one does.
|
|
//
|
|
renderStepTaken = False;
|
|
}
|
|
Simulation::ReadUpdateRecord(record);
|
|
}
|
|
break;
|
|
case DamageZoneUpdateModelBit:
|
|
ReadDamageUpdateRecord(record);
|
|
break;
|
|
default:
|
|
Simulation::ReadUpdateRecord(record);
|
|
}
|
|
|
|
|
|
|
|
Check_Fpu();
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::ReadDamageUpdateRecord(Simulation::UpdateRecord *update_record)
|
|
{
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Step through each block until there are no more remaining, and send the
|
|
// update out the the damageZone indicated by the damageZoneIndex
|
|
//------------------------------------------------------------------------
|
|
//
|
|
MemoryStream stream(
|
|
update_record,
|
|
update_record->recordLength,
|
|
sizeof(*update_record)
|
|
);
|
|
|
|
while (stream.GetBytesRemaining())
|
|
{
|
|
DamageZone::UpdateRecord *damage_zone_record =
|
|
(DamageZone::UpdateRecord*)stream.GetPointer();
|
|
int damage_zone_index = damage_zone_record->damageZoneIndex;
|
|
damageZones[damage_zone_index]->ReadUpdateRecord(damage_zone_record);
|
|
stream.AdvancePointer(damage_zone_record->recordLength);
|
|
}
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
#define MAX_UPDATE_LENGTH 1400
|
|
|
|
static long Update_Buffer[MAX_UPDATE_LENGTH >> 2];
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::WriteDamageUpdateRecord(Simulation::UpdateRecord *update_record)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(update_record);
|
|
|
|
//
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Make a memory stream this size of a network packet
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
MemoryStream stream(
|
|
update_record,
|
|
sizeof(Update_Buffer) - ((char*)update_record - (char*)Update_Buffer),
|
|
sizeof(*update_record)
|
|
);
|
|
DamageZone::UpdateRecord *record_start =
|
|
(DamageZone::UpdateRecord*)stream.GetPointer();
|
|
|
|
//
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Update Every DamageZone For NOW
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
for(int ii=0;ii<damageZoneCount;++ii)
|
|
{
|
|
DamageZone::UpdateRecord *damage_zone_update =
|
|
(DamageZone::UpdateRecord*)stream.GetPointer();
|
|
damageZones[ii]->WriteUpdateRecord(damage_zone_update);
|
|
stream.AdvancePointer(damage_zone_update->recordLength);
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------------
|
|
// Check to see if an update message is necessary
|
|
//-----------------------------------------------
|
|
//
|
|
if (stream.GetPointer() != record_start)
|
|
{
|
|
Simulation::UpdateRecord *damage_update =
|
|
(Simulation::UpdateRecord*)update_record;
|
|
damage_update->recordLength =
|
|
(char*)stream.GetPointer() - (char*)update_record;
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Entity::UpdateMessage*
|
|
Entity::Execute(const Time& till)
|
|
{
|
|
SET_EXECUTE_ENTITY();
|
|
Check(this);
|
|
Check(&till);
|
|
|
|
MemoryStream
|
|
stream(Update_Buffer, sizeof(Update_Buffer), sizeof(UpdateMessage));
|
|
char *record_start = (char*)stream.GetPointer();
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Check to see if the application will let us run. If We are starting a
|
|
// mission, only the player class objects can execute
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
Check(application);
|
|
if (
|
|
application->GetApplicationState() == Application::RunningMission
|
|
|| application->GetApplicationState() == Application::EndingMission
|
|
|| IsPreRunnable()
|
|
)
|
|
{
|
|
PerformAndWatch(till, &stream);
|
|
}
|
|
else
|
|
{
|
|
WriteSimulationUpdate(&stream);
|
|
}
|
|
|
|
#if defined(USE_FPU_ANALYSIS)
|
|
if (!Fpu_Ok())
|
|
{
|
|
Registry *registry = application->GetRegistry();
|
|
Receiver::SharedData *shared_data =
|
|
registry->GetStaticData(GetClassID());
|
|
Derivation *derivation = shared_data->derivedClasses;
|
|
DEBUG_STREAM << derivation->className << ',' << GetInstance() << endl << std::flush;
|
|
Fail("Simulation corrupted FPU!");
|
|
}
|
|
#endif
|
|
|
|
//
|
|
//-----------------------------------------------
|
|
// Check to see if an update message is necessary
|
|
//-----------------------------------------------
|
|
//
|
|
if (stream.GetPointer() != record_start)
|
|
{
|
|
UpdateMessage *update = (UpdateMessage*)Update_Buffer;
|
|
update->messageID = UpdateMessageID;
|
|
update->messageLength = (char*)stream.GetPointer() - (char*)Update_Buffer;
|
|
update->messageFlags = 0;
|
|
CLEAR_EXECUTE_ENTITY();
|
|
return update;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------
|
|
// Otherwise, return NULL for no update
|
|
//-------------------------------------
|
|
//
|
|
Check_Fpu();
|
|
CLEAR_EXECUTE_ENTITY();
|
|
return NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Simulation*
|
|
Entity::GetSimulation(int index)
|
|
{
|
|
Check(this);
|
|
|
|
if (index == EntitySubsystemID)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
Check_Fpu();
|
|
if ((unsigned)index < subsystemCount)
|
|
{
|
|
return subsystemArray[index];
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Subsystem*
|
|
Entity::FindSubsystem(const char* name)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(name);
|
|
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i])
|
|
{
|
|
Check(subsystemArray[i]);
|
|
if (!stricmp(subsystemArray[i]->GetName(), name))
|
|
{
|
|
return subsystemArray[i];
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
return NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
#define MAX_SUB_FILE_NAME (256)
|
|
|
|
int
|
|
Entity::FindSubsytemID
|
|
(
|
|
#if DEBUG_LEVEL>0
|
|
const char *model_name,
|
|
const char *subsystem_name
|
|
#else
|
|
const char *,
|
|
const char *subsystem_name
|
|
#endif
|
|
)
|
|
{
|
|
Check_Pointer(model_name);
|
|
Check_Pointer(subsystem_name);
|
|
|
|
int subsytem_id = EntitySubsystemID;
|
|
int sub_count = 0;
|
|
|
|
CString sub_file_string;
|
|
CString sub_directory_string("models\\");
|
|
CString model_name_string("model_name");
|
|
CString sub_post_string(".sub");
|
|
|
|
sub_file_string =
|
|
sub_directory_string + model_name_string + sub_post_string;
|
|
|
|
NotationFile sub_file(sub_file_string);
|
|
Check(&sub_file);
|
|
|
|
NameList *page_list = sub_file.MakePageList();
|
|
Register_Object(page_list);
|
|
NameList::Entry *entry=page_list->GetFirstEntry();
|
|
while (entry != NULL)
|
|
{
|
|
Check(entry);
|
|
Check_Pointer(entry->GetName());
|
|
if (!stricmp(subsystem_name, entry->GetName()))
|
|
{
|
|
subsytem_id = sub_count;
|
|
break;
|
|
}
|
|
|
|
++sub_count;
|
|
entry = entry->GetNextEntry();
|
|
}
|
|
|
|
Unregister_Object(page_list);
|
|
delete page_list;
|
|
|
|
return subsytem_id;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::PerformAndWatch(
|
|
const Time &till,
|
|
MemoryStream *update_stream
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(&till);
|
|
|
|
//
|
|
//-----------------------------------------
|
|
// Execute all the subsystems, then ourself
|
|
//-----------------------------------------
|
|
//
|
|
SET_PERFORM_SUBSYSTEMS();
|
|
if (subsystemArray)
|
|
{
|
|
Check_Pointer(subsystemArray);
|
|
|
|
//
|
|
//---------------------------------------------------
|
|
// If this is not a replicant, execute all subsystems
|
|
//---------------------------------------------------
|
|
//
|
|
if (GetInstance() != ReplicantInstance)
|
|
{
|
|
//
|
|
//----------------------------------------------------------------
|
|
// Fixed-step: the subsystems and the entity advance TOGETHER,
|
|
// one step at a time, because they read each other mid-flight.
|
|
// The VTV's hover spring is computed from its thrusters'
|
|
// measured heights, and each thruster measures from where the
|
|
// vehicle IS - so thrusters stepped twice against a vehicle
|
|
// that has not moved yet hand back two identical height
|
|
// samples, and the spring fires twice on stale data. Measured,
|
|
// that pod climbs at 30 fps and flies level at 144.
|
|
//
|
|
// So the step loop lives HERE, above both: everyone is walked
|
|
// to the same sub-frame instant before anyone takes the next
|
|
// step. Watchers and the update stream still run once per
|
|
// frame, after the loop - stepping is physics, watching is
|
|
// I/O, and only the first belongs inside.
|
|
//
|
|
// The interleave keys off the ENTITY's own clock so a
|
|
// subsystem created mid-flight (they are made alongside their
|
|
// owner) can never wedge the loop.
|
|
//----------------------------------------------------------------
|
|
//
|
|
Scalar fixed_step = Simulation::FixedStep();
|
|
|
|
if (fixed_step > (Scalar) 0)
|
|
{
|
|
//
|
|
// One grid for the whole vehicle. Every Simulation anchors
|
|
// its own lastPerformance at its creation time, so an
|
|
// entity and its subsystems were stepping on grids offset
|
|
// by a random fraction of a step - deterministic within a
|
|
// run, DIFFERENT between runs, because creation times ride
|
|
// on load timing. The thrusters' measurements then landed
|
|
// a different sub-step distance from the vehicle's
|
|
// integration every launch, which is physics drift no seed
|
|
// can pin. Snap the subsystems onto the entity's grid; the
|
|
// interleave below then keeps everyone in lockstep by
|
|
// construction, and once aligned this assignment is a
|
|
// no-op every frame after.
|
|
//
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i] &&
|
|
subsystemArray[i]->IsNonReplicantExecutable())
|
|
{
|
|
subsystemArray[i]->SetLastPerformance(
|
|
GetLastPerformance());
|
|
}
|
|
}
|
|
|
|
Time step_till = GetLastPerformance();
|
|
step_till += fixed_step;
|
|
|
|
while (step_till <= till)
|
|
{
|
|
//
|
|
// BeginStep on the ENTITY comes before the subsystems
|
|
// perform: the Mover's force accumulator is cleared
|
|
// here, and the thrusters then ADD this step's forces
|
|
// into a clean slate. The first version left that
|
|
// clear on the per-frame path, so a two-step frame
|
|
// integrated step one's thrust twice - and since how
|
|
// many steps land in a frame rides on wall-clock
|
|
// jitter, no two runs saw the same force history.
|
|
// Identical configs measured 0.23 apart because of it.
|
|
//
|
|
BeginStep();
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i] &&
|
|
subsystemArray[i]->IsNonReplicantExecutable())
|
|
{
|
|
subsystemArray[i]->BeginStep();
|
|
subsystemArray[i]->PerformTo(step_till);
|
|
}
|
|
}
|
|
Simulation::PerformTo(step_till);
|
|
step_till += fixed_step;
|
|
}
|
|
|
|
//
|
|
// How far past the last completed step the frame we are
|
|
// about to draw falls, as a fraction of one step. This is
|
|
// the leftover the fixed-step loop deliberately does not
|
|
// simulate - see renderPreviousOrigin in entity.h.
|
|
//
|
|
{
|
|
Scalar leftover = till - GetLastPerformance();
|
|
Scalar fraction = (fixed_step > (Scalar) 0)
|
|
? (leftover / fixed_step) : (Scalar) 0;
|
|
if (fraction < (Scalar) 0) fraction = (Scalar) 0;
|
|
if (fraction > (Scalar) 1) fraction = (Scalar) 1;
|
|
renderStepFraction = fraction;
|
|
}
|
|
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i] &&
|
|
subsystemArray[i]->IsNonReplicantExecutable())
|
|
{
|
|
subsystemArray[i]->WatchAndWrite(update_stream);
|
|
}
|
|
}
|
|
|
|
SET_PERFORM_ENTITY();
|
|
Simulation::WatchAndWrite(update_stream);
|
|
Check_Fpu();
|
|
CLEAR_PERFORM_ENTITY();
|
|
CLEAR_PERFORM_SUBSYSTEMS();
|
|
return;
|
|
}
|
|
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i])
|
|
{
|
|
Subsystem *subsystem = subsystemArray[i];
|
|
Check(subsystem);
|
|
if (subsystem->IsNonReplicantExecutable())
|
|
{
|
|
subsystem->PerformAndWatch(till, update_stream);
|
|
#if defined(USE_FPU_ANALYSIS)
|
|
if (!Fpu_Ok())
|
|
{
|
|
Registry *registry = application->GetRegistry();
|
|
Receiver::SharedData *shared_data =
|
|
registry->GetStaticData(GetClassID());
|
|
Derivation *derivation = shared_data->derivedClasses;
|
|
DEBUG_STREAM << derivation->className
|
|
<< "\nExecuting subsystem[" << i << "], "
|
|
<< subsystem->GetName() << endl;
|
|
Fail("Simulation corrupted FPU!");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// If this is a replicant, execute only those subsystems which need to
|
|
//--------------------------------------------------------------------
|
|
//
|
|
else
|
|
{
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i])
|
|
{
|
|
Subsystem *subsystem = subsystemArray[i];
|
|
Check(subsystem);
|
|
if (subsystem->IsReplicantExecutable())
|
|
{
|
|
subsystem->PerformAndWatch(till, update_stream);
|
|
#if defined(USE_FPU_ANALYSIS)
|
|
if (!Fpu_Ok())
|
|
{
|
|
Registry *registry = application->GetRegistry();
|
|
Receiver::SharedData *shared_data =
|
|
registry->GetStaticData(GetClassID());
|
|
Derivation *derivation = shared_data->derivedClasses;
|
|
DEBUG_STREAM << derivation->className
|
|
<< " replicant\nExecuting subsystem[" << i << "], "
|
|
<< subsystem->GetName() << endl;
|
|
Fail("Simulation corrupted FPU!");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CLEAR_PERFORM_SUBSYSTEMS();
|
|
|
|
SET_PERFORM_ENTITY();
|
|
Simulation::PerformAndWatch(till, update_stream);
|
|
Check_Fpu();
|
|
CLEAR_PERFORM_ENTITY();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::PlayerLinkMessageHandler(PlayerLinkMessage *message)
|
|
{
|
|
Check(application);
|
|
HostManager *host = application->GetHostManager();
|
|
Check(host);
|
|
playerLink =(Player*) host->GetEntityPointer(message->playerID);
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::TakeDamageStreamMessageHandler(TakeDamageStreamMessage *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
//-------------------------------------------------
|
|
// Extract the common information out of the stream
|
|
//-------------------------------------------------
|
|
//
|
|
Damage damage_data;
|
|
EntityID shooting_entity = message->shootingEntity;
|
|
int damage_zone_index = message->damageZoneIndex;
|
|
damage_data.impactPoint = message->impactPoint;
|
|
|
|
//
|
|
//---------------------------------------------------
|
|
// Figure out how many damage messages there are here
|
|
//---------------------------------------------------
|
|
//
|
|
DynamicMessage damage_stream(
|
|
message,
|
|
message->messageLength,
|
|
sizeof(*message)
|
|
);
|
|
int entry_count;
|
|
damage_stream >> entry_count;
|
|
|
|
for (int i=0; i<entry_count; ++i)
|
|
{
|
|
//
|
|
//-------------------------------------------------
|
|
// Read in damageType, damageAmount and subsystemID
|
|
//-------------------------------------------------
|
|
//
|
|
damage_stream >> damage_data.damageType >> damage_data.damageAmount;
|
|
int subsystem;
|
|
damage_stream >> subsystem;
|
|
|
|
//
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Apply the damage to the appropriate damageZone
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
TakeDamageMessage
|
|
message(
|
|
TakeDamageMessageID,
|
|
sizeof(TakeDamageMessage),
|
|
shooting_entity,
|
|
damage_zone_index,
|
|
damage_data,
|
|
subsystem
|
|
);
|
|
Dispatch(&message);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::TakeDamageMessageHandler(TakeDamageMessage *message)
|
|
{
|
|
//
|
|
//-------------------------------------
|
|
// YOU MUST CHECK FOR -1 DamageZone!!!!
|
|
//-------------------------------------
|
|
//
|
|
if (!damageZones || message->damageZone == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// If we have no damage zones allocated, we ignore any damage sent to us
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Verify(message->damageZone < damageZoneCount);
|
|
Verify(message->damageZone >= 0 );
|
|
Check_Pointer(damageZones);
|
|
Check(damageZones[message->damageZone]);
|
|
damageZones[message->damageZone]->TakeDamage(message->damageData);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// Construction and Destruction
|
|
//
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Entity::Entity(
|
|
Entity::MakeMessage *creation_message,
|
|
Entity::SharedData &virtual_data
|
|
):
|
|
Simulation(creation_message->classToCreate,virtual_data),
|
|
staticVideoSocket(NULL),
|
|
dynamicVideoSocket(NULL),
|
|
audioSocket(NULL)
|
|
{
|
|
Check_Pointer(this);
|
|
Check(creation_message);
|
|
|
|
//
|
|
// Get host manager pointer
|
|
//
|
|
HostManager *host_manager;
|
|
|
|
Check(application);
|
|
host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
|
|
//
|
|
//-----------------------------
|
|
// Initialize the entity fields
|
|
//-----------------------------
|
|
//
|
|
playerLink = NULL;
|
|
resourceID = creation_message->resourceID;
|
|
owningPlayer = (Player*)host_manager->GetEntityPointer(
|
|
creation_message->owningPlayerID
|
|
);
|
|
simulationFlags = creation_message->instanceFlags;
|
|
entityID = creation_message->entityID;
|
|
ownerID = creation_message->ownerID;
|
|
creationTime = Now();
|
|
interestZoneID = creation_message->interestZoneID;
|
|
interestCount = 0;
|
|
|
|
// initialize attributes
|
|
localOrigin = creation_message->localOrigin;
|
|
updateOrigin = localOrigin;
|
|
localToWorld = localOrigin;
|
|
|
|
//
|
|
// Render interpolation starts with nothing to blend: the previous
|
|
// origin IS the current one and no fraction of a step is outstanding,
|
|
// so GetRenderToWorld hands back localToWorld until the first step has
|
|
// actually been taken.
|
|
//
|
|
renderPreviousOrigin = localOrigin;
|
|
renderStepFraction = (Scalar) 0;
|
|
renderStepTaken = False;
|
|
|
|
// initialize camera stuff
|
|
cameraOffset = Origin::Identity;
|
|
|
|
SetInvalidFlag();
|
|
|
|
// initialize model stuff
|
|
lastPerformance = creationTime;
|
|
subsystemCount = 0;
|
|
subsystemArray = NULL;
|
|
damageZones = NULL;
|
|
damageZoneCount = 0;
|
|
|
|
//
|
|
//--------------------------------
|
|
// Allocate the damageZones array
|
|
//--------------------------------
|
|
//
|
|
|
|
//
|
|
// Read In DamageZones and Create DamageZoneID's
|
|
//
|
|
if (resourceID != ResourceDescription::NullResourceID)
|
|
{
|
|
ResourceDescription *dmg_res =
|
|
application->GetResourceFile()->SearchList(
|
|
resourceID,
|
|
ResourceDescription::DamageZoneStreamResourceType
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------
|
|
// Allocate damageZones for this entity only
|
|
// if they were defined in the resource
|
|
//--------------------------------------------
|
|
//
|
|
if (dmg_res)
|
|
{
|
|
dmg_res->Lock();
|
|
DynamicMemoryStream damage_zone_stream(
|
|
dmg_res->resourceAddress,
|
|
dmg_res->resourceSize);
|
|
|
|
damage_zone_stream >> damageZoneCount;
|
|
damageZones = new (::DamageZone(*[damageZoneCount]));
|
|
Register_Pointer(damageZones);
|
|
dmg_res->Unlock();
|
|
}
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------
|
|
// Create a new entity ID and assign ownership to this host
|
|
// If (this is a master entity) or
|
|
// (this is a dynamic hermit) or
|
|
// (this is the initial independent)
|
|
//---------------------------------------------------------
|
|
//
|
|
if (
|
|
(GetInstance() == MasterInstance) ||
|
|
(GetInstance() == HermitInstance && IsDynamic()) ||
|
|
(
|
|
(GetInstance() == IndependantInstance) &&
|
|
(creation_message->ownerID == NullHostID ||
|
|
creation_message->ownerID == MapHostID)
|
|
)
|
|
)
|
|
{
|
|
entityID = host_manager->MakeUniqueEntityID();
|
|
ownerID = host_manager->GetLocalHostID();
|
|
creation_message->entityID = entityID;
|
|
creation_message->ownerID = ownerID;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------
|
|
// Tell the host manager about this new entity
|
|
//--------------------------------------------
|
|
//
|
|
Check(host_manager);
|
|
host_manager->NotifyOfEntityCreation(this, creation_message);
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Entity*
|
|
Entity::Make(Entity::MakeMessage *creation_message)
|
|
{
|
|
return new Entity(creation_message,DefaultData);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
Entity::CreateMakeMessage(
|
|
MakeMessage *creation_message,
|
|
#if DEBUG_LEVEL>0
|
|
NotationFile *model_file,
|
|
#else
|
|
NotationFile *,
|
|
#endif
|
|
const ResourceDirectories *
|
|
)
|
|
{
|
|
Check(creation_message);
|
|
Check(model_file);
|
|
char *p;
|
|
|
|
creation_message->messageLength = sizeof(Entity::MakeMessage);
|
|
creation_message->messageID = Entity::MakeMessageID;
|
|
creation_message->owningPlayerID = EntityID::Null;
|
|
creation_message->messageFlags = 0;
|
|
creation_message->classToCreate = RegisteredClass::TrivialEntityClassID;
|
|
creation_message->instanceFlags = 0; //DefaultFlags;
|
|
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "X position coordinate missing!\n";
|
|
return False;
|
|
}
|
|
creation_message->localOrigin.linearPosition.x = atof(p);
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Y position coordinate missing!\n";
|
|
return False;
|
|
}
|
|
creation_message->localOrigin.linearPosition.y = atof(p);
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Z position coordinate missing!\n";
|
|
return False;
|
|
}
|
|
creation_message->localOrigin.linearPosition.z = atof(p);
|
|
|
|
EulerAngles pyr;
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Pitch missing!\n";
|
|
return False;
|
|
}
|
|
pyr.pitch = atof(p);
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Yaw missing!\n";
|
|
return False;
|
|
}
|
|
pyr.yaw = atof(p);
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Roll missing!\n";
|
|
return False;
|
|
}
|
|
pyr.roll = atof(p);
|
|
creation_message->localOrigin.angularPosition = pyr;
|
|
return True;
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
Entity::CreateMakeMapMessage(
|
|
MakeMapMessage *creation_message,
|
|
ResourceDescription::ResourceID resource_id,
|
|
#if DEBUG_LEVEL>0
|
|
NotationFile *model_file,
|
|
#else
|
|
NotationFile *,
|
|
#endif
|
|
const ResourceDirectories *
|
|
)
|
|
{
|
|
Check(creation_message);
|
|
Check(model_file);
|
|
char *p;
|
|
|
|
creation_message->messageLength = sizeof(Entity::MakeMapMessage);
|
|
creation_message->messageID = Entity::MakeMessageID;
|
|
creation_message->messageFlags = Entity::MakeMessage::MapStreamMarkerFlag;
|
|
|
|
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "X position coordinate missing!\n";
|
|
return False;
|
|
}
|
|
creation_message->localOrigin.linearPosition.x = atof(p);
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Y position coordinate missing!\n";
|
|
return False;
|
|
}
|
|
creation_message->localOrigin.linearPosition.y = atof(p);
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Z position coordinate missing!\n";
|
|
return False;
|
|
}
|
|
creation_message->localOrigin.linearPosition.z = atof(p);
|
|
|
|
EulerAngles pyr = EulerAngles::Identity;
|
|
#if 0
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Pitch missing!\n";
|
|
return False;
|
|
}
|
|
pyr.pitch = atof(p);
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Yaw missing!\n";
|
|
return False;
|
|
}
|
|
pyr.yaw = atof(p);
|
|
if ((p = strtok(NULL, " ")) == NULL)
|
|
{
|
|
std::cerr << "Roll missing!\n";
|
|
return False;
|
|
}
|
|
pyr.roll = atof(p);
|
|
#endif
|
|
|
|
creation_message->localOrigin.angularPosition = pyr;
|
|
creation_message->resourceID = resource_id;
|
|
|
|
return True;
|
|
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::DestroyEntityMessageHandler(Message *)
|
|
{
|
|
CondemnToDeathRow();
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Entity::~Entity()
|
|
{
|
|
//
|
|
// Notify the host manager that we are going away
|
|
//
|
|
Check(application);
|
|
application->GetHostManager()->NotifyOfEntityDestruction(this);
|
|
|
|
//
|
|
// Delete the subsystem array
|
|
//
|
|
if (subsystemArray)
|
|
{
|
|
Check_Pointer(subsystemArray);
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i])
|
|
{
|
|
Unregister_Object(subsystemArray[i]);
|
|
delete subsystemArray[i];
|
|
}
|
|
}
|
|
Unregister_Pointer(subsystemArray);
|
|
delete subsystemArray;
|
|
}
|
|
if (damageZones)
|
|
{
|
|
Check_Pointer(damageZones);
|
|
for (int i=0; i<damageZoneCount; ++i)
|
|
{
|
|
if (damageZones[i])
|
|
{
|
|
Unregister_Object(damageZones[i]);
|
|
delete damageZones[i];
|
|
}
|
|
}
|
|
Unregister_Pointer(damageZones);
|
|
delete damageZones;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Entity::CondemnToDeathRow()
|
|
{
|
|
Check(this);
|
|
Check(application);
|
|
EntityManager *entity_manager = application->GetEntityManager();
|
|
Check(entity_manager);
|
|
|
|
//
|
|
//-------------------------------------------------
|
|
// Make sure we are not entered into deathrow twice
|
|
//-------------------------------------------------
|
|
//
|
|
if (!IsCondemned())
|
|
{
|
|
entity_manager->deathRow->Add(this);
|
|
SetCondemnedFlag();
|
|
NeverExecute();
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// Test Support
|
|
//
|
|
|
|
Logical
|
|
Entity::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|
|
|
|
//##########################################################################
|
|
// Render interpolation - see entity.h for why, and for why it is render
|
|
// only. RP412INTERP=0 turns it off so the stepping it removes can be seen
|
|
// again on a test machine without a rebuild.
|
|
//##########################################################################
|
|
//
|
|
static Logical
|
|
RenderInterpolationEnabled()
|
|
{
|
|
static int enabled = -1;
|
|
|
|
if (enabled < 0)
|
|
{
|
|
const char *setting = getenv("RP412INTERP");
|
|
enabled = (setting != NULL && atoi(setting) == 0) ? 0 : 1;
|
|
if (!enabled)
|
|
{
|
|
DEBUG_STREAM << "Render: interpolation off (RP412INTERP=0) - "
|
|
<< "drawn motion steps at the physics rate\n" << std::flush;
|
|
}
|
|
}
|
|
return enabled ? True : False;
|
|
}
|
|
|
|
void
|
|
Entity::SnapshotRenderOrigin()
|
|
{
|
|
Check(this);
|
|
renderPreviousOrigin = localOrigin;
|
|
renderStepTaken = True;
|
|
}
|
|
|
|
void
|
|
Entity::SetRenderStepFraction(Scalar fraction)
|
|
{
|
|
Check(this);
|
|
renderStepFraction = fraction;
|
|
}
|
|
|
|
void
|
|
Entity::GetRenderToWorld(LinearMatrix *out)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(out);
|
|
|
|
//
|
|
// No fixed step at all, interpolation switched off, or this entity has
|
|
// not taken a step yet. Hand back exactly what every caller used before
|
|
// this existed.
|
|
//
|
|
// The test used to be on the fraction rather than on renderStepTaken,
|
|
// and that was wrong in a way that showed. Drawing at fraction f means
|
|
// drawing at the start of the step plus f of it, so f = 0 means the
|
|
// START of the step - while this early return hands back localToWorld,
|
|
// which is its END. The two are a whole step apart, about a metre at
|
|
// racing speed.
|
|
//
|
|
// behind is a whole number of milliseconds against a 20ms step, so it
|
|
// lands on exactly zero about one frame in twenty. On those frames a
|
|
// pod was drawn a full step ahead of itself and then snapped back on
|
|
// the next one: a jump three times a second at 59fps, regular because
|
|
// the beat between frame rate and step rate is regular, and worst when
|
|
// a pod crosses the view quickly. It never appeared in any simulation
|
|
// trace because the simulation was right - only the drawing was wrong.
|
|
//
|
|
// Interpolating at f = 0 is continuous with everything either side of
|
|
// it. Each frame advances the drawn position by frame_time / step
|
|
// whether or not a step boundary falls between the two, which is the
|
|
// entire point of doing it.
|
|
//
|
|
if (!RenderInterpolationEnabled() || !renderStepTaken)
|
|
{
|
|
*out = localToWorld;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Origin::Lerp does the position and the shortest-arc quaternion, and
|
|
// normalises - which over a 20 ms step is indistinguishable from a
|
|
// true slerp and a good deal cheaper.
|
|
//
|
|
Origin blended;
|
|
blended.Lerp(renderPreviousOrigin, localOrigin, renderStepFraction);
|
|
*out = blended;
|
|
Check_Fpu();
|
|
}
|
|
|
|
//##########################################################################
|
|
// Renderer Support
|
|
//
|
|
void
|
|
Entity::AddStaticVideoComponent(Component *component)
|
|
{
|
|
Check(component);
|
|
Check(&staticVideoSocket);
|
|
staticVideoSocket.Add(component);
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
Entity::AddDynamicVideoComponent(Component *component)
|
|
{
|
|
Check(component);
|
|
Check(&dynamicVideoSocket);
|
|
dynamicVideoSocket.Add(component);
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
Entity::AddAudioComponent(Component *component)
|
|
{
|
|
Check(component);
|
|
Check(&audioSocket);
|
|
audioSocket.Add(component);
|
|
Check_Fpu();
|
|
}
|
|
|
|
Enumeration
|
|
Entity::GetAudioRepresentation(Entity *linked_entity)
|
|
{
|
|
Check(this);
|
|
Check(linked_entity);
|
|
|
|
return
|
|
(linked_entity == this) ?
|
|
InternalAudioRepresentation : ExternalAudioRepresentation;
|
|
}
|
|
|
|
//#############################################################################
|
|
// Interest Support
|
|
//
|
|
void
|
|
Entity::BecomeInteresting()
|
|
{
|
|
BecomeInterestingMessage
|
|
message(
|
|
Entity::BecomeInterestingMessageID,
|
|
sizeof(BecomeInterestingMessage)
|
|
);
|
|
|
|
Receiver::Dispatch(&message);
|
|
Check_Fpu();
|
|
}
|
|
|
|
void Entity::BecomeInterestingMessageHandler(const Receiver::Message*)
|
|
{
|
|
++interestCount;
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
Entity::BecomeUninterestingMessageHandler()
|
|
{
|
|
--interestCount;
|
|
Check_Fpu();
|
|
}
|
|
|
|
Enumeration
|
|
Entity::GetInterestPriority(Entity *linked_entity)
|
|
{
|
|
Check(this);
|
|
Check(linked_entity);
|
|
|
|
return (linked_entity == this) ? HighInterestPriority : LowInterestPriority;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
Entity::GetDamageZoneIndex(const CString &damage_zone_name) const
|
|
{
|
|
|
|
for(int ii=0;ii<damageZoneCount;++ii)
|
|
{
|
|
if(damageZones[ii]->damageZoneName.Compare(damage_zone_name) == 0)
|
|
{
|
|
return ii;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
int
|
|
Entity::CreateDamageZoneStream(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
)
|
|
{
|
|
Check(resource_file);
|
|
Check_Pointer(model_name);
|
|
Check(model_file);
|
|
Check_Pointer(directories);
|
|
|
|
//
|
|
//-------------------
|
|
// Find the .dmg file
|
|
//-------------------
|
|
//
|
|
const char* dmg_file_cname;
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"DamageZones",
|
|
&dmg_file_cname
|
|
)
|
|
)
|
|
{
|
|
std::cerr << model_name << " is missing .dmg file specification!\n";
|
|
return -1;
|
|
}
|
|
|
|
CString dmg_filename =
|
|
MakePathedFilename(directories->modelDirectory, dmg_file_cname);
|
|
|
|
NotationFile *dmg_file = new NotationFile(dmg_filename);
|
|
Register_Object(dmg_file);
|
|
int page_count = dmg_file->PageCount();
|
|
if (!page_count)
|
|
{
|
|
std::cerr << dmg_filename << " is empty or missing!\n";
|
|
Unregister_Object(dmg_file);
|
|
delete dmg_file;
|
|
return -1;
|
|
}
|
|
|
|
DynamicMemoryStream damage_stream;
|
|
|
|
damage_stream << page_count;
|
|
NameList *dmg_namelist = dmg_file->MakePageList();
|
|
Register_Object(dmg_namelist);
|
|
|
|
NameList::Entry *dmg_entry = dmg_namelist->GetFirstEntry();
|
|
|
|
CString dmg_page_name;
|
|
while(dmg_entry)
|
|
{
|
|
dmg_page_name = dmg_entry->GetName();
|
|
DamageZone::CreateStreamedDamageZone(
|
|
model_file,
|
|
model_name,
|
|
NULL,
|
|
dmg_page_name,
|
|
&damage_stream,
|
|
dmg_file,
|
|
directories
|
|
);
|
|
dmg_entry = dmg_entry->GetNextEntry();
|
|
}
|
|
|
|
//
|
|
//~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Add Stream to Resource
|
|
//~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
ResourceDescription *new_res =
|
|
resource_file->AddResourceMemoryStream(
|
|
model_name,
|
|
ResourceDescription::DamageZoneStreamResourceType,
|
|
1,
|
|
ResourceDescription::Preload,
|
|
&damage_stream
|
|
);
|
|
Check(new_res);
|
|
|
|
Unregister_Object(dmg_file);
|
|
delete dmg_file;
|
|
Unregister_Object(dmg_namelist);
|
|
delete dmg_namelist;
|
|
|
|
return new_res->resourceID;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
ResourceDescription::ResourceID
|
|
Entity::CreateExplosionTableStream(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
)
|
|
{
|
|
Check(resource_file);
|
|
Check_Pointer(model_name);
|
|
Check(model_file);
|
|
Check_Pointer(directories);
|
|
//
|
|
//~~~~~~~~~~~~~~~~~
|
|
// Find *.dmg file
|
|
//~~~~~~~~~~~~~~~~~
|
|
//
|
|
const char *entry_data;
|
|
if (!model_file->GetEntry(
|
|
"gamedata",
|
|
"DamageZones",
|
|
&entry_data
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " is missing .dmg file specification!\n" << std::flush;
|
|
return -1;
|
|
}
|
|
CString filename =
|
|
MakePathedFilename(directories->modelDirectory, entry_data);
|
|
//
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Make a notation file of the DMG Entries
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
NotationFile *dmg_file = new NotationFile(filename);
|
|
Register_Object(dmg_file);
|
|
|
|
if(dmg_file->IsEmpty())
|
|
{
|
|
DEBUG_STREAM << model_name << " *.dmg file is empty or missing!\n" << std::flush;
|
|
Unregister_Object(dmg_file);
|
|
delete dmg_file;
|
|
return -1;
|
|
}
|
|
|
|
DynamicMemoryStream explosion_stream;
|
|
NameList *dmg_namelist = dmg_file->MakePageList();
|
|
Register_Object(dmg_namelist);
|
|
|
|
NameList::Entry *dmg_entry = dmg_namelist->GetFirstEntry();
|
|
while(dmg_entry)
|
|
{
|
|
const char *entry_data;
|
|
if (dmg_file->GetEntry(
|
|
dmg_entry->GetName(),
|
|
"ExplosionTable",
|
|
&entry_data
|
|
)
|
|
)
|
|
{
|
|
CString filename =
|
|
MakePathedFilename(directories->modelDirectory, entry_data);
|
|
|
|
NotationFile *exp_file = new NotationFile(filename);
|
|
Register_Object(exp_file);
|
|
|
|
if(exp_file->IsEmpty())
|
|
{
|
|
std::cerr<<model_name<<" : "<<filename<<" is emoty or missing! \n";
|
|
Unregister_Object(dmg_file);
|
|
delete dmg_file;
|
|
Unregister_Object(dmg_namelist);
|
|
delete dmg_namelist;
|
|
Unregister_Object(exp_file);
|
|
delete exp_file;
|
|
return ResourceDescription::NullResourceID;
|
|
}
|
|
else
|
|
{
|
|
ExplosionTable::CreateStreamedExplosionTable(
|
|
resource_file,
|
|
model_name,
|
|
exp_file,
|
|
filename,
|
|
&explosion_stream
|
|
);
|
|
}
|
|
Unregister_Object(exp_file);
|
|
delete exp_file;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Write Out no Explosions for this DamageZone
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
int zero_int(0);
|
|
explosion_stream << zero_int;
|
|
}
|
|
dmg_entry = dmg_entry->GetNextEntry();
|
|
}
|
|
|
|
ResourceDescription *new_res =
|
|
resource_file->AddResourceMemoryStream(
|
|
model_name,
|
|
ResourceDescription::ExplosionTableStreamResourceType,
|
|
1,
|
|
ResourceDescription::Preload,
|
|
&explosion_stream
|
|
);
|
|
Check(new_res);
|
|
Unregister_Object(dmg_file);
|
|
delete dmg_file;
|
|
Unregister_Object(dmg_namelist);
|
|
delete dmg_namelist;
|
|
return new_res->resourceID;
|
|
}
|
|
|
|
#if defined(TEST_CLASS)
|
|
# include "entity.tcp"
|
|
#endif
|