mirror of
https://bitbucket.org/theswgsource/src-1.2.git
synced 2026-08-01 02:15:54 -04:00
3393 lines
107 KiB
C++
Executable File
3393 lines
107 KiB
C++
Executable File
// ======================================================================
|
|
//
|
|
// Object.cpp
|
|
// copyright 1998 Bootprint Entertainment
|
|
// Copyright 2000-2001 Sony Online Entertainment Inc.
|
|
// All Rights Reserved.
|
|
//
|
|
// ======================================================================
|
|
|
|
#include "sharedObject/FirstSharedObject.h"
|
|
#include "sharedObject/Object.h"
|
|
|
|
#include "sharedCollision/CollisionProperty.h"
|
|
#include "sharedDebug/DebugFlags.h"
|
|
#include "sharedDebug/Profiler.h"
|
|
#include "sharedFile/TreeFile.h"
|
|
#include "sharedFoundation/ConfigFile.h"
|
|
#include "sharedFoundation/CrashReportInformation.h"
|
|
#include "sharedFoundation/DebugInfoManager.h"
|
|
#include "sharedFoundation/ExitChain.h"
|
|
#include "sharedFoundation/MemoryBlockManager.h"
|
|
#include "sharedFoundation/Production.h"
|
|
#include "sharedMath/AxialBox.h"
|
|
#include "sharedMath/DebugShapeRenderer.h"
|
|
#include "sharedMath/SpatialSubdivision.h"
|
|
#include "sharedMath/Sphere.h"
|
|
#include "sharedObject/AlterResult.h"
|
|
#include "sharedObject/AlterScheduler.h"
|
|
#include "sharedObject/Appearance.h"
|
|
#include "sharedObject/AppearanceTemplate.h"
|
|
#include "sharedObject/AppearanceTemplateList.h"
|
|
#include "sharedObject/CellProperty.h"
|
|
#include "sharedObject/ConfigSharedObject.h"
|
|
#include "sharedObject/ContainedByProperty.h"
|
|
#include "sharedObject/Controller.h"
|
|
#include "sharedObject/Dynamics.h"
|
|
#include "sharedObject/NetworkIdManager.h"
|
|
#include "sharedObject/ObjectNotification.h"
|
|
#include "sharedObject/ObjectTemplate.h"
|
|
#include "sharedObject/PortalProperty.h"
|
|
#include "sharedObject/ScheduleData.h"
|
|
#include "sharedObject/SlottedContainer.h"
|
|
#include "sharedObject/VolumeContainer.h"
|
|
#include "sharedObject/World.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <typeinfo>
|
|
#include <vector>
|
|
|
|
#define ENABLE_OBJECTTOWORLDDIRTY 1
|
|
#define PROFILE_GETTRANSFORM_O2W 0
|
|
|
|
// ======================================================================
|
|
|
|
class Object::NotificationList
|
|
{
|
|
public:
|
|
|
|
NotificationList();
|
|
NotificationList(const NotificationList &otherList, const ObjectNotification &otherEntry, bool addOtherEntry);
|
|
|
|
bool equalsAdd(const NotificationList &otherList, const ObjectNotification &otherEntry) const;
|
|
bool equalsRemove(const NotificationList &otherList, const ObjectNotification &otherEntry) const;
|
|
bool contains(const ObjectNotification &otherEntry) const;
|
|
|
|
void addToWorld(Object &object) const;
|
|
void removeFromWorld(Object &object) const;
|
|
|
|
void positionChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const;
|
|
void rotationChanged(Object &object, bool dueToParentChange) const;
|
|
void positionAndRotationChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const;
|
|
|
|
void cellChanged(Object &object, bool dueToParentChange) const;
|
|
void extentChanged(Object &object) const;
|
|
void pobFixupComplete(Object &object) const;
|
|
|
|
protected:
|
|
|
|
typedef std::vector<const ObjectNotification *> Notifications;
|
|
Notifications m_notificationList;
|
|
};
|
|
|
|
// ======================================================================
|
|
|
|
Object::NotificationList::NotificationList()
|
|
: m_notificationList()
|
|
{
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object::NotificationList::NotificationList(const NotificationList &otherList, const ObjectNotification &otherEntry, bool addOtherEntry)
|
|
: m_notificationList()
|
|
{
|
|
if (addOtherEntry)
|
|
{
|
|
// handle adding the other entry
|
|
|
|
m_notificationList.reserve(otherList.m_notificationList.size() + 1);
|
|
|
|
bool addedOtherEntry = false;
|
|
|
|
const int otherPriority = otherEntry.getPriority();
|
|
|
|
// copy the other notification list, inserting the other entry in sorted position
|
|
const Notifications::const_iterator end = otherList.m_notificationList.end();
|
|
for (Notifications::const_iterator i = otherList.m_notificationList.begin(); i != end; ++i)
|
|
{
|
|
const ObjectNotification *add = *i;
|
|
const int addPriority = add->getPriority();
|
|
|
|
if (!addedOtherEntry && (otherPriority < addPriority || (otherPriority == addPriority && &otherEntry < add)))
|
|
{
|
|
m_notificationList.push_back(&otherEntry);
|
|
addedOtherEntry = true;
|
|
}
|
|
|
|
m_notificationList.push_back(add);
|
|
}
|
|
|
|
// if the other entry didn't get copied already, it's the last element
|
|
if (!addedOtherEntry)
|
|
m_notificationList.push_back(&otherEntry);
|
|
|
|
DEBUG_FATAL(m_notificationList.size() != otherList.m_notificationList.size() + 1, ("failed to build new notification list correctly"));
|
|
}
|
|
else
|
|
{
|
|
// handle creating a copy but deleting the other entry
|
|
m_notificationList.reserve(otherList.m_notificationList.size() - 1);
|
|
|
|
bool deletedOtherEntry = false;
|
|
|
|
// copy the other notification list, deleting the other entry if found
|
|
const Notifications::const_iterator end = otherList.m_notificationList.end();
|
|
for (Notifications::const_iterator i = otherList.m_notificationList.begin(); i != end; ++i)
|
|
{
|
|
const ObjectNotification *add = *i;
|
|
if (add == &otherEntry)
|
|
{
|
|
// skip it --- we are removing this notification
|
|
deletedOtherEntry = true;
|
|
continue;
|
|
}
|
|
|
|
// copy the source notification
|
|
m_notificationList.push_back(add);
|
|
}
|
|
|
|
DEBUG_FATAL(!deletedOtherEntry, ("didn't find notification [0x%08x] in source list for deletion", &otherEntry));
|
|
DEBUG_FATAL(m_notificationList.size() != otherList.m_notificationList.size() - 1, ("failed to build new notification list correctly"));
|
|
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::NotificationList::equalsAdd(const NotificationList &otherList, const ObjectNotification &otherEntry) const
|
|
{
|
|
return otherList.equalsRemove(*this, otherEntry);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::NotificationList::equalsRemove(const NotificationList &otherList, const ObjectNotification &otherEntry) const
|
|
{
|
|
const uint otherSize = otherList.m_notificationList.size();
|
|
if (m_notificationList.size() + 1 != otherSize)
|
|
return false;
|
|
|
|
// compare against the other list and the other entry
|
|
bool once = false;
|
|
uint i = 0;
|
|
uint j = 0;
|
|
for ( ; i < otherSize; ++i)
|
|
{
|
|
if (otherList.m_notificationList[i] == &otherEntry)
|
|
{
|
|
// go on to next local element
|
|
DEBUG_FATAL(once, ("found other entry twice"));
|
|
once = true;
|
|
}
|
|
else
|
|
if (j < otherSize-1 && otherList.m_notificationList[i] == m_notificationList[j])
|
|
++j;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// make sure the item to remove was found
|
|
if (!once)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::NotificationList::contains(const ObjectNotification &otherEntry) const
|
|
{
|
|
return std::find(m_notificationList.begin(), m_notificationList.end(), &otherEntry) != m_notificationList.end();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Return whether the object is initialized or not.
|
|
*
|
|
* Uninitialized objects should not be processed by the AlterScheduler
|
|
* nor should their alter calls be made.
|
|
*/
|
|
|
|
bool Object::isInitialized() const
|
|
{
|
|
//-- By default, all objects are initialized. Derived classes can override
|
|
// this behavior and should not chain down to Object::isInitialized().
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationList::addToWorld(Object &object) const
|
|
{
|
|
Notifications::const_iterator end = m_notificationList.end();
|
|
for (Notifications::const_iterator i = m_notificationList.begin(); i != end; ++i)
|
|
(*i)->addToWorld(object);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationList::removeFromWorld(Object &object) const
|
|
{
|
|
Notifications::const_reverse_iterator end = m_notificationList.rend();
|
|
for (Notifications::const_reverse_iterator i = m_notificationList.rbegin(); i != end; ++i)
|
|
(*i)->removeFromWorld(object);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationList::positionChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const
|
|
{
|
|
Notifications::const_iterator end = m_notificationList.end();
|
|
for (Notifications::const_iterator i = m_notificationList.begin(); i != end; ++i)
|
|
if (!(*i)->positionChanged(object, dueToParentChange, oldPosition))
|
|
return;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationList::rotationChanged(Object &object, bool dueToParentChange) const
|
|
{
|
|
Notifications::const_iterator end = m_notificationList.end();
|
|
for (Notifications::const_iterator i = m_notificationList.begin(); i != end; ++i)
|
|
(*i)->rotationChanged(object, dueToParentChange);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationList::positionAndRotationChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const
|
|
{
|
|
Notifications::const_iterator end = m_notificationList.end();
|
|
for (Notifications::const_iterator i = m_notificationList.begin(); i != end; ++i)
|
|
if (!(*i)->positionAndRotationChanged(object, dueToParentChange, oldPosition))
|
|
return;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationList::cellChanged(Object &object, bool dueToParentChange) const
|
|
{
|
|
Notifications::const_iterator end = m_notificationList.end();
|
|
for (Notifications::const_iterator i = m_notificationList.begin(); i != end; ++i)
|
|
(*i)->cellChanged(object, dueToParentChange);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationList::extentChanged(Object &object) const
|
|
{
|
|
Notifications::const_iterator end = m_notificationList.end();
|
|
for (Notifications::const_iterator i = m_notificationList.begin(); i != end; ++i)
|
|
(*i)->extentChanged(object);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationList::pobFixupComplete(Object &object) const
|
|
{
|
|
Notifications::const_iterator end = m_notificationList.end();
|
|
for (Notifications::const_iterator i = m_notificationList.begin(); i != end; ++i)
|
|
(*i)->pobFixupComplete(object);
|
|
}
|
|
|
|
// ======================================================================
|
|
|
|
class Object::NotificationListManager
|
|
{
|
|
public:
|
|
|
|
static void install();
|
|
|
|
static const NotificationList *getEmptyNotificationList();
|
|
static const NotificationList *addNotification(const NotificationList *notificationList, const ObjectNotification ¬ification);
|
|
static const NotificationList *removeNotification(const NotificationList *notificationList, const ObjectNotification ¬ification);
|
|
|
|
public:
|
|
|
|
typedef std::vector<const Object::NotificationList*> List;
|
|
|
|
private:
|
|
|
|
static void remove();
|
|
|
|
private:
|
|
|
|
static List ms_list;
|
|
static Object::NotificationList ms_empty;
|
|
};
|
|
|
|
// ======================================================================
|
|
|
|
Object::NotificationListManager::List Object::NotificationListManager::ms_list;
|
|
Object::NotificationList Object::NotificationListManager::ms_empty;
|
|
|
|
// ======================================================================
|
|
|
|
void Object::NotificationListManager::install()
|
|
{
|
|
ms_list.push_back(&ms_empty);
|
|
ExitChain::add(remove, "Object::NotificationListManager::remove");
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::NotificationListManager::remove()
|
|
{
|
|
// don't delete the first element, it wasn't allocated from the heap
|
|
const uint count = ms_list.size();
|
|
for (uint i = 1; i < count; ++i)
|
|
{
|
|
const NotificationList *notificationList = ms_list.back();
|
|
ms_list.pop_back();
|
|
delete notificationList; //lint !e605 // Increase in pointer capability
|
|
}
|
|
|
|
// remove the first element from the list
|
|
ms_list.pop_back();
|
|
}
|
|
|
|
// ======================================================================
|
|
|
|
const Object::NotificationList *Object::NotificationListManager::getEmptyNotificationList()
|
|
{
|
|
return &ms_empty;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
const Object::NotificationList *Object::NotificationListManager::addNotification(const NotificationList *source, const ObjectNotification ¬ification)
|
|
{
|
|
const List::const_iterator end = ms_list.end();
|
|
|
|
// search for an existing copy of the requested notification list
|
|
for (List::const_iterator i = ms_list.begin(); i != end; ++i)
|
|
if ((*i)->equalsAdd(*source, notification))
|
|
return *i;
|
|
|
|
// wasn't found, create one
|
|
NotificationList *notificationList = new NotificationList(*source, notification, true);
|
|
ms_list.push_back(notificationList);
|
|
|
|
return notificationList;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
const Object::NotificationList *Object::NotificationListManager::removeNotification(const NotificationList *source, const ObjectNotification ¬ification)
|
|
{
|
|
const List::const_iterator end = ms_list.end();
|
|
|
|
// search for an existing copy of the requested notification list
|
|
for (List::const_iterator i = ms_list.begin(); i != end; ++i)
|
|
if ((*i)->equalsRemove(*source, notification))
|
|
return *i;
|
|
|
|
// wasn't found, create one
|
|
NotificationList *notificationList = new NotificationList(*source, notification, false);
|
|
ms_list.push_back(notificationList);
|
|
|
|
return notificationList;
|
|
}
|
|
|
|
// ======================================================================
|
|
|
|
namespace ObjectNamespace
|
|
{
|
|
typedef std::vector<Object::DpvsObjects *> DpvsObjectsList;
|
|
typedef std::map<PropertyId,int> PropertySearchStatistics;
|
|
typedef std::vector<Property *> PropertyList;
|
|
typedef std::vector<Object::PropertyList *> PropertyListList;
|
|
typedef std::vector<Object *> AttachedObjects;
|
|
typedef std::vector<AttachedObjects *> AttachedObjectsList;
|
|
|
|
MemoryBlockManager *ms_transformMemoryBlockManager;
|
|
DpvsObjectsList ms_systemAllocatedDpvsObjectsList;
|
|
int ms_allocatedDpvsObjects;
|
|
DpvsObjectsList ms_freeDpvsObjectsList;
|
|
PropertyListList ms_propertyListList;
|
|
AttachedObjectsList ms_attachedObjectsList;
|
|
bool ms_disallowObjectDelete;
|
|
bool ms_logObjectDelete;
|
|
bool ms_validateObjectPosition;
|
|
bool ms_objectsAlterChildrenAndContents;
|
|
|
|
PropertySearchStatistics ms_propertySearchStatistics;
|
|
int ms_propertySearchesPerFrame;
|
|
bool ms_reportPropertySearchStatistics;
|
|
|
|
Object::InWorldAddDpvsObjectHookFunction ms_inWorldAddDpvsObjectHookFunction;
|
|
Object::InWorldRemoveDpvsObjectHookFunction ms_inWorldRemoveDpvsObjectHookFunction;
|
|
Object::LeakedDpvsObjectHookFunction ms_leakedDpvsObjectHookFunction;
|
|
|
|
char ms_crashReportInfo[MAX_PATH * 2];
|
|
|
|
void remove();
|
|
Transform *newLocalTransform();
|
|
void deleteLocalTransform(Transform *t);
|
|
Object::DpvsObjects *newDpvsObjects();
|
|
void deleteDpvsObjects(Object::DpvsObjects *t);
|
|
PropertyList * newPropertyList();
|
|
void deletePropertyList(PropertyList * propertyList);
|
|
AttachedObjects * newAttachedObjects();
|
|
void deleteAttachedObjects(AttachedObjects * attachedObjects);
|
|
void reportPropertySearchStatistics();
|
|
void validatePosition(Object const & object, Vector const & position);
|
|
AxialBox const getTangibleExtentInternal(Object const &obj);
|
|
|
|
#if PROFILE_GETTRANSFORM_O2W == 1
|
|
bool ms_debugReport;
|
|
int ms_totalCalls;
|
|
int ms_totalAttachedCalls;
|
|
int ms_totalAttachedDirtyCalls;
|
|
|
|
void debugReport();
|
|
#endif
|
|
|
|
std::string const & ms_debugInfoSectionName = "Object";
|
|
}
|
|
using namespace ObjectNamespace;
|
|
|
|
// ======================================================================
|
|
|
|
void Object::install(bool objectsAlterChildrenAndContents)
|
|
{
|
|
NotificationListManager::install();
|
|
ms_transformMemoryBlockManager = new MemoryBlockManager("Object::ms_transformMemoryBlockManager", true, sizeof(Transform), 0, 0, 0);
|
|
ms_objectsAlterChildrenAndContents = objectsAlterChildrenAndContents;
|
|
ExitChain::add(&ObjectNamespace::remove, "Object::remove");
|
|
DebugFlags::registerFlag(ms_objectsAlterChildrenAndContents, "SharedObject", "objectsAlterChildrenAndContents");
|
|
DebugFlags::registerFlag(ms_reportPropertySearchStatistics, "SharedObject", "reportPropertySearchStatistics", reportPropertySearchStatistics);
|
|
DebugFlags::registerFlag(ms_logObjectDelete, "SharedObject", "logObjectDelete");
|
|
DebugFlags::registerFlag(ms_validateObjectPosition, "SharedObject", "validateObjectPosition");
|
|
#if PROFILE_GETTRANSFORM_O2W == 1
|
|
DebugFlags::registerFlag(ms_debugReport, "SharedObject/Object", "debugReport", ObjectNamespace::debugReport);
|
|
#endif
|
|
|
|
ms_crashReportInfo[0] = '\0';
|
|
CrashReportInformation::addDynamicText(ms_crashReportInfo);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::shouldObjectsAlterChildrenAndContents()
|
|
{
|
|
return ms_objectsAlterChildrenAndContents;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void ObjectNamespace::reportPropertySearchStatistics()
|
|
{
|
|
DEBUG_REPORT_PRINT(true, ("Object property searches (%d):\n", ms_propertySearchesPerFrame));
|
|
ms_propertySearchesPerFrame = 0;
|
|
PropertySearchStatistics ::const_iterator const iEnd = ms_propertySearchStatistics.end();
|
|
for (PropertySearchStatistics::const_iterator i = ms_propertySearchStatistics.begin(); i != iEnd; ++i)
|
|
DEBUG_REPORT_PRINT(true, (" %08lx %d\n", i->first, i->second));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
#if PROFILE_GETTRANSFORM_O2W == 1
|
|
|
|
void ObjectNamespace::debugReport()
|
|
{
|
|
DEBUG_REPORT_PRINT(true, ("totalCalls=%d\n", ms_totalCalls));
|
|
DEBUG_REPORT_PRINT(true, ("totalAttachedCalls=%d\n", ms_totalAttachedCalls));
|
|
DEBUG_REPORT_PRINT(true, ("totalAttachedDirtyCalls=%d\n", ms_totalAttachedDirtyCalls));
|
|
}
|
|
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void ObjectNamespace::remove()
|
|
{
|
|
CrashReportInformation::removeDynamicText(ms_crashReportInfo);
|
|
|
|
DebugFlags::unregisterFlag(ms_objectsAlterChildrenAndContents);
|
|
DebugFlags::unregisterFlag(ms_reportPropertySearchStatistics);
|
|
DebugFlags::unregisterFlag(ms_logObjectDelete);
|
|
DebugFlags::unregisterFlag(ms_validateObjectPosition);
|
|
#if PROFILE_GETTRANSFORM_O2W == 1
|
|
DebugFlags::unregisterFlag(ms_debugReport);
|
|
#endif
|
|
|
|
delete ms_transformMemoryBlockManager;
|
|
ms_transformMemoryBlockManager = nullptr;
|
|
|
|
DEBUG_WARNING(static_cast<int>(ms_freeDpvsObjectsList.size()) != ms_allocatedDpvsObjects, ("Leaked %d DpvsObjects lists", ms_allocatedDpvsObjects - static_cast<int>(ms_freeDpvsObjectsList.size())));
|
|
while (!ms_systemAllocatedDpvsObjectsList.empty())
|
|
{
|
|
delete [] ms_systemAllocatedDpvsObjectsList.back();
|
|
ms_systemAllocatedDpvsObjectsList.pop_back();
|
|
}
|
|
|
|
while (!ms_propertyListList.empty())
|
|
{
|
|
delete ms_propertyListList.back();
|
|
ms_propertyListList.pop_back();
|
|
}
|
|
|
|
while (!ms_attachedObjectsList.empty())
|
|
{
|
|
delete ms_attachedObjectsList.back();
|
|
ms_attachedObjectsList.pop_back();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Transform *ObjectNamespace::newLocalTransform()
|
|
{
|
|
void *memory = ms_transformMemoryBlockManager->allocate();
|
|
return new(memory) Transform;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void ObjectNamespace::deleteLocalTransform(Transform *transform)
|
|
{
|
|
void *const transformMemory = transform;
|
|
transform->~Transform();
|
|
|
|
ms_transformMemoryBlockManager->free(transformMemory);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void ObjectNamespace::validatePosition(Object const & object, Vector const & position)
|
|
{
|
|
if (std::abs(position.x) > 16000.0f || std::abs(position.y) > 16000.0f || std::abs(position.z) > 16000.0f)
|
|
{
|
|
Object const * parent = object.getAttachedTo();
|
|
|
|
WARNING_STRICT_FATAL(ms_validateObjectPosition, ("Position (%f,%f,%f) is out-of-world and is invalid for object id=[%s], template=[%s], pointer=[%p], appearance=[%s], parent id=[%s], template=[%s], pointer=[%p], appearance=[%s]\n",
|
|
position.x,
|
|
position.y,
|
|
position.z,
|
|
object.getNetworkId().getValueString().c_str(),
|
|
object.getObjectTemplateName(),
|
|
&object,
|
|
object.getAppearance() ? object.getAppearance()->getAppearanceTemplateName() : nullptr,
|
|
parent ? parent->getNetworkId().getValueString().c_str() : nullptr,
|
|
parent ? parent->getObjectTemplateName() : nullptr,
|
|
parent ? parent : nullptr,
|
|
parent && parent->getAppearance() ? parent->getAppearance()->getAppearanceTemplateName() : nullptr));
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object::DpvsObjects *ObjectNamespace::newDpvsObjects()
|
|
{
|
|
if (ms_freeDpvsObjectsList.empty())
|
|
{
|
|
// allocate them in bulk from the global memory manager
|
|
const int count = 256;
|
|
Object::DpvsObjects *dpvsObjectListArray = new Object::DpvsObjects[count];
|
|
ms_systemAllocatedDpvsObjectsList.push_back(dpvsObjectListArray);
|
|
ms_allocatedDpvsObjects += count;
|
|
for (int i = 0; i < count; ++i)
|
|
ms_freeDpvsObjectsList.push_back(dpvsObjectListArray+i);
|
|
}
|
|
|
|
Object::DpvsObjects *result = ms_freeDpvsObjectsList.back();
|
|
ms_freeDpvsObjectsList.pop_back();
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void ObjectNamespace::deleteDpvsObjects(Object::DpvsObjects *dpvsObjectList)
|
|
{
|
|
if (!dpvsObjectList->empty())
|
|
{
|
|
DEBUG_FATAL(true, ("leaked [%d] dpvsObjects", static_cast<int>(dpvsObjectList->size())));
|
|
dpvsObjectList->clear(); //lint !e527 // unreachable // Reachable in release.
|
|
}
|
|
ms_freeDpvsObjectsList.push_back(dpvsObjectList);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
PropertyList * ObjectNamespace::newPropertyList()
|
|
{
|
|
if (ms_propertyListList.empty())
|
|
{
|
|
for (int i = 0; i < 256; ++i)
|
|
ms_propertyListList.push_back(new PropertyList);
|
|
}
|
|
|
|
PropertyList * const result = ms_propertyListList.back();
|
|
ms_propertyListList.pop_back();
|
|
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void ObjectNamespace::deletePropertyList(PropertyList * const propertyList)
|
|
{
|
|
if (ms_propertyListList.size() < 256)
|
|
{
|
|
DEBUG_FATAL(!propertyList->empty(), ("propertyList is not empty"));
|
|
propertyList->clear();
|
|
ms_propertyListList.push_back(propertyList);
|
|
}
|
|
else
|
|
delete propertyList;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
AttachedObjects * ObjectNamespace::newAttachedObjects()
|
|
{
|
|
if (ms_attachedObjectsList.empty())
|
|
{
|
|
for (int i = 0; i < 256; ++i)
|
|
ms_attachedObjectsList.push_back(new AttachedObjects);
|
|
}
|
|
|
|
AttachedObjects * const result = ms_attachedObjectsList.back();
|
|
ms_attachedObjectsList.pop_back();
|
|
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void ObjectNamespace::deleteAttachedObjects(AttachedObjects * const attachedObjects)
|
|
{
|
|
if (ms_attachedObjectsList.size() < 256)
|
|
{
|
|
DEBUG_FATAL(!attachedObjects->empty(), ("attachedObjects is not empty"));
|
|
attachedObjects->clear();
|
|
ms_attachedObjectsList.push_back(attachedObjects);
|
|
}
|
|
else
|
|
delete attachedObjects;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setDpvsObjectHookFunctions(InWorldAddDpvsObjectHookFunction inWorldAddDpvsObjectHookFunction, InWorldRemoveDpvsObjectHookFunction inWorldRemoveDpvsObjectHookFunction, LeakedDpvsObjectHookFunction leakedDpvsObjectHookFunction)
|
|
{
|
|
ms_inWorldAddDpvsObjectHookFunction = inWorldAddDpvsObjectHookFunction;
|
|
ms_inWorldRemoveDpvsObjectHookFunction = inWorldRemoveDpvsObjectHookFunction;
|
|
ms_leakedDpvsObjectHookFunction = leakedDpvsObjectHookFunction;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setDisallowObjectDelete(bool const disallowObjectDelete)
|
|
{
|
|
ms_disallowObjectDelete = disallowObjectDelete;
|
|
}
|
|
|
|
// ======================================================================
|
|
// Construct an object
|
|
|
|
Object::Object():
|
|
m_propertyList(newPropertyList()),
|
|
m_inWorld(false),
|
|
m_active(true),
|
|
m_kill(false),
|
|
m_authoritative(false),
|
|
m_childObject(false),
|
|
m_objectToWorldDirty(true),
|
|
#if OBJECT_SUPPORTS_DESTROYED_FLAG
|
|
m_isDestroyed(false),
|
|
#endif
|
|
#if OBJECT_SUPPORTS_IS_ALTERING_FLAG
|
|
m_isAltering(false),
|
|
#endif
|
|
m_objectTemplate(nullptr),
|
|
m_notificationList(NotificationListManager::getEmptyNotificationList()),
|
|
m_debugName(nullptr),
|
|
m_networkId(NetworkId::cms_invalid),
|
|
m_appearance(nullptr),
|
|
m_controller(nullptr),
|
|
m_dynamics(nullptr),
|
|
m_attachedToObject(nullptr),
|
|
m_attachedObjects(nullptr),
|
|
m_dpvsObjects(nullptr),
|
|
m_rotations(0),
|
|
m_scale(Vector::xyz111),
|
|
m_objectToParent(),
|
|
m_objectToWorld(nullptr),
|
|
m_watchedByList(),
|
|
m_containerProperty(nullptr),
|
|
m_collisionProperty(nullptr),
|
|
m_spatialSubdivisionHandle (0),
|
|
m_useAlterScheduler(true),
|
|
m_scheduleData(nullptr),
|
|
m_shouldBakeIntoMesh(true),
|
|
m_defaultAppearance(nullptr),
|
|
m_alternateAppearance(nullptr),
|
|
m_containedBy(nullptr)
|
|
{
|
|
m_defaultAppearance = m_appearance;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object::Object(const ObjectTemplate *objectTemplate, const NetworkId &networkId):
|
|
m_propertyList(newPropertyList()),
|
|
m_inWorld(false),
|
|
m_active(true),
|
|
m_kill(false),
|
|
m_authoritative(false),
|
|
m_childObject(false),
|
|
m_objectToWorldDirty(true),
|
|
#if OBJECT_SUPPORTS_DESTROYED_FLAG
|
|
m_isDestroyed(false),
|
|
#endif
|
|
#if OBJECT_SUPPORTS_IS_ALTERING_FLAG
|
|
m_isAltering(false),
|
|
#endif
|
|
m_objectTemplate(objectTemplate),
|
|
m_notificationList(NotificationListManager::getEmptyNotificationList()),
|
|
m_debugName(0),
|
|
m_networkId(networkId),
|
|
m_appearance(0),
|
|
m_controller(nullptr),
|
|
m_dynamics(nullptr),
|
|
m_attachedToObject(nullptr),
|
|
m_attachedObjects(nullptr),
|
|
m_dpvsObjects(nullptr),
|
|
m_rotations(0),
|
|
m_scale(Vector::xyz111),
|
|
m_objectToParent(),
|
|
m_objectToWorld(nullptr),
|
|
m_watchedByList(),
|
|
m_containerProperty(nullptr),
|
|
m_collisionProperty(nullptr),
|
|
m_spatialSubdivisionHandle (0),
|
|
m_useAlterScheduler(true),
|
|
m_scheduleData(nullptr),
|
|
m_shouldBakeIntoMesh(true),
|
|
m_defaultAppearance(nullptr),
|
|
m_alternateAppearance(nullptr),
|
|
m_containedBy(nullptr)
|
|
{
|
|
objectTemplate->addReference();
|
|
NetworkIdManager::addObject(*this);
|
|
|
|
m_defaultAppearance = m_appearance;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object::Object(const ObjectTemplate *objectTemplate, InitializeFlag):
|
|
m_propertyList(newPropertyList()),
|
|
m_inWorld(false),
|
|
m_active(true),
|
|
m_kill(false),
|
|
m_authoritative(false),
|
|
m_childObject(false),
|
|
m_objectToWorldDirty(true),
|
|
#if OBJECT_SUPPORTS_DESTROYED_FLAG
|
|
m_isDestroyed(false),
|
|
#endif
|
|
#if OBJECT_SUPPORTS_IS_ALTERING_FLAG
|
|
m_isAltering(false),
|
|
#endif
|
|
m_objectTemplate(objectTemplate),
|
|
m_notificationList(NotificationListManager::getEmptyNotificationList()),
|
|
m_debugName(0),
|
|
m_networkId(NetworkId::cms_invalid),
|
|
m_appearance(0),
|
|
m_controller(nullptr),
|
|
m_dynamics(nullptr),
|
|
m_attachedToObject(nullptr),
|
|
m_attachedObjects(nullptr),
|
|
m_dpvsObjects(nullptr),
|
|
m_rotations(0),
|
|
m_scale(Vector::xyz111),
|
|
m_objectToParent(),
|
|
m_objectToWorld(nullptr),
|
|
m_watchedByList(),
|
|
m_containerProperty(nullptr),
|
|
m_collisionProperty(nullptr),
|
|
m_spatialSubdivisionHandle (0),
|
|
m_useAlterScheduler(true),
|
|
m_scheduleData(nullptr),
|
|
m_shouldBakeIntoMesh(true),
|
|
m_defaultAppearance(nullptr),
|
|
m_alternateAppearance(nullptr),
|
|
m_containedBy(nullptr)
|
|
{
|
|
objectTemplate->addReference();
|
|
NetworkIdManager::addObject(*this);
|
|
|
|
m_defaultAppearance = m_appearance;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Destroy an object.
|
|
*/
|
|
|
|
Object::~Object(void)
|
|
{
|
|
//-- Store the name of hte most recently deleted object so we can track down live crashes if it fails somewhere in here.
|
|
IGNORE_RETURN(snprintf(ms_crashReportInfo, sizeof(ms_crashReportInfo) - 1, "~Object: name=[%s] template=[%s]\n", getDebugName(), getObjectTemplateName()));
|
|
ms_crashReportInfo[sizeof(ms_crashReportInfo) - 1] = '\0';
|
|
|
|
DEBUG_REPORT_LOG(ms_logObjectDelete, ("Deleting object id=[%s], template=[%s], pointer=[%p], appearance=[%s], parent id=[%s], template=[%s], pointer=[%p], appearance=[%s]\n", getNetworkId().getValueString().c_str(), getObjectTemplateName(), this, getAppearance() ? getAppearance()->getAppearanceTemplateName() : nullptr, m_attachedToObject ? m_attachedToObject->getNetworkId().getValueString().c_str() : nullptr, m_attachedToObject ? m_attachedToObject->getObjectTemplateName() : nullptr, m_attachedToObject ? m_attachedToObject : nullptr, m_attachedToObject && m_attachedToObject->getAppearance() ? m_attachedToObject->getAppearance()->getAppearanceTemplateName() : nullptr));
|
|
FATAL(ConfigSharedObject::getAllowDisallowObjectDelete() && ms_disallowObjectDelete, ("Object id=[%s], template=[%s], pointer=[%p] is deleting itself when delete is not allowed", getNetworkId().getValueString().c_str(), getObjectTemplateName(), this));
|
|
|
|
#if OBJECT_SUPPORTS_IS_ALTERING_FLAG
|
|
FATAL(m_isAltering, ("Object id=[%s], template=[%s], pointer=[%p] is deleting itself while it is getting altered. Unsupported, fix object.", getNetworkId().getValueString().c_str(), getObjectTemplateName(), this));
|
|
#endif
|
|
|
|
if (m_inWorld)
|
|
Object::removeFromWorld();
|
|
|
|
if (m_scheduleData)
|
|
{
|
|
bool const wasInAlterScheduler = AlterScheduler::removeObject(*this);
|
|
UNREF(wasInAlterScheduler);
|
|
|
|
delete m_scheduleData;
|
|
m_scheduleData = 0;
|
|
}
|
|
|
|
#if OBJECT_SUPPORTS_DESTROYED_FLAG
|
|
// make sure we can tell this object is getting destroyed.
|
|
m_isDestroyed = true;
|
|
#endif
|
|
|
|
// remove object from parent (make sure it's in the world cell)
|
|
if (m_attachedToObject)
|
|
{
|
|
detachFromObject(DF_world);
|
|
m_attachedToObject = 0;
|
|
}
|
|
|
|
// destroy all the properties
|
|
while(!m_propertyList->empty())
|
|
{
|
|
Property * p = m_propertyList->back();
|
|
m_propertyList->pop_back();
|
|
delete p;
|
|
}
|
|
m_containerProperty = 0;
|
|
m_collisionProperty = 0;
|
|
m_containedBy = 0;
|
|
|
|
deletePropertyList(m_propertyList);
|
|
|
|
delete m_defaultAppearance;
|
|
delete m_alternateAppearance;
|
|
|
|
m_appearance = 0;
|
|
m_defaultAppearance = 0;
|
|
m_alternateAppearance = 0;
|
|
|
|
// destroy children
|
|
if (m_attachedObjects)
|
|
{
|
|
while (!m_attachedObjects->empty())
|
|
{
|
|
Object *child = m_attachedObjects->back();
|
|
if (!child->isChildObject())
|
|
{
|
|
WARNING_STRICT_FATAL(true, ("deleting object with non-child attached objects, parent id=[%s], objectTemplate=[%s], appearanceTemplate=[%s], child id=[%s], ptr=[0x%08x], objectTemplate=[%s], appearanceTemplate=[%s], name=[%s])", getNetworkId().getValueString().c_str(), getObjectTemplateName(), getAppearance() && getAppearance()->getAppearanceTemplate() ? getAppearance()->getAppearanceTemplate()->getName() : 0, child->getNetworkId().getValueString().c_str(), child, child->getObjectTemplateName(), child->getAppearance() && child->getAppearance()->getAppearanceTemplate() ? child->getAppearance()->getAppearanceTemplate()->getName() : 0, child->getDebugName()));
|
|
child->detachFromObject(DF_parent);
|
|
}
|
|
else
|
|
{
|
|
delete child;
|
|
}
|
|
}
|
|
|
|
deleteAttachedObjects(m_attachedObjects);
|
|
m_attachedObjects = nullptr;
|
|
}
|
|
|
|
if (m_objectToWorld)
|
|
{
|
|
deleteLocalTransform(m_objectToWorld);
|
|
m_objectToWorld = 0;
|
|
}
|
|
|
|
if (m_objectTemplate != nullptr)
|
|
m_objectTemplate->releaseReference();
|
|
m_objectTemplate = nullptr;
|
|
|
|
m_notificationList = 0;
|
|
|
|
if (m_dpvsObjects)
|
|
{
|
|
if (!m_dpvsObjects->empty())
|
|
{
|
|
WARNING(true, ("Object still has %d DPVS objects attached", static_cast<int>(m_dpvsObjects->size())));
|
|
|
|
if (ms_leakedDpvsObjectHookFunction)
|
|
{
|
|
DpvsObjects::iterator const iEnd = m_dpvsObjects->end();
|
|
for (DpvsObjects::iterator i = m_dpvsObjects->begin(); i != iEnd; ++i)
|
|
{
|
|
ms_leakedDpvsObjectHookFunction(*i);
|
|
}
|
|
}
|
|
|
|
m_dpvsObjects->clear();
|
|
|
|
}
|
|
|
|
deleteDpvsObjects(m_dpvsObjects);
|
|
m_dpvsObjects = 0;
|
|
}
|
|
|
|
delete m_controller;
|
|
m_controller = 0;
|
|
|
|
delete m_dynamics;
|
|
m_dynamics = 0;
|
|
|
|
delete [] m_debugName;
|
|
m_debugName = 0;
|
|
|
|
NetworkIdManager::removeObject(*this);
|
|
|
|
m_spatialSubdivisionHandle = 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
#if OBJECT_SUPPORTS_DESTROYED_FLAG
|
|
|
|
bool Object::isDestroyed() const
|
|
{
|
|
return m_isDestroyed;
|
|
}
|
|
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
#if OBJECT_SUPPORTS_IS_ALTERING_FLAG
|
|
|
|
bool Object::isAltering() const
|
|
{
|
|
return m_isAltering;
|
|
}
|
|
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Set a new name for the object.
|
|
*
|
|
* If the object had a name, that name will be deleted. The new name will
|
|
* be copied into a dynamically allocated buffer using DuplicateString().
|
|
*
|
|
* @param newName New name for the object
|
|
*/
|
|
|
|
void Object::setDebugName(const char *newName)
|
|
{
|
|
delete [] m_debugName;
|
|
m_debugName = 0;
|
|
|
|
if (newName)
|
|
m_debugName = DuplicateString(newName);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
std::string const Object::getDebugInformation(bool const includeParent) const
|
|
{
|
|
char buffer[512];
|
|
|
|
if (includeParent && m_attachedToObject)
|
|
{
|
|
IGNORE_RETURN(snprintf(buffer, sizeof(buffer) - 1, "[ptr=%p, id=%s, ot=%s, at=%s, child=%i, pptr=%p, pid=%s, pot=%s, pat=%s]",
|
|
this,
|
|
getNetworkId().getValueString().c_str(),
|
|
getObjectTemplateName(),
|
|
getAppearanceTemplateName(),
|
|
isChildObject() ? 1 : 0,
|
|
m_attachedToObject,
|
|
m_attachedToObject->getNetworkId().getValueString().c_str(),
|
|
m_attachedToObject->getObjectTemplateName(),
|
|
m_attachedToObject->getAppearanceTemplateName()));
|
|
}
|
|
else
|
|
{
|
|
IGNORE_RETURN(snprintf(buffer, sizeof(buffer) - 1, "[ptr=%p, id=%s, ot=%s, at=%s]",
|
|
this,
|
|
getNetworkId().getValueString().c_str(),
|
|
getObjectTemplateName(),
|
|
getAppearanceTemplateName()));
|
|
}
|
|
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::hasNotification(const ObjectNotification ¬ification) const
|
|
{
|
|
return m_notificationList->contains(notification);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::addNotification(const ObjectNotification ¬ification, bool allowWhenInWorld)
|
|
{
|
|
UNREF(allowWhenInWorld);
|
|
DEBUG_FATAL(m_inWorld && !allowWhenInWorld, ("Cannot change notification while part of the world"));
|
|
|
|
if (m_notificationList->contains(notification))
|
|
{
|
|
DEBUG_WARNING(true, ("Adding notification that is already present"));
|
|
return;
|
|
}
|
|
m_notificationList = NotificationListManager::addNotification(m_notificationList, notification);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::removeNotification(const ObjectNotification ¬ification, bool allowWhenInWorld)
|
|
{
|
|
UNREF(allowWhenInWorld);
|
|
DEBUG_FATAL(m_inWorld && !allowWhenInWorld, ("Cannot change notification while part of the world"));
|
|
DEBUG_FATAL(!m_notificationList->contains(notification), ("Removing notification that does not exist"));
|
|
m_notificationList = NotificationListManager::removeNotification(m_notificationList, notification);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::addToWorld()
|
|
{
|
|
if (m_inWorld)
|
|
{
|
|
DEBUG_WARNING(true, ("Object (%s : %s) is already in the world.", getObjectTemplateName(), getNetworkId().getValueString().c_str()));
|
|
return;
|
|
}
|
|
|
|
setObjectToWorldDirty(true);
|
|
|
|
m_inWorld = true;
|
|
|
|
if (m_appearance)
|
|
m_appearance->addToWorld();
|
|
|
|
{
|
|
PropertyList::iterator iEnd = m_propertyList->end();
|
|
for (PropertyList::iterator i = m_propertyList->begin(); i != iEnd; ++i)
|
|
(*i)->addToWorld();
|
|
}
|
|
|
|
m_notificationList->addToWorld(*this);
|
|
|
|
if (m_attachedObjects && !m_attachedObjects->empty())
|
|
{
|
|
const AttachedObjects::iterator iEnd = m_attachedObjects->end();
|
|
for (AttachedObjects::iterator i = m_attachedObjects->begin(); i != iEnd; ++i)
|
|
// Do not add attached child objects that are already in the world.
|
|
// This happens when an object has a PortalProperty in its PropertyList.
|
|
if ((*i)->isChildObject() && !(*i)->isInWorld())
|
|
(*i)->addToWorld();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Remove an object from the world.
|
|
*
|
|
* The notifications will be called in the reverse order.
|
|
*/
|
|
|
|
void Object::removeFromWorld()
|
|
{
|
|
if (!m_inWorld)
|
|
{
|
|
WARNING_STRICT_FATAL(true, ("Object %s not in the world", getDebugInformation(true).c_str()));
|
|
return;
|
|
}
|
|
|
|
setObjectToWorldDirty(true);
|
|
|
|
{
|
|
for (PropertyList::iterator i = m_propertyList->begin(); i != m_propertyList->end(); ++i)
|
|
(*i)->removeFromWorld();
|
|
}
|
|
|
|
m_notificationList->removeFromWorld(*this);
|
|
|
|
if (m_appearance)
|
|
m_appearance->removeFromWorld();
|
|
|
|
if (m_attachedObjects && !m_attachedObjects->empty())
|
|
{
|
|
const AttachedObjects temp(*m_attachedObjects);
|
|
const AttachedObjects::const_iterator end = temp.end();
|
|
for (AttachedObjects::const_iterator i = temp.begin(); i != end; ++i)
|
|
{
|
|
Object *attached= *i;
|
|
if (attached->isChildObject())
|
|
attached->removeFromWorld();
|
|
else
|
|
{
|
|
if (attached->isInWorld())
|
|
{
|
|
DEBUG_WARNING(true, ("Removing an object [id=%s template=%s] from the world with non-child attached objects [id=%s ptr=0x%08x template=%s name=%s]", getNetworkId ().getValueString ().c_str (), getObjectTemplateName () ? getObjectTemplateName () : "nullptr", attached->getNetworkId ().getValueString ().c_str (), attached, attached->getObjectTemplateName () ? attached->getObjectTemplateName () : "nullptr", attached->getDebugName()));
|
|
attached->detachFromObject(DF_parent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_inWorld = false;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::addDpvsObject(DPVS::Object *dpvsObject)
|
|
{
|
|
NOT_NULL(dpvsObject);
|
|
if (!m_dpvsObjects)
|
|
m_dpvsObjects = newDpvsObjects();
|
|
|
|
DpvsObjects::iterator i = std::find(m_dpvsObjects->begin(), m_dpvsObjects->end(), dpvsObject);
|
|
|
|
if (i != m_dpvsObjects->end())
|
|
{
|
|
DEBUG_FATAL(true, ("Object::addDpvsObject() Adding duplicate DPVS object [0x%08x] to Object %s", dpvsObject, getDebugInformation().c_str()));
|
|
return;
|
|
}
|
|
|
|
m_dpvsObjects->push_back(dpvsObject);
|
|
|
|
if (ms_inWorldAddDpvsObjectHookFunction && isInWorld())
|
|
ms_inWorldAddDpvsObjectHookFunction(this, dpvsObject);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::removeDpvsObject(DPVS::Object *dpvsObject)
|
|
{
|
|
NOT_NULL(dpvsObject);
|
|
NOT_NULL(m_dpvsObjects);
|
|
|
|
DpvsObjects::iterator i = std::find(m_dpvsObjects->begin(), m_dpvsObjects->end(), dpvsObject);
|
|
|
|
if (i == m_dpvsObjects->end())
|
|
{
|
|
DEBUG_FATAL(true, ("Object::removeDpvsObject() Removing unknown DPVS object [0x%08x] from Object %s", dpvsObject, getDebugInformation().c_str()));
|
|
return;
|
|
}
|
|
|
|
*i = m_dpvsObjects->back();
|
|
m_dpvsObjects->pop_back();
|
|
|
|
if (ms_inWorldRemoveDpvsObjectHookFunction && isInWorld())
|
|
ms_inWorldRemoveDpvsObjectHookFunction(dpvsObject);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::positionChanged(bool dueToParentChange, const Vector &oldPosition)
|
|
{
|
|
#ifdef _DEBUG
|
|
IGNORE_RETURN(getTransform_o2p().validate());
|
|
IGNORE_RETURN(getTransform_o2w().validate());
|
|
#endif
|
|
|
|
setObjectToWorldDirty(true);
|
|
|
|
if (m_inWorld)
|
|
{
|
|
validatePosition(*this, getPosition_p());
|
|
|
|
m_notificationList->positionChanged(*this, dueToParentChange, oldPosition);
|
|
|
|
if (m_attachedObjects && !m_attachedObjects->empty())
|
|
{
|
|
const AttachedObjects::iterator end = m_attachedObjects->end();
|
|
for (AttachedObjects::iterator i = m_attachedObjects->begin(); i != end; ++i)
|
|
(*i)->positionChanged(true, (*i)->getPosition_p());
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::rotationChanged(bool dueToParentChange)
|
|
{
|
|
#ifdef _DEBUG
|
|
IGNORE_RETURN(getTransform_o2p().validate());
|
|
IGNORE_RETURN(getTransform_o2w().validate());
|
|
#endif
|
|
|
|
setObjectToWorldDirty(true);
|
|
|
|
if (m_inWorld)
|
|
{
|
|
m_notificationList->rotationChanged(*this, dueToParentChange);
|
|
|
|
if (m_attachedObjects && !m_attachedObjects->empty())
|
|
{
|
|
const AttachedObjects::iterator end = m_attachedObjects->end();
|
|
for (AttachedObjects::iterator i = m_attachedObjects->begin(); i != end; ++i)
|
|
(*i)->rotationChanged(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::positionAndRotationChanged(bool dueToParentChange, const Vector &oldPosition)
|
|
{
|
|
#ifdef _DEBUG
|
|
IGNORE_RETURN(getTransform_o2p().validate());
|
|
IGNORE_RETURN(getTransform_o2w().validate());
|
|
#endif
|
|
|
|
setObjectToWorldDirty(true);
|
|
|
|
if (m_inWorld)
|
|
{
|
|
validatePosition(*this, getPosition_p());
|
|
|
|
m_notificationList->positionAndRotationChanged(*this, dueToParentChange, oldPosition);
|
|
|
|
if (m_attachedObjects && !m_attachedObjects->empty())
|
|
{
|
|
const AttachedObjects::iterator end = m_attachedObjects->end();
|
|
for (AttachedObjects::iterator i = m_attachedObjects->begin(); i != end; ++i)
|
|
(*i)->positionAndRotationChanged(true, (*i)->getPosition_p());
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::cellChanged(bool dueToParentChange)
|
|
{
|
|
setObjectToWorldDirty(true);
|
|
|
|
if (m_inWorld)
|
|
{
|
|
m_notificationList->cellChanged(*this, dueToParentChange);
|
|
|
|
if (m_attachedObjects && !m_attachedObjects->empty())
|
|
{
|
|
const AttachedObjects::iterator end = m_attachedObjects->end();
|
|
for (AttachedObjects::iterator i = m_attachedObjects->begin(); i != end; ++i)
|
|
(*i)->cellChanged(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::extentChanged()
|
|
{
|
|
if (m_inWorld)
|
|
m_notificationList->extentChanged(*this);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::pobFixupComplete()
|
|
{
|
|
if (m_inWorld)
|
|
m_notificationList->pobFixupComplete(*this);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Set an active state for the object.
|
|
*
|
|
* Inactive objects will not render.
|
|
*
|
|
* @param newActive New active state for the object
|
|
*/
|
|
|
|
void Object::setActive(bool newActive)
|
|
{
|
|
m_active = newActive;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Set a new dynamics for the object.
|
|
*
|
|
* The old dynamics pointer is simply overwritten.
|
|
*
|
|
* @param newDynamics New dynamics for the object
|
|
*/
|
|
|
|
void Object::setDynamics(Dynamics *newDynamics)
|
|
{
|
|
if (m_dynamics == newDynamics)
|
|
return;
|
|
|
|
delete m_dynamics;
|
|
m_dynamics = newDynamics;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Set a new controller for the object.
|
|
*
|
|
* The old controller pointer is simply overwritten.
|
|
*
|
|
* @param newController New controller for the object
|
|
*/
|
|
|
|
void Object::setController(Controller *newController)
|
|
{
|
|
if (m_controller == newController)
|
|
return;
|
|
|
|
delete m_controller;
|
|
m_controller = newController;
|
|
}
|
|
// ----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Determine if the object currently resides in the World cell
|
|
* @return true if in the world cell, else false
|
|
*/
|
|
|
|
bool Object::isInWorldCell() const
|
|
{
|
|
return getParentCell() == CellProperty::getWorldCellProperty();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
CellProperty *Object::getParentCell() const
|
|
{
|
|
Property *cell = nullptr;
|
|
|
|
for (Object *o = const_cast<Object *>(getAttachedTo()); o && !cell; o = o->getAttachedTo())
|
|
cell = o->getCellProperty();
|
|
|
|
if (!cell)
|
|
return CellProperty::getWorldCellProperty();
|
|
|
|
return safe_cast<CellProperty*>(cell);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setParentCell(CellProperty *cellProperty)
|
|
{
|
|
NOT_NULL(cellProperty);
|
|
|
|
// if we are already in that cell, don't do anything.
|
|
if (getParentCell() == cellProperty)
|
|
return;
|
|
|
|
// if we were in another cell, detach us. This will leave out object in world space.
|
|
if (!isInWorldCell())
|
|
detachFromObject(DF_world);
|
|
|
|
// we are now relative to the new cell
|
|
if (cellProperty != CellProperty::getWorldCellProperty())
|
|
attachToObject_w(&cellProperty->getOwner(), false);
|
|
|
|
// issue a cell notification change
|
|
cellChanged(false);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the object-to-world transformation for this object.
|
|
*
|
|
* @return Const reference to the object-to-world transform for this object
|
|
*/
|
|
|
|
const Transform &Object::getTransform_o2w() const
|
|
{
|
|
#if PROFILE_GETTRANSFORM_O2W == 1
|
|
++ms_totalCalls;
|
|
#endif
|
|
|
|
if (m_attachedToObject)
|
|
{
|
|
#if PROFILE_GETTRANSFORM_O2W == 1
|
|
++ms_totalAttachedCalls;
|
|
#endif
|
|
|
|
#if ENABLE_OBJECTTOWORLDDIRTY == 1
|
|
if (m_objectToWorldDirty)
|
|
#endif
|
|
{
|
|
#if PROFILE_GETTRANSFORM_O2W == 1
|
|
++ms_totalAttachedDirtyCalls;
|
|
#endif
|
|
|
|
m_objectToWorld->multiply(m_attachedToObject->getTransform_o2w(), m_objectToParent);
|
|
setObjectToWorldDirty(false);
|
|
}
|
|
|
|
return *m_objectToWorld;
|
|
}
|
|
|
|
return m_objectToParent;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setTransform_o2w(const Transform &objectToWorld)
|
|
{
|
|
CellProperty const * cell = getParentCell();
|
|
|
|
if(cell == CellProperty::getWorldCellProperty())
|
|
{
|
|
setTransform_o2p(objectToWorld);
|
|
return;
|
|
}
|
|
|
|
Transform const & cellToWorld = cell->getOwner().getTransform_o2w();
|
|
|
|
Transform worldToCell;
|
|
|
|
worldToCell.invert(cellToWorld);
|
|
|
|
Transform objectToCell;
|
|
|
|
objectToCell.multiply(worldToCell,objectToWorld);
|
|
|
|
setTransform_o2p(objectToCell);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the appearance-to-parent transformation for this object.
|
|
*
|
|
* @return Const reference to the object-to-parent transform for this object
|
|
*/
|
|
|
|
void Object::setTransform_a2w(const Transform &appearanceToWorld) const
|
|
{
|
|
if (!m_appearance)
|
|
{
|
|
Appearance *dummyAppearance = new Appearance(0);
|
|
const_cast<Object *>(this)->setAppearance(dummyAppearance);
|
|
}
|
|
|
|
m_appearance->setTransform_w(appearanceToWorld);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the appearance-to-parent transformation for this object.
|
|
*
|
|
* @return Const reference to the object-to-parent transform for this object
|
|
*/
|
|
|
|
const Transform &Object::getTransform_a2w() const
|
|
{
|
|
if (!m_appearance)
|
|
{
|
|
Appearance *dummyAppearance = new Appearance(0);
|
|
const_cast<Object *>(this)->setAppearance(dummyAppearance);
|
|
}
|
|
|
|
return m_appearance->getTransform_w();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
const Transform Object::getTransform_o2c() const
|
|
{
|
|
Transform result = getTransform_o2p();
|
|
|
|
for (const Object *o = getAttachedTo(); o && !o->getCellProperty(); o = o->getAttachedTo())
|
|
result.multiply(o->getTransform_o2p(), result);
|
|
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Set a new Appearance for the object.
|
|
*
|
|
* This routine will delete the old appearance and assign the specified
|
|
* appearance.
|
|
*
|
|
* @param newAppearance New appearance for the object
|
|
*/
|
|
|
|
void Object::setAppearance(Appearance *newAppearance)
|
|
{
|
|
if (m_appearance == newAppearance)
|
|
return;
|
|
|
|
|
|
if (m_appearance) // delete the old appearance
|
|
{
|
|
// DBE - Copy the transform from the old appearance to the new one.
|
|
// This was added to mimic the behavior of when the appearance-to-world
|
|
// transform was stored in Object instead of Appearance.
|
|
if (newAppearance)
|
|
{
|
|
newAppearance->setTransform_w(m_appearance->getTransform_w());
|
|
}
|
|
|
|
if (isInWorld())
|
|
{
|
|
m_appearance->removeFromWorld();
|
|
}
|
|
delete m_appearance;
|
|
}
|
|
else
|
|
{
|
|
// DBE - Initialize the transform of the new appearance to our object-to-world
|
|
// transform. This was added to mimic the behavior of when the appearance-to-world
|
|
// transform was stored in Object instead of Appearance.
|
|
newAppearance->setTransform_w(getTransform_o2w());
|
|
}
|
|
|
|
// start using the new appearance
|
|
m_appearance = newAppearance;
|
|
|
|
if (m_appearance)
|
|
{
|
|
m_appearance->setOwner(this);
|
|
m_appearance->setScale(m_scale);
|
|
|
|
if (isInWorld())
|
|
{
|
|
m_appearance->addToWorld();
|
|
}
|
|
}
|
|
|
|
m_defaultAppearance = m_appearance;
|
|
|
|
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Steal the Appearance from this object.
|
|
*
|
|
* This routine will return the current appearance of this object, and
|
|
* then reset its appearance to nullptr.
|
|
*
|
|
* @return The current appearance of this object
|
|
*/
|
|
|
|
Appearance *Object::stealAppearance(void)
|
|
{
|
|
Appearance *oldAppearance = m_appearance;
|
|
|
|
if(m_appearance == m_defaultAppearance)
|
|
m_defaultAppearance = nullptr;
|
|
else if (m_appearance == m_alternateAppearance)
|
|
m_alternateAppearance = nullptr;
|
|
|
|
m_appearance = nullptr;
|
|
|
|
if (oldAppearance)
|
|
{
|
|
if (isInWorld())
|
|
oldAppearance->removeFromWorld();
|
|
|
|
oldAppearance->setOwner(nullptr);
|
|
}
|
|
|
|
return oldAppearance;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the appearance's sphere's center.
|
|
*
|
|
* If the appearance doesn't exist, it returns the position of the object.
|
|
*/
|
|
|
|
const Vector &Object::getAppearanceSphereCenter(void) const
|
|
{
|
|
if (m_appearance)
|
|
return m_appearance->getSphere().getCenter();
|
|
else
|
|
return Vector::zero;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the appearance's sphere's radius.
|
|
*/
|
|
|
|
real Object::getAppearanceSphereRadius(void) const
|
|
{
|
|
if(!m_appearance)
|
|
return 0;
|
|
return m_appearance->getSphere().getRadius();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the appearance's sphere's center in world space.
|
|
*
|
|
* If the object does not have an appearance, this routine will return
|
|
* the same value as getPosition_w(), the object's world position.
|
|
*/
|
|
|
|
const Vector Object::getAppearanceSphereCenter_w(void) const
|
|
{
|
|
if (m_appearance)
|
|
return rotateTranslate_o2w(getAppearanceSphereCenter());
|
|
else
|
|
return getPosition_w();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Alter the object specifying the amount of time that has passed.
|
|
*
|
|
* If the object is active, and has a Controller, the Controller's alter() routine will
|
|
* be called.
|
|
*
|
|
* If the object is active, and has an Appearance, the Appearances's alter() routine will
|
|
* be called.
|
|
*
|
|
* If this object is active, it will call alter() for all its child objects,
|
|
*
|
|
* @param time Number of elapsed seconds
|
|
*/
|
|
|
|
float Object::alter(float time)
|
|
{
|
|
PROFILER_AUTO_BLOCK_DEFINE("Object::alter");
|
|
|
|
DEBUG_FATAL(!isInitialized(), ("Alter called on uninitialized object:pointer=[%p],id=[%s],template=[%s].", this, getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
|
|
if (m_kill)
|
|
return AlterResult::cms_kill;
|
|
|
|
// Allow Object components to dictate whether they need an alter.
|
|
float alterResult = AlterResult::cms_keepNoAlter;
|
|
float appearanceAlterResult = AlterResult::cms_keepNoAlter;
|
|
float controllerAlterResult = AlterResult::cms_keepNoAlter;
|
|
float dynamicsAlterResult = AlterResult::cms_keepNoAlter;
|
|
float childrenAlterResult = AlterResult::cms_keepNoAlter;
|
|
|
|
if (m_active)
|
|
{
|
|
// Note: characters assume appearances get an alter() prior to the controller getting an alter()
|
|
if (m_appearance)
|
|
{
|
|
appearanceAlterResult = m_appearance->alter(time);
|
|
AlterResult::incorporateAlterResult(alterResult, appearanceAlterResult);
|
|
}
|
|
|
|
if (m_controller)
|
|
{
|
|
controllerAlterResult = m_controller->alter(time);
|
|
AlterResult::incorporateAlterResult(alterResult, controllerAlterResult);
|
|
}
|
|
|
|
if (m_dynamics)
|
|
{
|
|
dynamicsAlterResult = m_dynamics->alter(time);
|
|
AlterResult::incorporateAlterResult(alterResult, dynamicsAlterResult);
|
|
}
|
|
|
|
if (ms_objectsAlterChildrenAndContents)
|
|
{
|
|
// alter all the child objects
|
|
if (m_attachedObjects)
|
|
{
|
|
//-- note: children may not remove other children
|
|
int size = static_cast<int>(m_attachedObjects->size ());
|
|
|
|
for (int i = 0; i < size;)
|
|
{
|
|
Object * const thisChild = (*m_attachedObjects)[static_cast<uint>(i)];
|
|
NOT_NULL (thisChild);
|
|
|
|
// handle child objects wanting to die
|
|
if (thisChild->isChildObject())
|
|
{
|
|
if (thisChild->isInitialized())
|
|
{
|
|
AlterScheduler::setMostRecentAlterTime(*thisChild);
|
|
float const childAlterResult = thisChild->alter(time);
|
|
if (childAlterResult == AlterResult::cms_kill) //lint !e777 // testing floats for equality // This is okay, we use constants for both set and test.
|
|
{
|
|
delete thisChild;
|
|
--size;
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
AlterResult::incorporateAlterResult(childrenAlterResult, childAlterResult);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_WARNING(true, ("Alter was about to be called on uninitialized child object [id=%s template=%s] with parent object [id=%s template=%s], skipping child alter", thisChild->getNetworkId().getValueString().c_str(), thisChild->getObjectTemplateName(), getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
++i;
|
|
}
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
|
|
AlterResult::incorporateAlterResult(alterResult, childrenAlterResult);
|
|
}
|
|
|
|
// alter all the contained objects
|
|
if (m_containerProperty)
|
|
m_containerProperty->alter(time);
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
AlterScheduler::countObjectAlter();
|
|
if (AlterScheduler::reportObjectAlters())
|
|
{
|
|
char const * appearanceType = m_appearance ? typeid(*m_appearance).name() : "none";
|
|
char const * controllerType = m_controller ? typeid(*m_controller).name() : "none";
|
|
char const * dynamicsType = m_dynamics ? typeid(*m_dynamics).name() : "none";
|
|
AlterScheduler::reportObjectAlter(this, appearanceType, appearanceAlterResult, controllerType, controllerAlterResult, dynamicsType, dynamicsAlterResult, childrenAlterResult, alterResult);
|
|
}
|
|
#endif
|
|
|
|
// @todo fix this up to return what's appropriate for the state of this instance.
|
|
return alterResult;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Conclude the object specifying the amount of time that has passed.
|
|
*
|
|
* If the object is active, and has a Controller, the Controller's conclude() routine will
|
|
* be called.
|
|
*
|
|
* If this object is active, it will call conclude() for all its child objects,
|
|
*
|
|
* @param time Number of elapsed seconds
|
|
*/
|
|
|
|
void Object::conclude()
|
|
{
|
|
if (isActive())
|
|
{
|
|
if (m_controller)
|
|
m_controller->conclude();
|
|
|
|
// conclude all the child objects
|
|
if (m_attachedObjects)
|
|
{
|
|
const AttachedObjects::iterator end = m_attachedObjects->end();
|
|
for (AttachedObjects::iterator i = m_attachedObjects->begin(); i != end; ++i)
|
|
if ((*i)->isChildObject())
|
|
(*i)->conclude ();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
int Object::getNumberOfAttachedObjects() const
|
|
{
|
|
if (!m_attachedObjects)
|
|
return 0;
|
|
|
|
return static_cast<int>(m_attachedObjects->size());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object * Object::getAttachedObject(int const attachedObjectIndex)
|
|
{
|
|
NOT_NULL(m_attachedObjects);
|
|
DEBUG_FATAL(attachedObjectIndex < 0 || attachedObjectIndex >= getNumberOfAttachedObjects(), ("attached object index out of range %d/%d", attachedObjectIndex, getNumberOfAttachedObjects()));
|
|
|
|
Object * const attached = (*m_attachedObjects)[static_cast<uint>(attachedObjectIndex)];
|
|
NOT_NULL (attached);
|
|
return attached;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object const * Object::getAttachedObject(int const attachedObjectIndex) const
|
|
{
|
|
NOT_NULL(m_attachedObjects);
|
|
DEBUG_FATAL(attachedObjectIndex < 0 || attachedObjectIndex >= getNumberOfAttachedObjects(), ("attached object index out of range %d/%d", attachedObjectIndex, getNumberOfAttachedObjects()));
|
|
|
|
Object const * const attached = (*m_attachedObjects)[static_cast<uint>(attachedObjectIndex)];
|
|
NOT_NULL (attached);
|
|
return attached;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the number of child objects of this object.
|
|
*
|
|
* This routine walks a linked list of the objects to count the number
|
|
* of them. If you only need to know if the Object has child objects,
|
|
* call the more efficient hasChildObjects() routine instead.
|
|
*
|
|
* @return The number of child objects of this object
|
|
* @see Object::hasChildObjects()
|
|
*/
|
|
|
|
int Object::getNumberOfChildObjects(void) const
|
|
{
|
|
// todo @portal: make this reflect only child objects, not attached objects
|
|
if (!m_attachedObjects)
|
|
return 0;
|
|
|
|
return static_cast<int> (m_attachedObjects->size());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the specified child object.
|
|
*
|
|
* If the childObjectIndex is out of range, this routine will call Fatal
|
|
* in debug compiles.
|
|
*
|
|
* @param childObjectIndex Index of the child object to retrieve
|
|
* @return Pointer to the child object
|
|
*/
|
|
|
|
Object *Object::getChildObject(int childObjectIndex)
|
|
{
|
|
// todo @portal: make this reflect only child objects, not attached objects
|
|
|
|
NOT_NULL(m_attachedObjects);
|
|
DEBUG_FATAL(childObjectIndex < 0 || childObjectIndex >= getNumberOfChildObjects(), ("child object index out of range %d/%d", childObjectIndex, getNumberOfChildObjects()));
|
|
|
|
Object *child = (*m_attachedObjects) [static_cast<uint> (childObjectIndex)];
|
|
NOT_NULL (child);
|
|
return child;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the specified child object.
|
|
*
|
|
* If the childObjecIndex is out of range, this routine will call Fatal
|
|
* in debug compiles.
|
|
*
|
|
* @param childObjectIndex Index of the child object to retrieve
|
|
* @return Pointer to the child object
|
|
*/
|
|
|
|
const Object *Object::getChildObject(int childObjectIndex) const
|
|
{
|
|
// todo @portal: make this reflect only child objects, not attached objects
|
|
|
|
NOT_NULL(m_attachedObjects);
|
|
DEBUG_FATAL(childObjectIndex < 0 || childObjectIndex >= getNumberOfChildObjects(), ("child object index out of range %d/%d", childObjectIndex, getNumberOfChildObjects()));
|
|
|
|
const Object *child = (*m_attachedObjects) [static_cast<uint> (childObjectIndex)];
|
|
NOT_NULL (child);
|
|
return child;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::attachToObject_p(Object * const object, bool const asChildObject)
|
|
{
|
|
NOT_NULL(object);
|
|
DEBUG_FATAL(m_childObject, ("Object::attachToObject_p: object %s already is a child object", getDebugInformation(true).c_str()));
|
|
DEBUG_FATAL(m_attachedToObject, ("Object::attachToObject_p: object %s already is relative to another object", getDebugInformation(true).c_str()));
|
|
DEBUG_FATAL(this == object, ("attempting to attach to self"));
|
|
|
|
// If the child object is already attached to an object, detach it.
|
|
if (m_attachedToObject)
|
|
detachFromObject(DF_none);
|
|
|
|
// put this on the list of attached objects
|
|
if (!object->m_attachedObjects)
|
|
object->m_attachedObjects = newAttachedObjects();
|
|
object->m_attachedObjects->push_back(this);
|
|
|
|
// remove the object from the world if its being added as a child
|
|
// and is currently in the world. This must happen before the m_childObject
|
|
// flag is set so that the worlds don't erroneously optimize out necessary
|
|
// operations by looking at the isChildObject() method. This issue happened
|
|
// with removing objects from the ClientWorld sphere trees.
|
|
if (asChildObject && isInWorld())
|
|
removeFromWorld();
|
|
|
|
// every child should know its parent
|
|
m_attachedToObject = object;
|
|
m_childObject = asChildObject;
|
|
|
|
// create a new objectToWorld transform for the child
|
|
m_objectToWorld = newLocalTransform();
|
|
setObjectToWorldDirty(true);
|
|
|
|
if (asChildObject)
|
|
{
|
|
// add the child to the world if the parent is in the world.
|
|
if (object->isInWorld())
|
|
addToWorld();
|
|
|
|
// if parent objects alter their children, ensure the new child
|
|
// object is not in the alter scheduler.
|
|
if (ms_objectsAlterChildrenAndContents)
|
|
IGNORE_RETURN(AlterScheduler::removeObject(*this));
|
|
}
|
|
|
|
//-- always schedule an object for alter next frame if it has received a new child object
|
|
if (World::isInstalled() && World::existsInWorld(getRootParent()))
|
|
scheduleForAlter();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::attachToObject_w(Object *object, bool asChildObject)
|
|
{
|
|
#ifdef _DEBUG
|
|
IGNORE_RETURN(m_objectToParent.validate());
|
|
#endif
|
|
// calculate frame and position in the master's space
|
|
const Vector i = object->rotate_w2o(getObjectFrameI_w());
|
|
const Vector j = object->rotate_w2o(getObjectFrameJ_w());
|
|
const Vector k = object->rotate_w2o(getObjectFrameK_w());
|
|
const Vector p = object->rotateTranslate_w2o(getPosition_w());
|
|
|
|
// set the transform of the child object
|
|
m_objectToParent.setLocalFrameIJK_p(i, j, k);
|
|
m_objectToParent.setPosition_p(p);
|
|
reorthonormalize();
|
|
|
|
// now do the attach in master space
|
|
attachToObject_p(object, asChildObject);
|
|
#ifdef _DEBUG
|
|
IGNORE_RETURN(m_objectToParent.validate());
|
|
#endif
|
|
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Detach an object from its master.
|
|
*
|
|
* Removal of an attached object causes the last attached object to move
|
|
* into its place in the vector. Therefore, attached object removal causes
|
|
* the order of attached objects to change.
|
|
*/
|
|
|
|
void Object::detachFromObject(DetachFlags const detachFlags)
|
|
{
|
|
NOT_NULL(m_attachedToObject);
|
|
#ifdef _DEBUG
|
|
IGNORE_RETURN(m_objectToParent.validate());
|
|
#endif
|
|
|
|
// leave the child object where it was in the same cell
|
|
bool const toParentCell = (detachFlags == DF_parent);
|
|
bool const noCell = (detachFlags == DF_none);
|
|
|
|
CellProperty * const cellProperty = noCell ? 0 : (toParentCell ? getParentCell() : CellProperty::getWorldCellProperty());
|
|
bool const shouldAttach = (!toParentCell || noCell) ? false : m_attachedToObject != &cellProperty->getOwner();
|
|
m_objectToParent = shouldAttach ? getTransform_o2c() : getTransform_o2w();
|
|
deleteLocalTransform(m_objectToWorld);
|
|
m_objectToWorld = nullptr;
|
|
setObjectToWorldDirty(true);
|
|
|
|
// remove from the attached objects list
|
|
AttachedObjects *attachedObjects = m_attachedToObject->m_attachedObjects;
|
|
const AttachedObjects::iterator i = std::find(attachedObjects->begin(), attachedObjects->end(), this);
|
|
DEBUG_FATAL(i == attachedObjects->end(), ("attached object was not found in parent"));
|
|
*i = attachedObjects->back();
|
|
attachedObjects->pop_back();
|
|
|
|
// set as unattached
|
|
m_attachedToObject = nullptr;
|
|
|
|
bool const wasChildObject = isChildObject();
|
|
bool const wasInWorld = isInWorld();
|
|
if (wasChildObject && wasInWorld)
|
|
removeFromWorld();
|
|
|
|
m_childObject = false;
|
|
|
|
if (wasChildObject && wasInWorld)
|
|
addToWorld();
|
|
|
|
#ifdef _DEBUG
|
|
IGNORE_RETURN(m_objectToParent.validate());
|
|
#endif
|
|
|
|
if (shouldAttach)
|
|
setParentCell(cellProperty);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Add a new child object to the end of the child object list.
|
|
*
|
|
* This routine will append the new child object to the list of child
|
|
* objects.
|
|
*
|
|
* @param newChildObject New child object
|
|
*/
|
|
|
|
void Object::addChildObject_o(Object * const newChildObject)
|
|
{
|
|
DEBUG_FATAL(!ms_objectsAlterChildrenAndContents, ("Only the client should add child objects."));
|
|
newChildObject->attachToObject_p(this, true);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Add a new child object to the end of the child object list.
|
|
*
|
|
* This routine will append the new child object to the list of child
|
|
* objects.
|
|
*
|
|
* @param newChildObject New child object
|
|
*/
|
|
|
|
void Object::addChildObject_p(Object * const newChildObject)
|
|
{
|
|
// calculate frame and position in this object's space
|
|
Vector p = rotateTranslate_p2o(newChildObject->getPosition_p());
|
|
Vector i = rotate_p2o(newChildObject->getObjectFrameI_p());
|
|
Vector j = rotate_p2o(newChildObject->getObjectFrameJ_p());
|
|
Vector k = rotate_p2o(newChildObject->getObjectFrameK_p());
|
|
|
|
// set the transform of the child object
|
|
newChildObject->setTransformIJK_o2p(i, j, k);
|
|
newChildObject->setPosition_p(p);
|
|
newChildObject->reorthonormalize();
|
|
|
|
// add this object as a child object
|
|
addChildObject_o(newChildObject);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Remove a child object from this object.
|
|
*
|
|
* If the specified child object is not a child object of this object,
|
|
* this routine will call Fatal() in Debug compiles.
|
|
*
|
|
* The child object will be removed, and greater child objects will shift
|
|
* down one index position.
|
|
*
|
|
* @param childObjectToRemove The child object to remove
|
|
*/
|
|
|
|
void Object::removeChildObject(Object * childObjectToRemove, DetachFlags detachFlags) const
|
|
{
|
|
DEBUG_FATAL(!childObjectToRemove || (childObjectToRemove->getAttachedTo() != this), ("Removing a child that is not attached to this object."));
|
|
childObjectToRemove->detachFromObject(detachFlags);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the object at the top level of the object hierarchy
|
|
*
|
|
* @return the object at the top of the hierarchy or the current object if the object is at the top of the hierarchy
|
|
* @todo don't make this routine recursive
|
|
*/
|
|
|
|
Object *Object::getRootParent(void)
|
|
{
|
|
DEBUG_FATAL(m_childObject && m_attachedToObject == nullptr, ("am a child but attached to is nullptr"));
|
|
return m_childObject ? NON_NULL(m_attachedToObject)->getRootParent() : this;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Get the object at the top level of the object hierarchy
|
|
*
|
|
* @return the object at the top of the hierarchy or the current object if the object is at the top of the hierarchy
|
|
* @todo don't make this routine recursive
|
|
*/
|
|
|
|
const Object *Object::getRootParent(void) const
|
|
{
|
|
DEBUG_FATAL(m_childObject && m_attachedToObject == nullptr, ("am a child but attached to is nullptr"));
|
|
return m_childObject ? NON_NULL(m_attachedToObject)->getRootParent() : this;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Move the object in it's parent space.
|
|
*
|
|
* This routine moves the object in it's parent space, or the world space if
|
|
* the object has no parent. Therefore, moving along the Z axis will move the
|
|
* object forward along the Z-axis of it's parent space, not forward in the
|
|
* direction in which it is pointed.
|
|
*
|
|
* @param vector Offset to move in parent space
|
|
* @see Object::move_o()
|
|
*/
|
|
|
|
void Object::move_p(const Vector &vector)
|
|
{
|
|
if (vector != Vector::zero)
|
|
{
|
|
const Vector oldPosition = getPosition_p();
|
|
m_objectToParent.move_p(vector);
|
|
positionChanged(false, oldPosition);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setRegionOfInfluenceEnabled(bool enabled) const
|
|
{
|
|
UNREF(enabled);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Called after the specified object has its containedByProperty changed
|
|
*
|
|
* This default implementation does nothing.
|
|
*
|
|
* Each object is assumed to have no more than one container associated
|
|
* with it; therefore, it is unambiguous which container is under
|
|
* consideration for this object.
|
|
*
|
|
* @param oldValue The container that used to hold this object
|
|
* @param newValue The container that now holds this object.
|
|
*/
|
|
|
|
|
|
void Object::containedByModified(const NetworkId & oldValue, const NetworkId & newValue,bool isLocal)
|
|
{
|
|
UNREF (oldValue);
|
|
UNREF (newValue);
|
|
UNREF (isLocal);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Called after the specified object has its slotted arrangement changed
|
|
*
|
|
* This default implementation does nothing.
|
|
*
|
|
* Each object is assumed to have no more than one container associated
|
|
* with it; therefore, it is unambiguous which container is under
|
|
* consideration for this object.
|
|
*
|
|
* @param oldValue The old arrangement
|
|
* @param newValue The new arrangement
|
|
*/
|
|
|
|
void Object::arrangementModified(int oldValue, int newValue, bool isLocal)
|
|
{
|
|
UNREF (oldValue);
|
|
UNREF (newValue);
|
|
UNREF (isLocal);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Set the scale for this object.
|
|
*
|
|
* @param scale New scale for this object
|
|
*/
|
|
|
|
void Object::setScale(const Vector &scale)
|
|
{
|
|
m_scale = scale;
|
|
if (m_appearance)
|
|
m_appearance->setScale(scale);
|
|
|
|
if (isInWorld())
|
|
extentChanged();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setRecursiveScale(Vector const & scale)
|
|
{
|
|
setScale(scale);
|
|
int const numberOfChildObjects = getNumberOfChildObjects();
|
|
for (int i = 0; i < numberOfChildObjects; ++i)
|
|
{
|
|
Object * const child = getChildObject(i);
|
|
if (child != 0)
|
|
{
|
|
child->setRecursiveScale(scale);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
Controller* Object::stealController(void)
|
|
{
|
|
Controller* returnValue = m_controller;
|
|
m_controller = nullptr;
|
|
return returnValue;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Transform sphere from the world frame to the object's frame.
|
|
*
|
|
* @param sphere Vector to rotate and translate from world space into object space
|
|
* @return The source world-space sphere transformed into object space
|
|
*/
|
|
|
|
const Sphere Object::rotateTranslate_o2w(const Sphere &sphere) const
|
|
{
|
|
return Sphere(getTransform_o2w().rotateTranslate_l2p(sphere.getCenter()), sphere.getRadius());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Transform sphere from the world frame to the object's frame.
|
|
*
|
|
* @param sphere Vector to rotate and translate from world space into object space
|
|
* @return The source world-space sphere transformed into object space
|
|
*/
|
|
|
|
const Sphere Object::rotateTranslate_w2o(const Sphere &sphere) const
|
|
{
|
|
return Sphere(getTransform_o2w().rotateTranslate_p2l(sphere.getCenter()), sphere.getRadius());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Determine if this object has child objects.
|
|
*
|
|
* @return True if the object has one or more child objects, otherwise false.
|
|
*/
|
|
|
|
bool Object::hasChildObjects() const
|
|
{
|
|
return (m_attachedObjects && !m_attachedObjects->empty());
|
|
}
|
|
|
|
// ======================================================================
|
|
|
|
const char *Object::getObjectTemplateName() const
|
|
{
|
|
return m_objectTemplate ? m_objectTemplate->getName() : 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Tag Object::getObjectType() const
|
|
{
|
|
return m_objectTemplate ? m_objectTemplate->getId() : 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setNetworkId(const NetworkId &networkId)
|
|
{
|
|
NetworkIdManager::removeObject(*this);
|
|
m_networkId = networkId;
|
|
NetworkIdManager::addObject(*this);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::kill()
|
|
{
|
|
m_kill = true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::addProperty(Property &property)
|
|
{
|
|
// Default is to add property only while not in world.
|
|
bool const allowWhileInWorld = false;
|
|
addProperty(property, allowWhileInWorld);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::addProperty(Property &property, bool allowWhileInWorld)
|
|
{
|
|
UNREF(allowWhileInWorld);
|
|
|
|
PropertyId propertyId = property.getPropertyId();
|
|
|
|
bool const inWorldNow = isInWorld();
|
|
DEBUG_FATAL(getProperty(propertyId),("Cannot add already existing property."));
|
|
DEBUG_FATAL(inWorldNow && !allowWhileInWorld, ("Cannot add property to object already in the world."));
|
|
m_propertyList->push_back(&property);
|
|
|
|
if (propertyId == CellProperty::getClassPropertyId() || propertyId == PortalProperty::getClassPropertyId() || propertyId == SlottedContainer::getClassPropertyId() || propertyId == VolumeContainer::getClassPropertyId())
|
|
{
|
|
DEBUG_FATAL(m_containerProperty, ("Object already has a container property"));
|
|
m_containerProperty = safe_cast<Container*>(&property);
|
|
}
|
|
else
|
|
{
|
|
if (propertyId == ContainedByProperty::getClassPropertyId())
|
|
{
|
|
DEBUG_FATAL(m_containedBy, ("Object already has a collision property"));
|
|
m_containedBy = safe_cast<ContainedByProperty*>(&property);
|
|
}
|
|
else
|
|
if (propertyId == CollisionProperty::getClassPropertyId())
|
|
{
|
|
DEBUG_FATAL(m_collisionProperty, ("Object already has a collision property"));
|
|
m_collisionProperty = safe_cast<CollisionProperty*>(&property);
|
|
}
|
|
}
|
|
|
|
//-- If this object is already in the world, let the property know about it.
|
|
if (inWorldNow)
|
|
property.addToWorld();
|
|
|
|
} //lint !e1764 // (Info -- Reference parameter 'property' (line 1717) could be declared const ref) // No, we need a pointer to a non-const Property.
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Property const *Object::getProperty(PropertyId const &id) const
|
|
{
|
|
++ms_propertySearchStatistics[id];
|
|
++ms_propertySearchesPerFrame;
|
|
|
|
for (PropertyList::const_iterator i = m_propertyList->begin(); i != m_propertyList->end(); ++i)
|
|
if ((*i)->getPropertyId() == id)
|
|
return *i;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Property *Object::getProperty(PropertyId const &id)
|
|
{
|
|
++ms_propertySearchStatistics[id];
|
|
++ms_propertySearchesPerFrame;
|
|
|
|
for (PropertyList::const_iterator i = m_propertyList->begin(); i != m_propertyList->end(); ++i)
|
|
if ((*i)->getPropertyId() == id)
|
|
return *i;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::removeProperty(PropertyId const &id)
|
|
{
|
|
PropertyList::iterator i = m_propertyList->begin();
|
|
for (; i != m_propertyList->end(); ++i)
|
|
if ((*i)->getPropertyId() == id)
|
|
break;
|
|
|
|
if (i != m_propertyList->end())
|
|
{
|
|
IGNORE_RETURN(m_propertyList->erase(i));
|
|
}
|
|
else
|
|
{
|
|
DEBUG_FATAL(true, ("property not found"));
|
|
return;
|
|
}
|
|
|
|
if (id == CellProperty::getClassPropertyId() || id == PortalProperty::getClassPropertyId() || id == SlottedContainer::getClassPropertyId() || id == VolumeContainer::getClassPropertyId())
|
|
m_containerProperty = nullptr;
|
|
else
|
|
if (id == ContainedByProperty::getClassPropertyId())
|
|
m_containedBy = nullptr;
|
|
else
|
|
if (id == CollisionProperty::getClassPropertyId())
|
|
m_collisionProperty = nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::lookAt_p (const Vector &position_p)
|
|
{
|
|
lookAt_o(rotateTranslate_p2o (position_p));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::lookAt_p (const Vector &position_p, const Vector &j_p)
|
|
{
|
|
Vector k = position_p - getPosition_p(); // fwd vector in parent space
|
|
if (!k.normalize())
|
|
return;
|
|
Vector i = j_p.cross(k);
|
|
if (!i.normalize())
|
|
return;
|
|
k = i.cross(j_p);
|
|
if (!k.normalize())
|
|
return;
|
|
setTransformIJK_o2p(i, j_p, k);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::lookAt_o (const Vector &position_o)
|
|
{
|
|
yaw_o (position_o.theta());
|
|
pitch_o (position_o.phi());
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::lookAt_o (const Vector &position_o, const Vector &j_o)
|
|
{
|
|
lookAt_p(rotateTranslate_o2p(position_o), rotate_o2p(j_o));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Sets the new appearance from the specified appearance path.
|
|
*/
|
|
|
|
void Object::setAppearanceByName(char const *path)
|
|
{
|
|
if (path != nullptr)
|
|
{
|
|
if (TreeFile::exists(path))
|
|
{
|
|
// Create the object's new appearance
|
|
|
|
Appearance *appearance = AppearanceTemplateList::createAppearance(path);
|
|
|
|
if (appearance != nullptr)
|
|
{
|
|
setAppearance(appearance);
|
|
} else {
|
|
DEBUG_WARNING(true, ("Object::setAppearanceByName() - Unable to find the object's appearance because the file does not exist: %s", path));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_WARNING(true, ("Object::setAppearanceByName() - Unable to change the object's appearance because the file does not exist: %s", path));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_WARNING(true, ("Object::setAppearanceByName() - nullptr appearance path specified for object: %s", (getObjectTemplateName() == nullptr) ? "" : getObjectTemplateName()));
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
char const * Object::getAppearanceTemplateName() const
|
|
{
|
|
return m_appearance ? m_appearance->getAppearanceTemplateName() : 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
/**
|
|
* Find the actual world position of any object, regardless of containment.
|
|
*
|
|
* Note we're trying to get a real world coordinate, so we'll stop looking
|
|
* at the parent the second we have an object that's in the world.
|
|
*/
|
|
|
|
const Vector Object::findPosition_w () const
|
|
{
|
|
const Object * obj = this;
|
|
|
|
const ContainedByProperty * cbp = getContainedByProperty ();
|
|
|
|
for ( ; !obj->isInWorld() && cbp; )
|
|
{
|
|
const Object * const parent = cbp->getContainedBy ();
|
|
|
|
if (parent)
|
|
{
|
|
obj = parent;
|
|
cbp = obj->getContainedByProperty ();
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
return obj->getPosition_w ();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::scheduleForAlter()
|
|
{
|
|
if (ms_objectsAlterChildrenAndContents)
|
|
{
|
|
scheduleForAlter_scheduleTopmostWorldParent();
|
|
}
|
|
else
|
|
{
|
|
DEBUG_FATAL(!isInitialized(), ("scheduleForAlter() called on uninitialized object %s", getDebugInformation().c_str()));
|
|
|
|
AlterScheduler::submitForAlter(*this);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
SlottedContainer * Object::getSlottedContainerProperty()
|
|
{
|
|
Container *container = getContainerProperty();
|
|
if (container && container->getPropertyId() == SlottedContainer::getClassPropertyId())
|
|
return safe_cast<SlottedContainer *>(container);
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
SlottedContainer const * Object::getSlottedContainerProperty() const
|
|
{
|
|
Container const *container = getContainerProperty();
|
|
if (container && container->getPropertyId() == SlottedContainer::getClassPropertyId())
|
|
return safe_cast<SlottedContainer const *>(container);
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
VolumeContainer * Object::getVolumeContainerProperty()
|
|
{
|
|
Container *container = getContainerProperty();
|
|
if (container && container->getPropertyId() == VolumeContainer::getClassPropertyId())
|
|
return safe_cast<VolumeContainer *>(container);
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
VolumeContainer const * Object::getVolumeContainerProperty() const
|
|
{
|
|
Container const *container = getContainerProperty();
|
|
if (container && container->getPropertyId() == VolumeContainer::getClassPropertyId())
|
|
return safe_cast<VolumeContainer const *>(container);
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
CellProperty * Object::getCellProperty()
|
|
{
|
|
Container *container = getContainerProperty();
|
|
if (container && container->getPropertyId() == CellProperty::getClassPropertyId())
|
|
return safe_cast<CellProperty *>(container);
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
CellProperty const * Object::getCellProperty() const
|
|
{
|
|
Container const *container = getContainerProperty();
|
|
if (container && container->getPropertyId() == CellProperty::getClassPropertyId())
|
|
return safe_cast<CellProperty const *>(container);
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
PortalProperty * Object::getPortalProperty()
|
|
{
|
|
Container *container = getContainerProperty();
|
|
if (container && container->getPropertyId() == PortalProperty::getClassPropertyId())
|
|
return safe_cast<PortalProperty *>(container);
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
PortalProperty const * Object::getPortalProperty() const
|
|
{
|
|
Container const *container = getContainerProperty();
|
|
if (container && container->getPropertyId() == PortalProperty::getClassPropertyId())
|
|
return safe_cast<PortalProperty const *>(container);
|
|
return nullptr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
SpatialSubdivisionHandle* Object::getSpatialSubdivisionHandle ()
|
|
{
|
|
return m_spatialSubdivisionHandle;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setSpatialSubdivisionHandle (SpatialSubdivisionHandle* const spatialSubdivisionHandle)
|
|
{
|
|
m_spatialSubdivisionHandle = spatialSubdivisionHandle;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
ClientObject * Object::asClientObject()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
ClientObject const * Object::asClientObject() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
ServerObject * Object::asServerObject()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
ServerObject const * Object::asServerObject() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::hasScheduleData() const
|
|
{
|
|
return (m_scheduleData != nullptr);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::createScheduleDataIfNecessary(ScheduleTime initialMostRecentAlterTime)
|
|
{
|
|
if (!m_scheduleData)
|
|
{
|
|
#if OBJECT_SUPPORTS_DESTROYED_FLAG
|
|
// Do not let a destroyed object get handled by the alter scheduler.
|
|
FATAL(m_isDestroyed, ("createScheduleDataIfNecessary(): trying to add a destroyed object to the alter scheduler."));
|
|
#endif
|
|
|
|
m_scheduleData = new ScheduleData(initialMostRecentAlterTime);
|
|
AlterScheduler::initializeScheduleTimeMapIterator(*this);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object::ScheduleTime Object::getMostRecentAlterTime() const
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("Calling getMostRecentAlterTime() on an object that has no schedule data."));
|
|
return m_scheduleData->getMostRecentAlterTime();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setMostRecentAlterTime(ScheduleTime mostRecentAlterTime)
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("Calling setMostRecentAlterTime() on an object that has no schedule data."));
|
|
m_scheduleData->setMostRecentAlterTime(mostRecentAlterTime);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::isInAlterNextFrameList() const
|
|
{
|
|
return (m_scheduleData == nullptr) ? false : (m_scheduleData->getAlterNextFrameNext() != nullptr) || (m_scheduleData->getAlterNextFramePrevious() != nullptr);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::insertIntoAlterNextFrameList(Object *afterThisObject)
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("Calling insertIntoAlterNextFrameList() on an object that has no schedule data."));
|
|
DEBUG_FATAL(isInAlterNextFrameList(), ("insertIntoAlterNextFrameList(): object id=[%s],template=[%s] already in AlterNextFrame list.", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
NOT_NULL(afterThisObject);
|
|
NOT_NULL(afterThisObject->m_scheduleData);
|
|
DEBUG_FATAL(afterThisObject->m_scheduleData->getAlterNextFrameNext() && !afterThisObject->m_scheduleData->getAlterNextFrameNext()->m_scheduleData, ("Non-nullptr forward link does not have schedule data."));
|
|
DEBUG_FATAL(afterThisObject->m_scheduleData->getAlterNextFrameNext() && (afterThisObject->m_scheduleData->getAlterNextFrameNext()->m_scheduleData->getAlterNextFramePrevious() != afterThisObject), ("List corruption: alter next frame."));
|
|
|
|
//-- Get new next and previous object for the list.
|
|
Object *newPrevious = afterThisObject;
|
|
Object *newNext = afterThisObject->m_scheduleData->getAlterNextFrameNext();
|
|
|
|
//-- Handle new previous link.
|
|
m_scheduleData->setAlterNextFramePrevious(newPrevious);
|
|
newPrevious->m_scheduleData->setAlterNextFrameNext(this);
|
|
|
|
//-- Handle new next link.
|
|
m_scheduleData->setAlterNextFrameNext(newNext);
|
|
if (newNext)
|
|
newNext->m_scheduleData->setAlterNextFramePrevious(this);
|
|
|
|
#if AS_USE_HARDCORE_CONTAINER_VALIDATION
|
|
//-- Post assertion.
|
|
DEBUG_FATAL(!AlterScheduler::findObjectInAlterNextFrameList(this), ("insertIntoAlterNextFrameList(): object should be in list but isn't: pointer=[%p],id=[%s],template=[%s].", this, getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::removeFromAlterNextFrameList()
|
|
{
|
|
DEBUG_FATAL(!isInAlterNextFrameList(), ("Called Object::removeFromAlterNextFrameList() when not in the list."));
|
|
NOT_NULL(m_scheduleData);
|
|
|
|
//-- Get the next and previous for the list.
|
|
Object *newPrevious = m_scheduleData->getAlterNextFramePrevious();
|
|
Object *newNext = m_scheduleData->getAlterNextFrameNext();
|
|
|
|
DEBUG_FATAL(newPrevious && !newPrevious->m_scheduleData, ("Previous node missing schedule data."));
|
|
DEBUG_FATAL(newNext && !newNext->m_scheduleData, ("Next node missing schedule data."));
|
|
|
|
//-- Handle previous.
|
|
m_scheduleData->setAlterNextFramePrevious(nullptr);
|
|
if (newPrevious)
|
|
newPrevious->m_scheduleData->setAlterNextFrameNext(newNext);
|
|
|
|
|
|
//-- Handle next.
|
|
m_scheduleData->setAlterNextFrameNext(nullptr);
|
|
if (newNext)
|
|
newNext->m_scheduleData->setAlterNextFramePrevious(newPrevious);
|
|
|
|
|
|
#if AS_USE_HARDCORE_CONTAINER_VALIDATION
|
|
//-- Post assertion.
|
|
DEBUG_FATAL(AlterScheduler::findObjectInAlterNextFrameList(this), ("removeFromAlterNextFrameList(): object shouldn't be in list but is: pointer=[%p],id=[%s],template=[%s].", this, getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
int Object::getAlterSchedulePhase() const
|
|
{
|
|
DEBUG_FATAL(m_scheduleData == nullptr, ("calling Object::getAlterSchedulePhase() when Object has no schedule data: id=[%s], template=[%s]", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
return m_scheduleData->getSchedulePhase();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setAlterSchedulePhase(int schedulePhaseIndex)
|
|
{
|
|
DEBUG_FATAL(m_scheduleData == nullptr, ("calling Object::setAlterSchedulePhase() when Object has no schedule data: id=[%s], template=[%s]", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
m_scheduleData->setSchedulePhase(schedulePhaseIndex);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::isInAlterNowList() const
|
|
{
|
|
return (m_scheduleData == nullptr) ? false : (m_scheduleData->getAlterNowNext() != nullptr) || (m_scheduleData->getAlterNowPrevious() != nullptr);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::insertIntoAlterNowList(Object *afterThisObject)
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("Calling insertIntoAlterNowList() on an object that has no schedule data."));
|
|
DEBUG_FATAL(isInAlterNowList(), ("insertIntoAlterNowList(): object id=[%s],template=[%s] already in AlterNow list.", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
NOT_NULL(afterThisObject);
|
|
NOT_NULL(afterThisObject->m_scheduleData);
|
|
DEBUG_FATAL(afterThisObject->m_scheduleData->getAlterNowNext() && !afterThisObject->m_scheduleData->getAlterNowNext()->m_scheduleData, ("Non-nullptr forward link does not have schedule data."));
|
|
DEBUG_FATAL(afterThisObject->m_scheduleData->getAlterNowNext() && (afterThisObject->m_scheduleData->getAlterNowNext()->m_scheduleData->getAlterNowPrevious() != afterThisObject), ("List corruption: alter now."));
|
|
|
|
//-- Get new next and previous object for the list.
|
|
Object *newPrevious = afterThisObject;
|
|
Object *newNext = afterThisObject->m_scheduleData->getAlterNowNext();
|
|
|
|
//-- Handle new previous link.
|
|
m_scheduleData->setAlterNowPrevious(newPrevious);
|
|
newPrevious->m_scheduleData->setAlterNowNext(this);
|
|
|
|
//-- Handle new next link.
|
|
m_scheduleData->setAlterNowNext(newNext);
|
|
if (newNext)
|
|
newNext->m_scheduleData->setAlterNowPrevious(this);
|
|
|
|
#if AS_USE_HARDCORE_CONTAINER_VALIDATION
|
|
//-- Post assertion.
|
|
DEBUG_FATAL(!AlterScheduler::findObjectInAlterNowList(this), ("insertIntoAlterNowList(): object should be in list but isn't: pointer=[%p],id=[%s],template=[%s].", this, getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::removeFromAlterNowList()
|
|
{
|
|
DEBUG_FATAL(!isInAlterNowList(), ("Called Object::removeFromAlterNowList() when not in the list."));
|
|
NOT_NULL(m_scheduleData);
|
|
|
|
//-- Get the next and previous for the list.
|
|
Object *newPrevious = m_scheduleData->getAlterNowPrevious();
|
|
Object *newNext = m_scheduleData->getAlterNowNext();
|
|
|
|
DEBUG_FATAL(newPrevious && !newPrevious->m_scheduleData, ("Previous node missing schedule data."));
|
|
DEBUG_FATAL(newNext && !newNext->m_scheduleData, ("Next node missing schedule data."));
|
|
|
|
//-- Handle previous.
|
|
m_scheduleData->setAlterNowPrevious(nullptr);
|
|
if (newPrevious)
|
|
newPrevious->m_scheduleData->setAlterNowNext(newNext);
|
|
|
|
//-- Handle next.
|
|
m_scheduleData->setAlterNowNext(nullptr);
|
|
if (newNext)
|
|
newNext->m_scheduleData->setAlterNowPrevious(newPrevious);
|
|
|
|
#if AS_USE_HARDCORE_CONTAINER_VALIDATION
|
|
//-- Post assertion.
|
|
DEBUG_FATAL(AlterScheduler::findObjectInAlterNowList(this), ("removeFromAlterNowList(): object shouldn't be in list but is: pointer=[%p],id=[%s],template=[%s].", this, getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::isInConcludeList() const
|
|
{
|
|
return (m_scheduleData == nullptr) ? false : (m_scheduleData->getConcludeNext() != nullptr) || (m_scheduleData->getConcludePrevious() != nullptr);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::insertIntoConcludeList(Object *afterThisObject)
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("Calling insertIntoConcludeList() on an object that has no schedule data."));
|
|
DEBUG_FATAL(isInConcludeList(), ("insertIntoConcludeList(): object id=[%s],template=[%s] already in Conclude list.", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
NOT_NULL(afterThisObject);
|
|
NOT_NULL(afterThisObject->m_scheduleData);
|
|
DEBUG_FATAL(afterThisObject->m_scheduleData->getConcludeNext() && !afterThisObject->m_scheduleData->getConcludeNext()->m_scheduleData, ("Non-nullptr forward link does not have schedule data."));
|
|
DEBUG_FATAL(afterThisObject->m_scheduleData->getConcludeNext() && (afterThisObject->m_scheduleData->getConcludeNext()->m_scheduleData->getConcludePrevious() != afterThisObject), ("List corruption: alter now."));
|
|
|
|
//-- Get new next and previous object for the list.
|
|
Object *newPrevious = afterThisObject;
|
|
Object *newNext = afterThisObject->m_scheduleData->getConcludeNext();
|
|
|
|
//-- Handle new previous link.
|
|
m_scheduleData->setConcludePrevious(newPrevious);
|
|
newPrevious->m_scheduleData->setConcludeNext(this);
|
|
|
|
//-- Handle new next link.
|
|
m_scheduleData->setConcludeNext(newNext);
|
|
if (newNext)
|
|
newNext->m_scheduleData->setConcludePrevious(this);
|
|
|
|
#if AS_USE_HARDCORE_CONTAINER_VALIDATION
|
|
//-- Post assertion.
|
|
DEBUG_FATAL(!AlterScheduler::findObjectInConcludeList(this), ("insertIntoConcludeList(): object should be in list but isn't: pointer=[%p],id=[%s],template=[%s].", this, getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::removeFromConcludeList()
|
|
{
|
|
DEBUG_FATAL(!isInConcludeList(), ("Called Object::removeFromConcludeList() when not in the list."));
|
|
NOT_NULL(m_scheduleData);
|
|
|
|
//-- Get the next and previous for the list.
|
|
Object *newPrevious = m_scheduleData->getConcludePrevious();
|
|
Object *newNext = m_scheduleData->getConcludeNext();
|
|
|
|
DEBUG_FATAL(newPrevious && !newPrevious->m_scheduleData, ("Previous node missing schedule data."));
|
|
DEBUG_FATAL(newNext && !newNext->m_scheduleData, ("Next node missing schedule data."));
|
|
|
|
//-- Handle previous.
|
|
m_scheduleData->setConcludePrevious(nullptr);
|
|
if (newPrevious)
|
|
newPrevious->m_scheduleData->setConcludeNext(newNext);
|
|
|
|
//-- Handle next.
|
|
m_scheduleData->setConcludeNext(nullptr);
|
|
if (newNext)
|
|
newNext->m_scheduleData->setConcludePrevious(newPrevious);
|
|
|
|
#if AS_USE_HARDCORE_CONTAINER_VALIDATION
|
|
//-- Post assertion.
|
|
DEBUG_FATAL(AlterScheduler::findObjectInConcludeList(this), ("removeFromConcludeList(): object shouldn't be in list but is: pointer=[%p],id=[%s],template=[%s].", this, getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void *Object::getScheduleTimeMapIterator()
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("getScheduleTimeMapIterator() called but this object doesn't have schedule data."));
|
|
return &m_scheduleData->getScheduleTimeMapIterator();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object *Object::getNextFromAlterNowList()
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("getNextFromAlterNowList() called but this object doesn't have schedule data."));
|
|
return m_scheduleData->getAlterNowNext();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object *Object::getPreviousFromAlterNowList()
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("getPreviousFromAlterNowList() called but this object doesn't have schedule data."));
|
|
return m_scheduleData->getAlterNowPrevious();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object *Object::getNextFromAlterNextFrameList()
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("getNextFromAlterNextFrameList() called but this object doesn't have schedule data."));
|
|
return m_scheduleData->getAlterNextFrameNext();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object *Object::getPreviousFromAlterNextFrameList()
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("getPreviousFromAlterNextFrameList() called but this object doesn't have schedule data."));
|
|
return m_scheduleData->getAlterNextFramePrevious();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object *Object::getNextFromConcludeList()
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("getNextFromConcludeList() called but this object doesn't have schedule data."));
|
|
return m_scheduleData->getConcludeNext();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Object *Object::getPreviousFromConcludeList()
|
|
{
|
|
DEBUG_FATAL(!m_scheduleData, ("getPreviousFromConcludeList() called but this object doesn't have schedule data."));
|
|
return m_scheduleData->getConcludePrevious();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
#if OBJECT_SUPPORTS_IS_ALTERING_FLAG
|
|
|
|
void Object::setIsAltering(bool newIsAltering)
|
|
{
|
|
DEBUG_FATAL((newIsAltering && m_isAltering) || (!newIsAltering && !m_isAltering), ("Object::setIsAltering(): setting same alter state, likely bad logic flow for object. old/new state is [%s].", m_isAltering ? "true" : "false"));
|
|
m_isAltering = newIsAltering;
|
|
}
|
|
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Alter scheduler scheduleForAlter strategy that goes up the parent
|
|
* chain to the top-most object in the world for alter scheduling.
|
|
*
|
|
* Client-side objects adopt this strategy, including
|
|
* ClientObject and HardpointObject. Not all client-side objects
|
|
* derive from ClientObject, so we stick this strategy in the base
|
|
* Object class, override the virtual scheduleForAlter(), and forward
|
|
* that function to this one.
|
|
*
|
|
* More details:
|
|
*
|
|
* Ensure this Object instance gets an alter() call via the
|
|
* AlterScheduler the next time alter processing occurs.
|
|
*
|
|
* This function will ensure only the top-most object in the world is
|
|
* added to the AlterScheduler. We do this because those are the only
|
|
* objects that should receive an alter, as they propagate alter calls
|
|
* to their children and contained items.
|
|
*
|
|
* To find the top-most instance to alter given this instance, first
|
|
* we traverse the parent-child loop looking for the top-most parent.
|
|
* Then we traverse the container-contained hierarchy looking for the
|
|
* first Object that is in the world. The traversed object that meets
|
|
* the following conditions is put on the AlterScheduler: (1) does not have
|
|
* a parent object and (2) is not contained or is contained but is in the world.
|
|
*/
|
|
void Object::scheduleForAlter_scheduleTopmostWorldParent()
|
|
{
|
|
Object *alterObject = this;
|
|
|
|
do
|
|
{
|
|
//-- Traverse parent links until there is no more parent.
|
|
for (Object *parentObject = alterObject->getParent(); parentObject != nullptr; parentObject = alterObject->getParent())
|
|
alterObject = parentObject;
|
|
|
|
//-- Traverse container until we're at a container object that is in the world.
|
|
if (!alterObject->isInWorld())
|
|
{
|
|
//-- Use the first container parent that is in the world.
|
|
ContainedByProperty *containedByProperty = alterObject->getContainedByProperty();
|
|
while (containedByProperty)
|
|
{
|
|
// Get the container object.
|
|
Object *const containerObject = containedByProperty->getContainedBy();
|
|
if (!containerObject)
|
|
break;
|
|
else
|
|
{
|
|
alterObject = containerObject;
|
|
if (alterObject->isInWorld())
|
|
{
|
|
// We're done searching.
|
|
containedByProperty = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// The container object is not in the world, so search for it's container.
|
|
containedByProperty = alterObject->getContainedByProperty();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-- If we get here and the alterObject is not in the world, something seems wrong.
|
|
// This object needs to be altered but doesn't have a root-most object in the world.
|
|
// DEBUG_FATAL(!alterObject->isInWorld(), ("trying to schedule an alter for an object [%s] whose root-most container object [%s] isn't in the world!", getNetworkId().getValueString().c_str(), alterObject->getNetworkId().getValueString().c_str()));
|
|
}
|
|
|
|
// New container might be a child object, make sure we test for a parent again.
|
|
// If a parent exists, we need to run through the loop again to find the parent object.
|
|
} while (alterObject->getParent() != nullptr);
|
|
|
|
NOT_NULL(alterObject);
|
|
if (alterObject->isInitialized())
|
|
AlterScheduler::submitForAlter(*alterObject);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::nullWatchers()
|
|
{
|
|
m_watchedByList.nullWatchers();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
AxialBox const ObjectNamespace::getTangibleExtentInternal(Object const &obj)
|
|
{
|
|
AxialBox result;
|
|
|
|
//-- If this has an applicable appearance, the result begins with this
|
|
Appearance const * const appearance = obj.getAppearance();
|
|
if (appearance)
|
|
result = appearance->getTangibleExtent();
|
|
|
|
//-- Iterate through all of the children and grow the result axial box
|
|
int const numberOfChildObjects = obj.getNumberOfChildObjects();
|
|
for (int i = 0; i < numberOfChildObjects; ++i)
|
|
{
|
|
Object const * const childObject = obj.getChildObject(i);
|
|
if (!childObject || !childObject->isChildObject())
|
|
continue;
|
|
|
|
AxialBox const axialBox = getTangibleExtentInternal(*childObject);
|
|
|
|
//-- Transform box from child space into parent space and grow result
|
|
if (!axialBox.isEmpty())
|
|
for (int j = 0; j < 8; ++j)
|
|
result.add(childObject->getTransform_o2p().rotateTranslate_l2p(axialBox.getCorner(j)));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
AxialBox const Object::getTangibleExtent() const
|
|
{
|
|
AxialBox result = getTangibleExtentInternal(*this);
|
|
|
|
// Return a zero sized box at the appearance center if there is no other
|
|
// tangible extent. If there is no appearance this centers at the local
|
|
// origin.
|
|
if (result.isEmpty())
|
|
result.add(getAppearanceSphereCenter());
|
|
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool Object::getUseAlterScheduler() const
|
|
{
|
|
return m_useAlterScheduler;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setUseAlterScheduler(bool const useAlterScheduler)
|
|
{
|
|
m_useAlterScheduler = useAlterScheduler;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setObjectToWorldDirty(bool const objectToWorldDirty) const
|
|
{
|
|
#if ENABLE_OBJECTTOWORLDDIRTY == 1
|
|
for (int i = 0; i < getNumberOfAttachedObjects(); ++i)
|
|
getAttachedObject(i)->setObjectToWorldDirty(true);
|
|
#endif
|
|
|
|
m_objectToWorldDirty = objectToWorldDirty;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Sphere const Object::getCollisionSphereExtent_o() const
|
|
{
|
|
Sphere sphere_o(Sphere::unit);
|
|
|
|
CollisionProperty const * const collisionProperty = getCollisionProperty();
|
|
|
|
if (collisionProperty != 0)
|
|
{
|
|
sphere_o = collisionProperty->getBoundingSphere_l();
|
|
}
|
|
else
|
|
{
|
|
sphere_o.setRadius(1.0f);
|
|
}
|
|
|
|
return sphere_o;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Sphere const Object::getCollisionSphereExtent_w() const
|
|
{
|
|
Sphere const sphere_o(getCollisionSphereExtent_o());
|
|
Sphere sphere_w;
|
|
|
|
sphere_w.setCenter(rotateTranslate_o2w(sphere_o.getCenter()));
|
|
sphere_w.setRadius(sphere_o.getRadius());
|
|
|
|
return sphere_w;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
float Object::getDistanceBetweenCollisionSpheres_w(Object const & object) const
|
|
{
|
|
float distance = 0.0f;
|
|
|
|
if (&object != this)
|
|
{
|
|
Vector const & a_w = getPosition_w();
|
|
Vector const & b_w = object.getPosition_w();
|
|
|
|
distance = a_w.magnitudeBetween(b_w);
|
|
distance -= getCollisionSphereExtent_o().getRadius();
|
|
distance -= object.getCollisionSphereExtent_o().getRadius();
|
|
distance = std::max(0.0f, distance);
|
|
}
|
|
|
|
return distance;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::getObjectInfo(std::map<std::string, std::map<std::string, Unicode::String> > & propertyMap) const
|
|
{
|
|
/**
|
|
When adding a variable to this class, please add it here. Variable that aren't easily displayable are still listed, for tracking purposes.
|
|
*/
|
|
|
|
/**
|
|
Don't compile in production build because this maps human-readable values to data members and makes hacking easier
|
|
*/
|
|
#if PRODUCTION == 0
|
|
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "InWorld", m_inWorld);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "Active", m_active);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "Kill", m_kill);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "Authoritative", m_authoritative);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "ChildObject", m_childObject);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "ObjectToWorldDirty", m_objectToWorldDirty);
|
|
#if OBJECT_SUPPORTS_DESTROYED_FLAG
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "IsDestroyed", m_isDestroyed);
|
|
#endif
|
|
#if OBJECT_SUPPORTS_IS_ALTERING_FLAG
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "IsAltering", m_isAltering);
|
|
#endif
|
|
if(getObjectTemplateName())
|
|
{
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "ObjectTemplateName", getObjectTemplateName());
|
|
}
|
|
else
|
|
{
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "ObjectTemplateName", "Unknown");
|
|
}
|
|
//const NotificationList *m_notificationList;
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "DebugName", m_debugName);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "NetworkId", m_networkId.getValueString());
|
|
if(m_objectTemplate)
|
|
{
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "AppearanceTemplateName", getAppearanceTemplateName());
|
|
}
|
|
else
|
|
{
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "AppearanceTemplateName", "Unknown");
|
|
}
|
|
//Controller *m_controller;
|
|
//Dynamics *m_dynamics;
|
|
|
|
if(m_attachedToObject)
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "AttachedToObject", m_attachedToObject->getNetworkId().getValueString());
|
|
else
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "AttachedToObject", "");
|
|
|
|
//AttachedObjects *m_attachedObjects;
|
|
//DpvsObjects *m_dpvsObjects;
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "Rotations", m_rotations);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "Scale", m_scale);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "ObjectToParent", m_objectToParent);
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "ObjectToWorld", m_objectToWorld);
|
|
//mutable WatchedByList m_watchedByList;
|
|
//Container *m_containerProperty;
|
|
//CollisionProperty *m_collisionProperty;
|
|
//SpatialSubdivisionHandle *m_spatialSubdivisionHandle;
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "UseAlterScheduler", m_useAlterScheduler);
|
|
//ScheduleData *m_scheduleData;
|
|
|
|
if(m_containedBy)
|
|
DebugInfoManager::addProperty(propertyMap, ms_debugInfoSectionName, "ContainedBy", m_containedBy->getContainedByNetworkId().getValueString());
|
|
#else
|
|
UNREF(propertyMap);
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::setAlternateAppearance(const char * path)
|
|
{
|
|
|
|
if(!path)
|
|
return;
|
|
|
|
Appearance *alternateAppearance = nullptr;
|
|
|
|
if (TreeFile::exists(path))
|
|
{
|
|
alternateAppearance = AppearanceTemplateList::createAppearance(path);
|
|
|
|
if (alternateAppearance == nullptr) {
|
|
DEBUG_WARNING(true, ("Object::setAlternateAppearance() - Unable to change the object's appearance because the file does not exist: %s", path));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool const instantlyApplyAppearance = isUsingAlternateAppearance();
|
|
|
|
if(instantlyApplyAppearance)
|
|
{
|
|
if(isInWorld() && m_appearance)
|
|
m_appearance->removeFromWorld();
|
|
}
|
|
|
|
if (m_alternateAppearance == alternateAppearance)
|
|
{
|
|
delete alternateAppearance;
|
|
return;
|
|
}
|
|
|
|
|
|
if (m_alternateAppearance) // delete the old appearance
|
|
{
|
|
// DBE - Copy the transform from the old appearance to the new one.
|
|
// This was added to mimic the behavior of when the appearance-to-world
|
|
// transform was stored in Object instead of Appearance.
|
|
if (alternateAppearance)
|
|
{
|
|
alternateAppearance->setTransform_w(m_alternateAppearance->getTransform_w());
|
|
}
|
|
|
|
delete m_alternateAppearance;
|
|
|
|
m_alternateAppearance = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// DBE - Initialize the transform of the new appearance to our object-to-world
|
|
// transform. This was added to mimic the behavior of when the appearance-to-world
|
|
// transform was stored in Object instead of Appearance.
|
|
alternateAppearance->setTransform_w(getTransform_o2w());
|
|
}
|
|
|
|
// start using the new appearance
|
|
m_alternateAppearance = alternateAppearance;
|
|
|
|
if(instantlyApplyAppearance)
|
|
{
|
|
m_alternateAppearance->setOwner(this);
|
|
m_alternateAppearance->setScale(m_scale);
|
|
|
|
if (isInWorld())
|
|
{
|
|
m_alternateAppearance->addToWorld();
|
|
}
|
|
|
|
m_appearance = m_alternateAppearance;
|
|
}
|
|
else
|
|
{
|
|
useAlternateAppearance();
|
|
}
|
|
|
|
DEBUG_REPORT_LOG(false, ("Successfully set the alternate appearance for [%s] to [%s]\n", getNetworkId().getValueString().c_str(), path));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::useAlternateAppearance()
|
|
{
|
|
if(!m_alternateAppearance || m_alternateAppearance == m_appearance)
|
|
return;
|
|
|
|
if(isInWorld())
|
|
m_appearance->removeFromWorld();
|
|
|
|
m_alternateAppearance->setOwner(this);
|
|
m_alternateAppearance->setScale(m_scale);
|
|
|
|
|
|
if (isInWorld())
|
|
{
|
|
m_alternateAppearance->addToWorld();
|
|
}
|
|
|
|
m_appearance = m_alternateAppearance;
|
|
|
|
DEBUG_REPORT_LOG(false, ("Object [%s] changed to their alternate appearance.\n", getNetworkId().getValueString().c_str()));
|
|
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void Object::useDefaultAppearance()
|
|
{
|
|
if(!m_defaultAppearance || m_defaultAppearance == m_appearance)
|
|
return;
|
|
|
|
if(m_appearance)
|
|
m_appearance->removeFromWorld();
|
|
|
|
m_defaultAppearance->setOwner(this);
|
|
m_defaultAppearance->setScale(m_scale);
|
|
|
|
if (isInWorld())
|
|
{
|
|
m_defaultAppearance->addToWorld();
|
|
}
|
|
|
|
m_appearance = m_defaultAppearance;
|
|
|
|
DEBUG_REPORT_LOG(false, ("Object [%s] changed to their default appearance.\n", getNetworkId().getValueString().c_str()));
|
|
}
|
|
// ======================================================================
|