mirror of
https://github.com/JulianGro/overte.git
synced 2025-08-14 01:38:50 +02:00
Merge pull request #3599 from ZappoMan/frustumCullModelParts
Frustum cull model parts
This commit is contained in:
commit
3d13792a9d
13 changed files with 170 additions and 26 deletions
|
@ -423,6 +423,7 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
|
||||
|
||||
QMenu* modelDebugMenu = developerMenu->addMenu("Models");
|
||||
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DontCullMeshParts, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false);
|
||||
|
|
|
@ -367,6 +367,7 @@ namespace MenuOption {
|
|||
const QString Collisions = "Collisions";
|
||||
const QString Console = "Console...";
|
||||
const QString ControlWithSpeech = "Control With Speech";
|
||||
const QString DontCullMeshParts = "Don't Cull Mesh Parts";
|
||||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DecreaseVoxelSize = "Decrease Voxel Size";
|
||||
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
|
||||
#include "Joystick.h"
|
||||
|
||||
const float MAX_AXIS = 32768.0f;
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
const float MAX_AXIS = 32768.0f;
|
||||
|
||||
Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) :
|
||||
_instanceId(instanceId),
|
||||
|
|
|
@ -314,6 +314,7 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
|
|||
glower = new Glower(entityItem->getGlowLevel());
|
||||
}
|
||||
entityItem->render(args);
|
||||
args->_itemsRendered++;
|
||||
if (glower) {
|
||||
delete glower;
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c
|
|||
|
||||
|
||||
void RenderableModelEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableModelEntityItem::render");
|
||||
PerformanceTimer perfTimer("RMEIrender");
|
||||
assert(getType() == EntityTypes::Model);
|
||||
|
||||
bool drawAsModel = hasModel();
|
||||
|
@ -119,7 +119,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
|||
// TODO: this is the majority of model render time. And rendering of a cube model vs the basic Box render
|
||||
// is significantly more expensive. Is there a way to call this that doesn't cost us as much?
|
||||
PerformanceTimer perfTimer("model->render");
|
||||
_model->render(alpha, modelRenderMode);
|
||||
_model->render(alpha, modelRenderMode, args);
|
||||
} else {
|
||||
// if we couldn't get a model, then just draw a cube
|
||||
glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include <CapsuleShape.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <PerfStat.h>
|
||||
#include <PhysicsEntity.h>
|
||||
#include <ShapeCollider.h>
|
||||
#include <SphereShape.h>
|
||||
|
@ -44,7 +45,8 @@ Model::Model(QObject* parent) :
|
|||
_pupilDilation(0.0f),
|
||||
_url("http://invalid.com"),
|
||||
_blendNumber(0),
|
||||
_appliedBlendNumber(0) {
|
||||
_appliedBlendNumber(0),
|
||||
_calculatedMeshBoxesValid(false) {
|
||||
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
|
@ -384,7 +386,7 @@ void Model::setJointStates(QVector<JointState> states) {
|
|||
_boundingRadius = radius;
|
||||
}
|
||||
|
||||
bool Model::render(float alpha, RenderMode mode) {
|
||||
bool Model::render(float alpha, RenderMode mode, RenderArgs* args) {
|
||||
// render the attachments
|
||||
foreach (Model* attachment, _attachments) {
|
||||
attachment->render(alpha, mode);
|
||||
|
@ -392,6 +394,23 @@ bool Model::render(float alpha, RenderMode mode) {
|
|||
if (_meshStates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we don't have valid mesh boxes, calculate them now, this only matters in cases
|
||||
// where our caller has passed RenderArgs which will include a view frustum we can cull
|
||||
// against. We cache the results of these calculations so long as the model hasn't been
|
||||
// simulated and the mesh hasn't changed.
|
||||
if (args && !_calculatedMeshBoxesValid) {
|
||||
PerformanceTimer perfTimer("calculatedMeshBoxes");
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
int numberOfMeshes = geometry.meshes.size();
|
||||
_calculatedMeshBoxes.resize(numberOfMeshes);
|
||||
for (int i = 0; i < numberOfMeshes; i++) {
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents);
|
||||
_calculatedMeshBoxes[i] = AABox(scaledMeshExtents);
|
||||
}
|
||||
_calculatedMeshBoxesValid = true;
|
||||
}
|
||||
|
||||
// set up dilated textures on first render after load/simulate
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
@ -431,11 +450,12 @@ bool Model::render(float alpha, RenderMode mode) {
|
|||
mode == DEFAULT_RENDER_MODE || mode == NORMAL_RENDER_MODE,
|
||||
mode == DEFAULT_RENDER_MODE);
|
||||
|
||||
renderMeshes(mode, false);
|
||||
const float DEFAULT_ALPHA_THRESHOLD = 0.5f;
|
||||
renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, args);
|
||||
|
||||
// render translucent meshes afterwards
|
||||
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(false, true, true);
|
||||
renderMeshes(mode, true, 0.75f);
|
||||
renderMeshes(mode, true, 0.75f, args);
|
||||
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
glEnable(GL_BLEND);
|
||||
|
@ -445,7 +465,7 @@ bool Model::render(float alpha, RenderMode mode) {
|
|||
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true);
|
||||
|
||||
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
|
||||
renderMeshes(mode, true, 0.0f);
|
||||
renderMeshes(mode, true, 0.0f, args);
|
||||
}
|
||||
|
||||
glDepthMask(true);
|
||||
|
@ -511,6 +531,22 @@ Extents Model::getUnscaledMeshExtents() const {
|
|||
return scaledExtents;
|
||||
}
|
||||
|
||||
Extents Model::calculateScaledOffsetExtents(const Extents& extents) const {
|
||||
// we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix
|
||||
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
|
||||
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
|
||||
|
||||
Extents scaledOffsetExtents = { ((minimum + _offset) * _scale),
|
||||
((maximum + _offset) * _scale) };
|
||||
|
||||
Extents rotatedExtents = scaledOffsetExtents.getRotated(_rotation);
|
||||
|
||||
Extents translatedExtents = { rotatedExtents.minimum + _translation,
|
||||
rotatedExtents.maximum + _translation };
|
||||
return translatedExtents;
|
||||
}
|
||||
|
||||
|
||||
bool Model::getJointState(int index, glm::quat& rotation) const {
|
||||
if (index == -1 || index >= _jointStates.size()) {
|
||||
return false;
|
||||
|
@ -790,6 +826,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
|||
|| (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint);
|
||||
|
||||
if (isActive() && fullUpdate) {
|
||||
_calculatedMeshBoxesValid = false; // if we have to simulate, we need to assume our mesh boxes are all invalid
|
||||
|
||||
// check for scale to fit
|
||||
if (_scaleToFit && !_scaledToFit) {
|
||||
scaleToFit();
|
||||
|
@ -1200,10 +1238,12 @@ void Model::deleteGeometry() {
|
|||
_blendedBlendshapeCoefficients.clear();
|
||||
}
|
||||
|
||||
void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold) {
|
||||
void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args) {
|
||||
updateVisibleJointStates();
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
||||
|
||||
bool cullMeshParts = args && !Menu::getInstance()->isOptionChecked(MenuOption::DontCullMeshParts);
|
||||
|
||||
for (int i = 0; i < networkMeshes.size(); i++) {
|
||||
// exit early if the translucency doesn't match what we're drawing
|
||||
|
@ -1221,6 +1261,23 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
|
|||
continue;
|
||||
}
|
||||
|
||||
// if we got here, then check to see if this mesh is in view
|
||||
if (args) {
|
||||
bool shouldRender = true;
|
||||
args->_meshesConsidered++;
|
||||
|
||||
if (cullMeshParts && args->_viewFrustum) {
|
||||
shouldRender = args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
|
||||
}
|
||||
|
||||
if (shouldRender) {
|
||||
args->_meshesRendered++;
|
||||
} else {
|
||||
args->_meshesOutOfView++;
|
||||
continue; // skip this mesh
|
||||
}
|
||||
}
|
||||
|
||||
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
|
||||
|
||||
ProgramObject* program = &_program;
|
||||
|
@ -1372,6 +1429,13 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
|
|||
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
|
||||
GL_UNSIGNED_INT, (void*)offset);
|
||||
offset += part.triangleIndices.size() * sizeof(int);
|
||||
|
||||
if (args) {
|
||||
const int INDICES_PER_TRIANGLE = 3;
|
||||
const int INDICES_PER_QUAD = 4;
|
||||
args->_trianglesRendered += part.triangleIndices.size() / INDICES_PER_TRIANGLE;
|
||||
args->_quadsRendered += part.quadIndices.size() / INDICES_PER_QUAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mesh.colors.isEmpty()) {
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include <PhysicsEntity.h>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <AnimationCache.h>
|
||||
#include <PhysicsEntity.h>
|
||||
|
||||
#include "GeometryCache.h"
|
||||
#include "InterfaceConfig.h"
|
||||
|
@ -30,6 +30,7 @@ class QScriptEngine;
|
|||
|
||||
class AnimationHandle;
|
||||
class Shape;
|
||||
class RenderArgs;
|
||||
|
||||
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
|
||||
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
|
||||
|
@ -84,7 +85,7 @@ public:
|
|||
|
||||
enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE };
|
||||
|
||||
bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE);
|
||||
bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL);
|
||||
|
||||
/// Sets the URL of the model to render.
|
||||
/// \param fallback the URL of a fallback model to render if the requested model fails to load
|
||||
|
@ -107,6 +108,9 @@ public:
|
|||
/// Returns the unscaled extents of the model's mesh
|
||||
Extents getUnscaledMeshExtents() const;
|
||||
|
||||
/// Returns the scaled equivalent of some extents in model space.
|
||||
Extents calculateScaledOffsetExtents(const Extents& extents) const;
|
||||
|
||||
/// Returns a reference to the shared geometry.
|
||||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||
|
||||
|
@ -247,7 +251,7 @@ private:
|
|||
|
||||
void applyNextGeometry();
|
||||
void deleteGeometry();
|
||||
void renderMeshes(RenderMode mode, bool translucent, float alphaThreshold = 0.5f);
|
||||
void renderMeshes(RenderMode mode, bool translucent, float alphaThreshold = 0.5f, RenderArgs* args = NULL);
|
||||
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||
void initJointTransforms();
|
||||
|
||||
|
@ -325,6 +329,9 @@ private:
|
|||
static SkinLocations _skinTranslucentLocations;
|
||||
|
||||
static void initSkinProgram(ProgramObject& program, SkinLocations& locations, int specularTextureUnit = 1);
|
||||
|
||||
QVector<AABox> _calculatedMeshBoxes;
|
||||
bool _calculatedMeshBoxesValid;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(QPointer<Model>)
|
||||
|
|
|
@ -391,7 +391,7 @@ void Stats::display(
|
|||
|
||||
VoxelSystem* voxels = Application::getInstance()->getVoxels();
|
||||
|
||||
lines = _expanded ? 11 : 3;
|
||||
lines = _expanded ? 14 : 3;
|
||||
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
|
||||
lines += 9; // spatial audio processing adds 1 spacing line and 8 extra lines of info
|
||||
}
|
||||
|
@ -415,6 +415,24 @@ void Stats::display(
|
|||
horizontalOffset += 5;
|
||||
|
||||
if (_expanded) {
|
||||
// Model/Entity render details
|
||||
EntityTreeRenderer* entities = Application::getInstance()->getEntities();
|
||||
voxelStats.str("");
|
||||
voxelStats << "Entity Items rendered: " << entities->getItemsRendered() << " Out of view:" << entities->getItemsOutOfView();
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
||||
|
||||
voxelStats.str("");
|
||||
voxelStats << "Meshes rendered: " << entities->getMeshesRendered() << " Out of view:" << entities->getMeshesOutOfView();
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
||||
|
||||
voxelStats.str("");
|
||||
voxelStats << "Triangles: " << entities->getTrianglesRendered() << " Quads:" << entities->getQuadsRendered();
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
||||
|
||||
|
||||
// Local Voxel Memory Usage
|
||||
voxelStats.str("");
|
||||
voxelStats << "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB";
|
||||
|
|
|
@ -162,12 +162,23 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
|
|||
}
|
||||
|
||||
void OctreeRenderer::render(RenderMode renderMode) {
|
||||
RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, 0, 0, 0 };
|
||||
RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
if (_tree) {
|
||||
_tree->lockForRead();
|
||||
_tree->recurseTreeWithOperation(renderOperation, &args);
|
||||
_tree->unlock();
|
||||
}
|
||||
_meshesConsidered = args._meshesConsidered;
|
||||
_meshesRendered = args._meshesRendered;
|
||||
_meshesOutOfView = args._meshesOutOfView;
|
||||
|
||||
_elementsTouched = args._elementsTouched;
|
||||
_itemsRendered = args._itemsRendered;
|
||||
_itemsOutOfView = args._itemsOutOfView;
|
||||
|
||||
_trianglesRendered = args._trianglesRendered;
|
||||
_quadsRendered = args._quadsRendered;
|
||||
|
||||
}
|
||||
|
||||
void OctreeRenderer::clear() {
|
||||
|
|
|
@ -63,10 +63,33 @@ public:
|
|||
|
||||
/// clears the tree
|
||||
virtual void clear();
|
||||
|
||||
int getElementsTouched() const { return _elementsTouched; }
|
||||
int getItemsRendered() const { return _itemsRendered; }
|
||||
int getItemsOutOfView() const { return _itemsOutOfView; }
|
||||
|
||||
int getMeshesConsidered() const { return _meshesConsidered; }
|
||||
int getMeshesRendered() const { return _meshesRendered; }
|
||||
int getMeshesOutOfView() const { return _meshesOutOfView; }
|
||||
|
||||
int getTrianglesRendered() const { return _trianglesRendered; }
|
||||
int getQuadsRendered() const { return _quadsRendered; }
|
||||
|
||||
protected:
|
||||
Octree* _tree;
|
||||
bool _managedTree;
|
||||
ViewFrustum* _viewFrustum;
|
||||
|
||||
int _elementsTouched;
|
||||
int _itemsRendered;
|
||||
int _itemsOutOfView;
|
||||
|
||||
int _meshesConsidered;
|
||||
int _meshesRendered;
|
||||
int _meshesOutOfView;
|
||||
|
||||
int _trianglesRendered;
|
||||
int _quadsRendered;
|
||||
};
|
||||
|
||||
class RenderArgs {
|
||||
|
@ -80,6 +103,13 @@ public:
|
|||
int _elementsTouched;
|
||||
int _itemsRendered;
|
||||
int _itemsOutOfView;
|
||||
|
||||
int _meshesConsidered;
|
||||
int _meshesRendered;
|
||||
int _meshesOutOfView;
|
||||
|
||||
int _trianglesRendered;
|
||||
int _quadsRendered;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -88,13 +88,11 @@ inline bool operator==(const AABox& a, const AABox& b) {
|
|||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const AABox& box) {
|
||||
const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe
|
||||
|
||||
debug << "AABox[ ("
|
||||
<< box.getCorner().x * (float)TREE_SCALE << "," << box.getCorner().y * (float)TREE_SCALE << "," << box.getCorner().z * (float)TREE_SCALE << " ) to ("
|
||||
<< box.calcTopFarLeft().x * (float)TREE_SCALE << "," << box.calcTopFarLeft().y * (float)TREE_SCALE << "," << box.calcTopFarLeft().z * (float)TREE_SCALE << ") size: ("
|
||||
<< box.getDimensions().x * (float)TREE_SCALE << "," << box.getDimensions().y * (float)TREE_SCALE << "," << box.getDimensions().z * (float)TREE_SCALE << ")"
|
||||
<< " in meters]";
|
||||
<< box.getCorner().x << "," << box.getCorner().y << "," << box.getCorner().z << " ) to ("
|
||||
<< box.calcTopFarLeft().x << "," << box.calcTopFarLeft().y << "," << box.calcTopFarLeft().z << ") size: ("
|
||||
<< box.getDimensions().x << "," << box.getDimensions().y << "," << box.getDimensions().z << ")"
|
||||
<< "]";
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
|
|
@ -79,12 +79,11 @@ inline bool operator==(const AACube& a, const AACube& b) {
|
|||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const AACube& cube) {
|
||||
const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe
|
||||
debug << "AACube[ ("
|
||||
<< cube.getCorner().x * (float)TREE_SCALE << "," << cube.getCorner().y * (float)TREE_SCALE << "," << cube.getCorner().z * (float)TREE_SCALE << " ) to ("
|
||||
<< cube.calcTopFarLeft().x * (float)TREE_SCALE << "," << cube.calcTopFarLeft().y * (float)TREE_SCALE << "," << cube.calcTopFarLeft().z * (float)TREE_SCALE << ") size: ("
|
||||
<< cube.getDimensions().x * (float)TREE_SCALE << "," << cube.getDimensions().y * (float)TREE_SCALE << "," << cube.getDimensions().z * (float)TREE_SCALE << ")"
|
||||
<< " in meters]";
|
||||
<< cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to ("
|
||||
<< cube.calcTopFarLeft().x << "," << cube.calcTopFarLeft().y << "," << cube.calcTopFarLeft().z << ") size: ("
|
||||
<< cube.getDimensions().x << "," << cube.getDimensions().y << "," << cube.getDimensions().z << ")"
|
||||
<< "]";
|
||||
return debug;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
#include "StreamUtils.h"
|
||||
|
||||
class Extents {
|
||||
public:
|
||||
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
|
||||
|
@ -54,4 +57,15 @@ public:
|
|||
glm::vec3 maximum;
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const Extents& extents) {
|
||||
debug << "Extents[ ("
|
||||
<< extents.minimum << " ) to ("
|
||||
<< extents.maximum << ") size: ("
|
||||
<< (extents.maximum - extents.minimum) << ")"
|
||||
<< " ]";
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
|
||||
#endif // hifi_Extents_h
|
Loading…
Reference in a new issue