place models in proper containing voxel

This commit is contained in:
ZappoMan 2014-05-07 17:32:51 -07:00
parent 37ca6efc7c
commit 128e4a13de
15 changed files with 303 additions and 69 deletions

View file

@ -309,7 +309,9 @@ Menu::Menu() :
QMenu* modelOptionsMenu = developerMenu->addMenu("Model Options");
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::Models, 0, true);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelProxies, 0, false);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementChildProxies, 0, false);
QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options");

View file

@ -303,7 +303,9 @@ namespace MenuOption {
const QString DisplayFrustum = "Display Frustum";
const QString DisplayHands = "Display Hands";
const QString DisplayHandTargets = "Display Hand Targets";
const QString DisplayModelProxies = "Display Model Proxies";
const QString DisplayModelBounds = "Display Model Bounds";
const QString DisplayModelElementProxy = "Display Model Element Bounds";
const QString DisplayModelElementChildProxies = "Display Model Element Children";
const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes";
const QString EchoLocalAudio = "Echo Local Audio";
const QString EchoServerAudio = "Echo Server Audio";

View file

@ -66,55 +66,130 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
const QList<ModelItem>& modelItems = modelTreeElement->getModels();
uint16_t numberOfModels = modelItems.size();
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
bool displayElementProxy = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementProxy);
bool displayElementChildProxies = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementChildProxies);
if (!isShadowMode && displayElementProxy && numberOfModels > 0) {
glm::vec3 elementCenter = modelTreeElement->getAABox().calcCenter() * (float)TREE_SCALE;
float elementSize = modelTreeElement->getScale() * (float)TREE_SCALE;
glColor3f(1.0f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z);
glutWireCube(elementSize);
glPopMatrix();
if (displayElementChildProxies) {
// draw the children
float halfSize = elementSize / 2.0f;
float quarterSize = elementSize / 4.0f;
glColor3f(1.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(1.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(1.0f, 1.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.5f, 0.5f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.5f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.5f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
}
}
for (uint16_t i = 0; i < numberOfModels; i++) {
const ModelItem& modelItem = modelItems[i];
// render modelItem aspoints
glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE;
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
float radius = modelItem.getRadius() * (float)TREE_SCALE;
//glm::vec3 center = position + glm::vec3(radius, radius, radius); // center it around the position
AABox modelBox = modelItem.getAABox();
modelBox.scale(TREE_SCALE);
if (args->_viewFrustum->boxInFrustum(modelBox) != ViewFrustum::OUTSIDE) {
glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE;
float radius = modelItem.getRadius() * (float)TREE_SCALE;
float size = modelItem.getSize() * (float)TREE_SCALE;
bool drawAsModel = modelItem.hasModel();
bool drawAsModel = modelItem.hasModel();
args->_renderedItems++;
args->_renderedItems++;
if (drawAsModel) {
glPushMatrix();
const float alpha = 1.0f;
if (drawAsModel) {
glPushMatrix();
const float alpha = 1.0f;
Model* model = getModel(modelItem.getModelURL());
Model* model = getModel(modelItem.getModelURL());
model->setScaleToFit(true, radius * 2.0f);
model->setSnapModelToCenter(true);
model->setScaleToFit(true, radius * 2.0f);
model->setSnapModelToCenter(true);
// set the rotation
glm::quat rotation = modelItem.getModelRotation();
model->setRotation(rotation);
// set the rotation
glm::quat rotation = modelItem.getModelRotation();
model->setRotation(rotation);
// set the position
model->setTranslation(position);
model->simulate(0.0f);
// set the position
model->setTranslation(position);
model->simulate(0.0f);
// TODO: should we allow modelItems to have alpha on their models?
Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE
? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
model->render(alpha, modelRenderMode);
// TODO: should we allow modelItems to have alpha on their models?
Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE
? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
model->render(alpha, modelRenderMode);
const bool wantDebugSphere = false;
if (wantDebugSphere) {
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutWireSphere(radius, 15, 15);
glPopMatrix();
}
if (!isShadowMode && displayModelBounds) {
glColor3f(0.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutWireCube(size);
glPopMatrix();
}
glPopMatrix();
} else {
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
glPopMatrix();
} else {
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
}
}
}
}

View file

@ -85,6 +85,9 @@ public:
/// used by ModelScriptingInterface to return ModelItemProperties for unknown models
void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; }
glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); }
private:
glm::vec3 _position;
@ -156,11 +159,20 @@ public:
/// get position in domain scale units (0.0 - 1.0)
const glm::vec3& getPosition() const { return _position; }
glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); }
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
/// get radius in domain scale units (0.0 - 1.0)
float getRadius() const { return _radius; }
/// get maximum dimension in domain scale units (0.0 - 1.0)
float getSize() const { return _radius * 2.0f; }
/// get maximum dimension in domain scale units (0.0 - 1.0)
AABox getAABox() const { return AABox(getMinimumPoint(), getSize()); }
// model related properties
bool hasModel() const { return !_modelURL.isEmpty(); }

View file

@ -113,13 +113,10 @@ void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& send
FindAndUpdateModelOperator theOperator(model);
recurseTreeWithOperator(&theOperator);
// if we didn't find it in the tree, then store it...
if (!theOperator.wasFound()) {
glm::vec3 position = model.getPosition();
float size = std::max(MINIMUM_MODEL_ELEMENT_SIZE, model.getRadius());
ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size);
AABox modelBox = model.getAABox();
ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementContaining(model.getAABox());
element->storeModel(model);
}
// what else do we need to do here to get reaveraging to work
@ -494,12 +491,29 @@ void ModelTree::update() {
lockForWrite();
_isDirty = true;
// TODO: we don't need to update models yet, but when we do, for example
// when we add animation support, we will revisit this code.
//ModelTreeUpdateArgs args = { };
//recurseTreeWithOperation(updateOperation, &args);
ModelTreeUpdateArgs args = { };
recurseTreeWithOperation(updateOperation, &args);
// Now is a reasonable time to prune the tree...
// now add back any of the particles that moved elements....
int movingModels = args._movingModels.size();
for (int i = 0; i < movingModels; i++) {
bool shouldDie = args._movingModels[i].getShouldDie();
// if the particle is still inside our total bounds, then re-add it
AABox treeBounds = getRoot()->getAABox();
if (!shouldDie && treeBounds.contains(args._movingModels[i].getPosition())) {
storeModel(args._movingModels[i]);
} else {
uint32_t modelItemID = args._movingModels[i].getID();
quint64 deletedAt = usecTimestampNow();
_recentlyDeletedModelsLock.lockForWrite();
_recentlyDeletedModelItemIDs.insert(deletedAt, modelItemID);
_recentlyDeletedModelsLock.unlock();
}
}
// prune the tree...
recurseTreeWithOperation(pruneOperation, NULL);
unlock();
}

View file

@ -47,15 +47,32 @@ ModelTreeElement* ModelTreeElement::addChildAtIndex(int index) {
}
bool ModelTreeElement::appendElementData(OctreePacketData* packetData) const {
bool ModelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const {
bool success = true; // assume the best...
// write our models out...
uint16_t numberOfModels = _modelItems->size();
// write our models out... first determine which of the models are in view based on our params
uint16_t numberOfModels = 0;
QVector<uint16_t> indexesOfModelsToInclude;
for (uint16_t i = 0; i < _modelItems->size(); i++) {
if (params.viewFrustum) {
const ModelItem& model = (*_modelItems)[i];
AABox modelBox = model.getAABox();
modelBox.scale(TREE_SCALE);
if (params.viewFrustum->boxInFrustum(modelBox) != ViewFrustum::OUTSIDE) {
indexesOfModelsToInclude << i;
numberOfModels++;
}
} else {
indexesOfModelsToInclude << i;
numberOfModels++;
}
}
success = packetData->appendValue(numberOfModels);
if (success) {
for (uint16_t i = 0; i < numberOfModels; i++) {
foreach (uint16_t i, indexesOfModelsToInclude) {
const ModelItem& model = (*_modelItems)[i];
success = model.appendModelData(packetData);
if (!success) {
@ -66,10 +83,25 @@ bool ModelTreeElement::appendElementData(OctreePacketData* packetData) const {
return success;
}
void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
markWithChangedTime();
// TODO: early exit when _modelItems is empty
bool ModelTreeElement::containsModelBounds(const ModelItem& model) const {
return _box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint());
}
bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const {
if (_box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint())) {
int childForMinimumPoint = getMyChildContainingPoint(model.getMinimumPoint());
int childForMaximumPoint = getMyChildContainingPoint(model.getMaximumPoint());
// If I contain both the minimum and maximum point, but two different children of mine
// contain those points, then I am the best fit for that model
if (childForMinimumPoint != childForMaximumPoint) {
return true;
}
}
return false;
}
void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
// update our contained models
QList<ModelItem>::iterator modelItr = _modelItems->begin();
while(modelItr != _modelItems->end()) {
@ -78,19 +110,18 @@ void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
// If the model wants to die, or if it's left our bounding box, then move it
// into the arguments moving models. These will be added back or deleted completely
if (model.getShouldDie() || !_box.contains(model.getPosition())) {
if (model.getShouldDie() || !bestFitModelBounds(model)) {
args._movingModels.push_back(model);
// erase this model
modelItr = _modelItems->erase(modelItr);
// this element has changed so mark it...
markWithChangedTime();
} else {
++modelItr;
}
}
// TODO: if _modelItems is empty after while loop consider freeing memory in _modelItems if
// internal array is too big (QList internal array does not decrease size except in dtor and
// assignment operator). Otherwise _modelItems could become a "resource leak" for large
// roaming piles of models.
}
bool ModelTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
@ -136,6 +167,7 @@ bool ModelTreeElement::updateModel(const ModelItem& model) {
(localOlder ? "OLDER" : "NEWER"),
difference, debug::valueOf(model.isNewlyCreated()) );
}
thisModel.copyChangedProperties(model);
markWithChangedTime();
} else {

View file

@ -74,7 +74,7 @@ public:
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const;
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const;
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
@ -118,6 +118,9 @@ public:
bool removeModelWithID(uint32_t id);
bool containsModelBounds(const ModelItem& model) const;
bool bestFitModelBounds(const ModelItem& model) const;
protected:
virtual void init(unsigned char * octalCode);

View file

@ -578,6 +578,10 @@ OctreeElement* Octree::getOrCreateChildElementAt(float x, float y, float z, floa
return getRoot()->getOrCreateChildElementAt(x, y, z, s);
}
OctreeElement* Octree::getOrCreateChildElementContaining(const AABox& box) {
return getRoot()->getOrCreateChildElementContaining(box);
}
// combines the ray cast arguments into a single object
class RayArgs {
@ -1001,7 +1005,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
// Keep track of how deep we've encoded.
currentEncodeLevel++;
params.maxLevelReached = std::max(currentEncodeLevel,params.maxLevelReached);
params.maxLevelReached = std::max(currentEncodeLevel, params.maxLevelReached);
// If we've reached our max Search Level, then stop searching.
if (currentEncodeLevel >= params.maxEncodeLevel) {
@ -1342,7 +1346,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
OctreeElement* childElement = element->getChildAtIndex(i);
if (childElement) {
int bytesBeforeChild = packetData->getUncompressedSize();
continueThisLevel = childElement->appendElementData(packetData);
continueThisLevel = childElement->appendElementData(packetData, params);
int bytesAfterChild = packetData->getUncompressedSize();
if (!continueThisLevel) {

View file

@ -227,6 +227,7 @@ public:
OctreeElement* getOctreeEnclosingElementAt(float x, float y, float z, float s) const;
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
OctreeElement* getOrCreateChildElementContaining(const AABox& box);
void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL);
void recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData = NULL);

View file

@ -1379,3 +1379,90 @@ OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float
// Now that we have the child to recurse down, let it answer the original question...
return child->getOrCreateChildElementAt(x, y, z, s);
}
OctreeElement* OctreeElement::getOrCreateChildElementContaining(const AABox& box) {
OctreeElement* child = NULL;
float ourScale = getScale();
float boxScale = box.getScale();
if(boxScale > ourScale) {
qDebug("UNEXPECTED -- OctreeElement::getOrCreateChildElementContaining() "
"boxScale=[%f] > ourScale=[%f] ", boxScale, ourScale);
}
// Determine which of our children the minimum and maximum corners of the box live in...
glm::vec3 boxCornerMinimum = box.getCorner();
glm::vec3 boxCornerMaximum = box.calcTopFarLeft();
int childIndexBoxMinimum = getMyChildContainingPoint(boxCornerMinimum);
int childIndexBoxMaximum = getMyChildContainingPoint(boxCornerMaximum);
// If the minimum and maximum corners of the box are in two different children's boxes, then we are the containing element
if (childIndexBoxMinimum != childIndexBoxMaximum) {
return this;
}
// otherwise, they are the same and that child should be considered as the correct element
int childIndex = childIndexBoxMinimum; // both the same...
// Now, check if we have a child at that location
child = getChildAtIndex(childIndex);
if (!child) {
child = addChildAtIndex(childIndex);
}
// Now that we have the child to recurse down, let it answer the original question...
return child->getOrCreateChildElementContaining(box);
}
int OctreeElement::getMyChildContainingPoint(const glm::vec3& point) const {
glm::vec3 ourCenter = _box.calcCenter();
int childIndex = CHILD_UNKNOWN;
// left half
if (point.x > ourCenter.x) {
if (point.y > ourCenter.y) {
// top left
if (point.z > ourCenter.z) {
// top left far
childIndex = CHILD_TOP_LEFT_FAR;
} else {
// top left near
childIndex = CHILD_TOP_LEFT_NEAR;
}
} else {
// bottom left
if (point.z > ourCenter.z) {
// bottom left far
childIndex = CHILD_BOTTOM_LEFT_FAR;
} else {
// bottom left near
childIndex = CHILD_BOTTOM_LEFT_NEAR;
}
}
} else {
// right half
if (point.y > ourCenter.y) {
// top right
if (point.z > ourCenter.z) {
// top right far
childIndex = CHILD_TOP_RIGHT_FAR;
} else {
// top right near
childIndex = CHILD_TOP_RIGHT_NEAR;
}
} else {
// bottom right
if (point.z > ourCenter.z) {
// bottom right far
childIndex = CHILD_BOTTOM_RIGHT_FAR;
} else {
// bottom right near
childIndex = CHILD_BOTTOM_RIGHT_NEAR;
}
}
}
return childIndex;
}

View file

@ -23,14 +23,14 @@
#include "AABox.h"
#include "ViewFrustum.h"
#include "OctreeConstants.h"
//#include "Octree.h"
class EncodeBitstreamParams;
class Octree;
class OctreeElement;
class OctreeElementDeleteHook;
class OctreePacketData;
class VoxelSystem;
class ReadBitstreamToTreeParams;
class VoxelSystem;
// Callers who want delete hook callbacks should implement this class
class OctreeElementDeleteHook {
@ -81,7 +81,7 @@ public:
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const { return true; }
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { return true; }
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
@ -217,6 +217,8 @@ public:
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
OctreeElement* getOrCreateChildElementContaining(const AABox& box);
int getMyChildContainingPoint(const glm::vec3& point) const;
protected:

View file

@ -47,7 +47,7 @@ ParticleTreeElement* ParticleTreeElement::addChildAtIndex(int index) {
}
bool ParticleTreeElement::appendElementData(OctreePacketData* packetData) const {
bool ParticleTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const {
bool success = true; // assume the best...
// write our particles out...

View file

@ -76,7 +76,7 @@ public:
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const;
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const;
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.

View file

@ -65,7 +65,7 @@ void VoxelTreeElement::splitChildren() {
}
}
bool VoxelTreeElement::appendElementData(OctreePacketData* packetData) const {
bool VoxelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const {
return packetData->appendColor(getColor());
}

View file

@ -43,7 +43,7 @@ public:
virtual bool hasContent() const { return isColored(); }
virtual void splitChildren();
virtual bool requiresSplit() const;
virtual bool appendElementData(OctreePacketData* packetData) const;
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const;
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
virtual void calculateAverageFromChildren();
virtual bool collapseChildren();