Adding support for the meta rendering

This commit is contained in:
samcake 2017-01-16 17:47:58 -08:00
parent 95aad3140d
commit 83d1c3ad01
15 changed files with 156 additions and 60 deletions

View file

@ -62,7 +62,7 @@ const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
return ItemKey::Builder::opaqueShape();
return ItemKey::Builder::opaqueShape().withTypeMeta();
}
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) {
return static_pointer_cast<Avatar>(avatar)->getBounds();
@ -74,6 +74,15 @@ namespace render {
avatarPtr->render(args, qApp->getCamera()->getPosition());
}
}
template <> uint32_t metaFetchMetaSubItems(const AvatarSharedPointer& avatar, ItemIDs& subItems) {
auto avatarPtr = static_pointer_cast<Avatar>(avatar);
if (avatarPtr->getSkeletonModel()) {
auto metaSubItems = avatarPtr->getSkeletonModel()->fetchRenderItemIDs();
subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end());
return metaSubItems.size();
}
return 0;
}
}
static uint64_t timeProcessingJoints = 0;

View file

@ -32,6 +32,7 @@ namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar);
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar);
template <> void payloadRender(const AvatarSharedPointer& avatar, RenderArgs* args);
template <> uint32_t metaFetchMetaSubItems(const AvatarSharedPointer& avatar, ItemIDs& subItems);
}
static const float SCALING_RATIO = .05f;

View file

@ -15,21 +15,21 @@
namespace render {
template <> const ItemKey payloadGetKey(const RenderableEntityItemProxy::Pointer& payload) {
if (payload && payload->entity) {
if (payload->entity->getType() == EntityTypes::Light) {
return ItemKey::Builder::light();
if (payload && payload->_entity) {
if (payload->_entity->getType() == EntityTypes::Light) {
return ItemKey::Builder::light().withTypeMeta();
}
if (payload && payload->entity->isTransparent()) {
return ItemKey::Builder::transparentShape();
if (payload && payload->_entity->isTransparent()) {
return ItemKey::Builder::transparentShape().withTypeMeta();
}
}
return ItemKey::Builder::opaqueShape();
return ItemKey::Builder::opaqueShape().withTypeMeta();
}
template <> const Item::Bound payloadGetBound(const RenderableEntityItemProxy::Pointer& payload) {
if (payload && payload->entity) {
if (payload && payload->_entity) {
bool success;
auto result = payload->entity->getAABox(success);
auto result = payload->_entity->getAABox(success);
if (!success) {
return render::Item::Bound();
}
@ -39,11 +39,19 @@ namespace render {
}
template <> void payloadRender(const RenderableEntityItemProxy::Pointer& payload, RenderArgs* args) {
if (args) {
if (payload && payload->entity && payload->entity->getVisible()) {
payload->entity->render(args);
if (payload && payload->_entity && payload->_entity->getVisible()) {
payload->_entity->render(args);
}
}
}
template <> uint32_t metaFetchMetaSubItems(const RenderableEntityItemProxy::Pointer& payload, ItemIDs& subItems) {
auto metaID = payload->_metaID;
if (Item::isValidID(metaID)) {
subItems.emplace_back(metaID);
return 1;
}
return 0;
}
}
void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) {

View file

@ -36,17 +36,19 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status:
class RenderableEntityItemProxy {
public:
RenderableEntityItemProxy(EntityItemPointer entity) : entity(entity) { }
RenderableEntityItemProxy(EntityItemPointer entity, render::ItemID metaID) : _entity(entity), _metaID(metaID) { }
typedef render::Payload<RenderableEntityItemProxy> Payload;
typedef Payload::DataPointer Pointer;
EntityItemPointer entity;
EntityItemPointer _entity;
render::ItemID _metaID;
};
namespace render {
template <> const ItemKey payloadGetKey(const RenderableEntityItemProxy::Pointer& payload);
template <> const Item::Bound payloadGetBound(const RenderableEntityItemProxy::Pointer& payload);
template <> void payloadRender(const RenderableEntityItemProxy::Pointer& payload, RenderArgs* args);
template <> uint32_t metaFetchMetaSubItems(const RenderableEntityItemProxy::Pointer& payload, ItemIDs& subItems);
}
// Mixin class for implementing basic single item rendering
@ -55,7 +57,7 @@ public:
bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
_myItem = scene->allocateID();
auto renderData = std::make_shared<RenderableEntityItemProxy>(self);
auto renderData = std::make_shared<RenderableEntityItemProxy>(self, _myItem);
auto renderPayload = std::make_shared<RenderableEntityItemProxy::Payload>(renderData);
render::Item::Status::Getters statusGetters;

View file

@ -194,7 +194,7 @@ public:
namespace render {
template <> const ItemKey payloadGetKey(const RenderableModelEntityItemMeta::Pointer& payload) {
return ItemKey::Builder::opaqueShape();
return ItemKey::Builder::opaqueShape().withTypeMeta();
}
template <> const Item::Bound payloadGetBound(const RenderableModelEntityItemMeta::Pointer& payload) {
@ -216,6 +216,15 @@ namespace render {
}
}
}
template <> uint32_t metaFetchMetaSubItems(const RenderableModelEntityItemMeta::Pointer& payload, ItemIDs& subItems) {
auto modelEntity = std::static_pointer_cast<RenderableModelEntityItem>(payload->entity);
if (modelEntity->hasModel()) {
auto metaSubItems = modelEntity->getModelNotSafe()->fetchRenderItemIDs();
subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end());
return (uint32_t) metaSubItems.size();
}
return 0;
}
}
bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene,
@ -473,6 +482,10 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
}
}
ModelPointer RenderableModelEntityItem::getModelNotSafe() {
return _model;
}
ModelPointer RenderableModelEntityItem::getModel(QSharedPointer<EntityTreeRenderer> renderer) {
if (!renderer) {
return nullptr;

View file

@ -52,6 +52,7 @@ public:
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const override;
ModelPointer getModel(QSharedPointer<EntityTreeRenderer> renderer);
ModelPointer getModelNotSafe();
virtual bool needsToCallUpdate() const override;
virtual void update(const quint64& now) override;

View file

@ -673,6 +673,7 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent();
verticesCount += renderItem.get()->getVerticesCount();
_modelMeshRenderItems.insert(item, renderPayload);
_modelMeshRenderItemIDs.emplace_back(item);
}
somethingAdded = !_modelMeshRenderItems.empty();
@ -695,6 +696,7 @@ void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::Pendin
foreach (auto item, _modelMeshRenderItems.keys()) {
pendingChanges.removeItem(item);
}
_modelMeshRenderItemIDs.clear();
_modelMeshRenderItems.clear();
_modelMeshRenderItemsSet.clear();
@ -1301,6 +1303,10 @@ AABox Model::getRenderableMeshBound() const {
}
}
const render::ItemIDs& Model::fetchRenderItemIDs() const {
return _modelMeshRenderItemIDs;
}
void Model::createRenderItemSet() {
if (_collisionGeometry) {
if (_collisionRenderItemsSet.empty()) {
@ -1479,6 +1485,7 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) {
{
Lock lock(_mutex);
_modelsRequiringBlends.insert(model);
}
}

View file

@ -105,6 +105,7 @@ public:
void setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; }
bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; }
AABox getRenderableMeshBound() const;
const render::ItemIDs& fetchRenderItemIDs() const;
bool maybeStartBlender();
@ -389,6 +390,8 @@ protected:
QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _modelMeshRenderItems;
render::ItemIDs _modelMeshRenderItemIDs;
bool _addedToScene { false }; // has been added to scene
bool _needsFixupInScene { true }; // needs to be removed/re-added to scene
bool _needsReload { true };

View file

@ -53,14 +53,15 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>();
initDeferredPipelines(*shapePlumber);
// Extract opaques / transparents / lights / overlays
const auto opaques = items[0];
const auto transparents = items[1];
const auto lights = items[2];
const auto overlayOpaques = items[3];
const auto overlayTransparents = items[4];
const auto background = items[5];
const auto spatialSelection = items[6];
// Extract opaques / transparents / lights / metas / overlays / background
const auto opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE];
const auto transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE];
const auto lights = items[RenderFetchCullSortTask::LIGHT];
const auto metas = items[RenderFetchCullSortTask::META];
const auto overlayOpaques = items[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE];
const auto overlayTransparents = items[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE];
const auto background = items[RenderFetchCullSortTask::BACKGROUND];
const auto spatialSelection = items[RenderFetchCullSortTask::SPATIAL_SELECTION];
// Prepare deferred, generate the shared Deferred Frame Transform
const auto deferredFrameTransform = addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform");

View file

@ -36,13 +36,15 @@ RenderForwardTask::RenderForwardTask(RenderFetchCullSortTask::Output items) {
ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>();
initForwardPipelines(*shapePlumber);
// Extract opaques / transparents / lights / overlays
const auto opaques = items[0];
const auto transparents = items[1];
const auto lights = items[2];
const auto overlayOpaques = items[3];
const auto overlayTransparents = items[4];
const auto background = items[5];
// Extract opaques / transparents / lights / metas / overlays / background
const auto opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE];
const auto transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE];
const auto lights = items[RenderFetchCullSortTask::LIGHT];
const auto metas = items[RenderFetchCullSortTask::META];
const auto overlayOpaques = items[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE];
const auto overlayTransparents = items[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE];
const auto background = items[RenderFetchCullSortTask::BACKGROUND];
const auto spatialSelection = items[RenderFetchCullSortTask::SPATIAL_SELECTION];
const auto framebuffer = addJob<PrepareFramebuffer>("PrepareFramebuffer");

View file

@ -38,6 +38,7 @@ public:
enum FlagBit {
TYPE_SHAPE = 0, // Item is a Shape
TYPE_LIGHT, // Item is a Light
TYPE_META, // Item is a Meta: meanning it s used to represent a higher level object, potentially represented by other render items
TRANSLUCENT, // Transparent and not opaque, for some odd reason TRANSPARENCY doesn't work...
VIEW_SPACE, // Transformed in view space, and not in world space
DYNAMIC, // Dynamic and bound will change unlike static item
@ -72,6 +73,7 @@ public:
Builder& withTypeShape() { _flags.set(TYPE_SHAPE); return (*this); }
Builder& withTypeLight() { _flags.set(TYPE_LIGHT); return (*this); }
Builder& withTypeMeta() { _flags.set(TYPE_META); return (*this); }
Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); }
Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); }
Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); }
@ -91,6 +93,7 @@ public:
bool isShape() const { return _flags[TYPE_SHAPE]; }
bool isLight() const { return _flags[TYPE_LIGHT]; }
bool isMeta() const { return _flags[TYPE_META]; }
bool isOpaque() const { return !_flags[TRANSLUCENT]; }
bool isTransparent() const { return _flags[TRANSLUCENT]; }
@ -150,6 +153,7 @@ public:
Builder& withTypeShape() { _value.set(ItemKey::TYPE_SHAPE); _mask.set(ItemKey::TYPE_SHAPE); return (*this); }
Builder& withTypeLight() { _value.set(ItemKey::TYPE_LIGHT); _mask.set(ItemKey::TYPE_LIGHT); return (*this); }
Builder& withTypeMeta() { _value.set(ItemKey::TYPE_META); _mask.set(ItemKey::TYPE_META); return (*this); }
Builder& withOpaque() { _value.reset(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
Builder& withTransparent() { _value.set(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
@ -179,6 +183,7 @@ public:
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); }
static Builder light() { return Builder().withTypeLight(); }
static Builder meta() { return Builder().withTypeMeta(); }
static Builder background() { return Builder().withViewSpace().withLayered(); }
static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); }
static Builder transparentShapeLayered() { return Builder().withTypeShape().withTransparent().withWorldSpace().withLayered(); }
@ -210,6 +215,25 @@ inline QDebug operator<<(QDebug debug, const ItemFilter& me) {
using ItemID = uint32_t;
using ItemCell = int32_t;
// A few typedefs for standard containers of ItemIDs
using ItemIDs = std::vector<ItemID>;
using ItemIDSet = std::set<ItemID>;
// Handy type to just pass the ID and the bound of an item
class ItemBound {
public:
ItemBound(ItemID id) : id(id) { }
ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { }
ItemID id;
AABox bound;
};
// many Item Bounds in a vector
using ItemBounds = std::vector<ItemBound>;
// Item is the proxy to a bounded "object" in the scene
// An item is described by its Key
class Item {
public:
typedef std::vector<Item> Vector;
@ -295,6 +319,8 @@ public:
virtual const ShapeKey getShapeKey() const = 0;
virtual uint32_t fetchMetaSubItems(ItemIDs& subItems) const = 0;
~PayloadInterface() {}
// Status interface is local to the base class
@ -313,6 +339,9 @@ public:
Item() {}
~Item() {}
// Item exists if it has a valid payload
bool exist() const { return (bool)(_payload); }
// Main scene / item managment interface reset/update/kill
void resetPayload(const PayloadPointer& payload);
void resetCell(ItemCell cell = INVALID_CELL, bool _small = false) { _cell = cell; _key.setSmaller(_small); }
@ -339,6 +368,9 @@ public:
// Shape Type Interface
const ShapeKey getShapeKey() const { return _payload->getShapeKey(); }
// Meta Type Interface
uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return _payload->fetchMetaSubItems(subItems); }
// Access the status
const StatusPointer& getStatus() const { return _payload->getStatus(); }
@ -370,10 +402,7 @@ inline QDebug operator<<(QDebug debug, const Item& item) {
return debug;
}
// THe Payload class is the real Payload to be used
// THis allow anything to be turned into a Payload as long as the required interface functions are available
// When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
// of the Payload interface
// Item shared interface supported by the payload
template <class T> const ItemKey payloadGetKey(const std::shared_ptr<T>& payloadData) { return ItemKey(); }
template <class T> const Item::Bound payloadGetBound(const std::shared_ptr<T>& payloadData) { return Item::Bound(); }
template <class T> int payloadGetLayer(const std::shared_ptr<T>& payloadData) { return 0; }
@ -385,6 +414,14 @@ template <class T> void payloadRender(const std::shared_ptr<T>& payloadData, Ren
// implying that the shape will setup its own pipeline without the use of the ShapeKey.
template <class T> const ShapeKey shapeGetShapeKey(const std::shared_ptr<T>& payloadData) { return ShapeKey::Builder::ownPipeline(); }
// Meta Type Interface
// Meta items act as the grouping object for several sub items (typically shapes).
template <class T> uint32_t metaFetchMetaSubItems(const std::shared_ptr<T>& payloadData, ItemIDs& subItems) { return 0; }
// THe Payload class is the real Payload to be used
// THis allow anything to be turned into a Payload as long as the required interface functions are available
// When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
// of the Payload interface
template <class T> class Payload : public Item::PayloadInterface {
public:
typedef std::shared_ptr<T> DataPointer;
@ -403,6 +440,9 @@ public:
// Shape Type interface
virtual const ShapeKey getShapeKey() const override { return shapeGetShapeKey<T>(_data); }
// Meta Type Interface
virtual uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return metaFetchMetaSubItems<T>(_data, subItems); }
protected:
DataPointer _data;
@ -450,22 +490,6 @@ template <> const Item::Bound payloadGetBound(const FooPointer& foo) {
typedef Item::PayloadPointer PayloadPointer;
typedef std::vector< PayloadPointer > Payloads;
// A few typedefs for standard containers of ItemIDs
using ItemIDs = std::vector<ItemID>;
using ItemIDSet = std::set<ItemID>;
// Handy type to just pass the ID and the bound of an item
class ItemBound {
public:
ItemBound(ItemID id) : id(id) { }
ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { }
ItemID id;
AABox bound;
};
// many Item Bounds in a vector
using ItemBounds = std::vector<ItemBound>;
// A map of items by ShapeKey to optimize rendering pipeline assignments
using ShapeBounds = std::unordered_map<ShapeKey, ItemBounds, ShapeKey::Hash, ShapeKey::KeyEqual>;

View file

@ -29,33 +29,41 @@ RenderFetchCullSortTask::RenderFetchCullSortTask(CullFunctor cullFunctor) {
const auto nonspatialSelection = addJob<FetchNonspatialItems>("FetchOverlaySelection");
// Multi filter visible items into different buckets
const int NUM_FILTERS = 3;
const int NUM_SPATIAL_FILTERS = 4;
const int NUM_NON_SPATIAL_FILTERS = 3;
const int OPAQUE_SHAPE_BUCKET = 0;
const int TRANSPARENT_SHAPE_BUCKET = 1;
const int LIGHT_BUCKET = 2;
const int META_BUCKET = 3;
const int BACKGROUND_BUCKET = 2;
MultiFilterItem<NUM_FILTERS>::ItemFilterArray spatialFilters = { {
MultiFilterItem<NUM_SPATIAL_FILTERS>::ItemFilterArray spatialFilters = { {
ItemFilter::Builder::opaqueShape(),
ItemFilter::Builder::transparentShape(),
ItemFilter::Builder::light()
ItemFilter::Builder::light(),
ItemFilter::Builder::meta()
} };
MultiFilterItem<NUM_FILTERS>::ItemFilterArray nonspatialFilters = { {
MultiFilterItem<NUM_NON_SPATIAL_FILTERS>::ItemFilterArray nonspatialFilters = { {
ItemFilter::Builder::opaqueShape(),
ItemFilter::Builder::transparentShape(),
ItemFilter::Builder::background()
} };
const auto filteredSpatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSpatialSelection, spatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
const auto filteredNonspatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
const auto filteredSpatialBuckets =
addJob<MultiFilterItem<NUM_SPATIAL_FILTERS>>("FilterSceneSelection", culledSpatialSelection, spatialFilters)
.get<MultiFilterItem<NUM_SPATIAL_FILTERS>::ItemBoundsArray>();
const auto filteredNonspatialBuckets =
addJob<MultiFilterItem<NUM_NON_SPATIAL_FILTERS>>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters)
.get<MultiFilterItem<NUM_NON_SPATIAL_FILTERS>::ItemBoundsArray>();
// Extract opaques / transparents / lights / overlays
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredSpatialBuckets[OPAQUE_SHAPE_BUCKET]);
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredSpatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
const auto lights = filteredSpatialBuckets[LIGHT_BUCKET];
const auto metas = filteredSpatialBuckets[META_BUCKET];
const auto overlayOpaques = addJob<DepthSortItems>("DepthSortOverlayOpaque", filteredNonspatialBuckets[OPAQUE_SHAPE_BUCKET]);
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET];
setOutput(Output{{
opaques, transparents, lights, overlayOpaques, overlayTransparents, background, spatialSelection }});
opaques, transparents, lights, metas, overlayOpaques, overlayTransparents, background, spatialSelection }});
}

View file

@ -19,7 +19,21 @@
class RenderFetchCullSortTask : public render::Task {
public:
using Output = std::array<render::Varying, 7>;
enum Buckets {
OPAQUE_SHAPE = 0,
TRANSPARENT_SHAPE,
LIGHT,
META,
OVERLAY_OPAQUE_SHAPE,
OVERLAY_TRANSPARENT_SHAPE,
BACKGROUND,
SPATIAL_SELECTION,
NUM_BUCKETS
};
using Output = std::array<render::Varying, Buckets::NUM_BUCKETS>;
using JobModel = ModelO<RenderFetchCullSortTask>;
RenderFetchCullSortTask(render::CullFunctor cullFunctor);

View file

@ -58,7 +58,7 @@ ItemID Scene::allocateID() {
return _IDAllocator.fetch_add(1);
}
bool Scene::isAllocatedID(const ItemID& id) {
bool Scene::isAllocatedID(const ItemID& id) const {
return Item::isValidID(id) && (id < _numAllocatedItems.load());
}

View file

@ -61,7 +61,7 @@ public:
ItemID allocateID();
// Check that the ID is valid and allocated for this scene, this a threadsafe call
bool isAllocatedID(const ItemID& id);
bool isAllocatedID(const ItemID& id) const;
// THis is the total number of allocated items, this a threadsafe call
size_t getNumItems() const { return _numAllocatedItems.load(); }
@ -78,6 +78,9 @@ public:
// WARNING, There is No check on the validity of the ID, so this could return a bad Item
const Item& getItem(const ItemID& id) const { return _items[id]; }
// Same as getItem, checking if the id is valid
const Item getItemSafe(const ItemID& id) const { if (isAllocatedID(id)) { return _items[id]; } else { return Item(); } }
// Access the spatialized items
const ItemSpatialTree& getSpatialTree() const { return _masterSpatialTree; }