From b4e70d9101df4ded16c7c95ee2067c0ee4ba373c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 25 Mar 2016 21:16:53 -0700 Subject: [PATCH 01/29] WIP: checkpoint * bug fix in AABox::operator+= * added AABox::emiggen * Avatar now has a default bound for it's skinned mesh. * WIP: AABox tests; NEED MORE * Model: split collision and model mesh render items. Because ModelMeshRenderItems need special handling to update bounds for animated joints. * Model: dynamically update the bound for rigidly bound animated meshes * Rig: added access to geometryToRigTransform * RenderableModelEntityItem: try to update bounds for skinned mesh to be the entity dimentions (this doesn't seem to be working) * Geometry.cpp: removed unused bounds parameter in evalPartBounds * ModelMeshPartPayload: bounds updating * non-animated: use existing _localBound * rigid bound mesh: use _localBound transformed by clusterMatrix joint transform * fully skinned mesh: use _skinnedMeshBound provided by the application. --- interface/src/avatar/Avatar.cpp | 3 + libraries/animation/src/Rig.h | 2 + .../src/RenderableModelEntityItem.cpp | 4 + libraries/model/src/model/Geometry.cpp | 2 +- libraries/model/src/model/Geometry.h | 4 +- .../render-utils/src/MeshPartPayload.cpp | 64 ++++++--- libraries/render-utils/src/MeshPartPayload.h | 20 ++- libraries/render-utils/src/Model.cpp | 125 ++++++++++++++---- libraries/render-utils/src/Model.h | 15 ++- libraries/shared/src/AABox.cpp | 31 ++++- libraries/shared/src/AABox.h | 5 +- tests/shared/src/AABoxTests.cpp | 12 ++ tests/shared/src/AABoxTests.h | 1 + 13 files changed, 229 insertions(+), 59 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f722210a8e..24e786caa2 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -56,6 +56,8 @@ const float DISPLAYNAME_ALPHA = 1.0f; const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f; const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); +const AABox DEFAULT_AVATAR_SKINNED_MESH_BOUND(glm::vec3(-1.0, -1.0, -1.0), glm::vec3(2.0f)); + namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { return ItemKey::Builder::opaqueShape(); @@ -101,6 +103,7 @@ Avatar::Avatar(RigPointer rig) : _headData = static_cast(new Head(this)); _skeletonModel = std::make_shared(this, nullptr, rig); + _skeletonModel->setSkinnedMeshBound(DEFAULT_AVATAR_SKINNED_MESH_BOUND); } Avatar::~Avatar() { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 1f9a02d8ab..3a27b9304b 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -221,6 +221,8 @@ public: void setEnableInverseKinematics(bool enable); + const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; } + protected: bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); } void updateAnimationStateHandlers(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 312e1fcea5..e0d6c90758 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -54,6 +54,8 @@ void RenderableModelEntityItem::setModelURL(const QString& url) { // Here we reset those guards. This doesn't cause the entity values to change -- it just allows the model to match once it comes in. _model->setScaleToFit(false, getDimensions()); _model->setSnapModelToRegistrationPoint(false, getRegistrationPoint()); + AABox skinnedMeshBound(getPosition() - getDimensions() * getRegistrationPoint(), getDimensions()); + _model->setSkinnedMeshBound(skinnedMeshBound); } ModelEntityItem::setModelURL(url); @@ -76,6 +78,8 @@ void RenderableModelEntityItem::loader() { if (_model) { _model->setURL(getParsedModelURL()); _model->setCollisionModelURL(QUrl(getCompoundShapeURL())); + AABox skinnedMeshBound(getPosition() - getDimensions() * getRegistrationPoint(), getDimensions()); + _model->setSkinnedMeshBound(skinnedMeshBound); } } diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index df86ba2a8f..1c4072b6f3 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -111,7 +111,7 @@ Box Mesh::evalPartBound(int partNum) const { return box; } -Box Mesh::evalPartBounds(int partStart, int partEnd, Boxes& bounds) const { +Box Mesh::evalPartBounds(int partStart, int partEnd) const { Box totalBound; auto part = _partBuffer.cbegin() + partStart; auto partItEnd = _partBuffer.cbegin() + partEnd; diff --git a/libraries/model/src/model/Geometry.h b/libraries/model/src/model/Geometry.h index f9d9b0eeb4..b8873dba17 100755 --- a/libraries/model/src/model/Geometry.h +++ b/libraries/model/src/model/Geometry.h @@ -108,9 +108,9 @@ public: // evaluate the bounding box of A part Box evalPartBound(int partNum) const; - // evaluate the bounding boxes of the parts in the range [start, end[ and fill the bounds parameter + // evaluate the bounding boxes of the parts in the range [start, end] // the returned box is the bounding box of ALL the evaluated part bounds. - Box evalPartBounds(int partStart, int partEnd, Boxes& bounds) const; + Box evalPartBounds(int partStart, int partEnd) const; static gpu::Primitive topologyToPrimitive(Topology topo) { return static_cast(topo); } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 363def05a1..482d630cdb 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -52,17 +52,6 @@ MeshPartPayload::MeshPartPayload(model::MeshPointer mesh, int partIndex, model:: updateTransform(transform, offsetTransform); } -void MeshPartPayload::updateMeshPart(model::MeshPointer drawMesh, int partIndex) { - _drawMesh = drawMesh; - if (_drawMesh) { - auto vertexFormat = _drawMesh->getVertexFormat(); - _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); - _drawPart = _drawMesh->getPartBuffer().get(partIndex); - - _localBound = _drawMesh->evalPartBound(partIndex); - } -} - void MeshPartPayload::updateTransform(const Transform& transform, const Transform& offsetTransform) { _transform = transform; _offsetTransform = offsetTransform; @@ -71,6 +60,16 @@ void MeshPartPayload::updateTransform(const Transform& transform, const Transfor _worldBound.transform(_drawTransform); } +void MeshPartPayload::updateMeshPart(model::MeshPointer drawMesh, int partIndex) { + _drawMesh = drawMesh; + if (_drawMesh) { + auto vertexFormat = _drawMesh->getVertexFormat(); + _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); + _drawPart = _drawMesh->getPartBuffer().get(partIndex); + _localBound = _drawMesh->evalPartBound(partIndex); + } +} + void MeshPartPayload::updateMaterial(model::MaterialPointer drawMaterial) { _drawMaterial = drawMaterial; } @@ -316,10 +315,13 @@ template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, Ren } } -ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : +ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, + const Transform& offsetTransform, const AABox& skinnedMeshBound) : _model(model), _meshIndex(_meshIndex), - _shapeID(shapeIndex) { + _shapeID(shapeIndex), + _skinnedMeshBound(skinnedMeshBound) { + auto& modelMesh = _model->_geometry->getMeshes().at(_meshIndex)->_mesh; updateMeshPart(modelMesh, partIndex); @@ -351,6 +353,36 @@ void ModelMeshPartPayload::notifyLocationChanged() { _model->_needsUpdateClusterMatrices = true; } +void ModelMeshPartPayload::updateTransformForRigidlyBoundMesh(const Transform& transform, const Transform& clusterTransform, const Transform& offsetTransform) { + ModelMeshPartPayload::updateTransform(transform, offsetTransform); + + // clusterMatrix has world rotation but not world translation. + Transform worldTranslation, geomToWorld; + worldTranslation.setTranslation(transform.getTranslation()); + Transform::mult(geomToWorld, worldTranslation, clusterTransform); + + // transform the localBound into world space + _worldBound = _localBound; + _worldBound.transform(geomToWorld); +} + +void ModelMeshPartPayload::updateMeshPart(model::MeshPointer drawMesh, int partIndex) { + _drawMesh = drawMesh; + if (_drawMesh) { + auto vertexFormat = _drawMesh->getVertexFormat(); + _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); + _drawPart = _drawMesh->getPartBuffer().get(partIndex); + + // this is a skinned mesh.. + if (vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX)) { + // use the specified skinned bounding box. + _localBound = _skinnedMeshBound; + } else { + _localBound = _drawMesh->evalPartBound(partIndex); + } + } +} + ItemKey ModelMeshPartPayload::getKey() const { ItemKey::Builder builder; builder.withTypeShape(); @@ -373,12 +405,6 @@ ItemKey ModelMeshPartPayload::getKey() const { return builder.build(); } -Item::Bound ModelMeshPartPayload::getBound() const { - // NOTE: we can't cache this bounds because we need to handle the case of a moving - // entity or mesh part. - return _model->getPartBounds(_meshIndex, _partIndex, _transform.getTranslation(), _transform.getRotation()); -} - ShapeKey ModelMeshPartPayload::getShapeKey() const { const FBXGeometry& geometry = _model->_geometry->getFBXGeometry(); const std::vector>& networkMeshes = _model->_geometry->getMeshes(); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index feccf7ee64..0aadf9baf8 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -64,6 +64,7 @@ public: bool _hasColorAttrib = false; }; + namespace render { template <> const ItemKey payloadGetKey(const MeshPartPayload::Pointer& payload); template <> const Item::Bound payloadGetBound(const MeshPartPayload::Pointer& payload); @@ -73,16 +74,18 @@ namespace render { class ModelMeshPartPayload : public MeshPartPayload { public: - ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); - + ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, + const Transform& offsetTransform, const AABox& skinnedMeshBound); + typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - void notifyLocationChanged() override; + virtual void updateMeshPart(model::MeshPointer drawMesh, int partIndex) override; + virtual void notifyLocationChanged() override; + void updateTransformForRigidlyBoundMesh(const Transform& transform, const Transform& jointTransform, const Transform& offsetTransform); // Render Item interface render::ItemKey getKey() const override; - render::Item::Bound getBound() const override; render::ShapeKey getShapeKey() const override; // shape interface void render(RenderArgs* args) const override; @@ -99,6 +102,15 @@ public: bool _isSkinned{ false }; bool _isBlendShaped{ false }; + + AABox _skinnedMeshBound; }; +namespace render { + template <> const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload); + template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload); + template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload); + template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args); +} + #endif // hifi_MeshPartPayload_h diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 0a483914df..e47f8caf65 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -102,6 +102,7 @@ void Model::setRotation(const glm::quat& rotation) { void Model::setScale(const glm::vec3& scale) { setScaleInternal(scale); + // if anyone sets scale manually, then we are no longer scaled to fit _scaleToFit = false; _scaledToFit = false; @@ -130,22 +131,46 @@ void Model::setOffset(const glm::vec3& offset) { void Model::enqueueLocationChange() { render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - Transform transform; - transform.setTranslation(_translation); - transform.setRotation(_rotation); + Transform modelTransform; + modelTransform.setScale(_scale); + modelTransform.setTranslation(_translation); + modelTransform.setRotation(_rotation); Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); + if (_geometry && _geometry->isLoaded()) { + offset = Transform(_rig->getGeometryToRigTransform()); + } else { + offset.postTranslate(_offset); + } render::PendingChanges pendingChanges; - foreach (auto itemID, _renderItems.keys()) { - pendingChanges.updateItem(itemID, [transform, offset](MeshPartPayload& data) { - data.updateTransform(transform, offset); + foreach (auto itemID, _modelMeshRenderItems.keys()) { + pendingChanges.updateItem(itemID, [modelTransform, offset](ModelMeshPartPayload& data) { + //data._model->updateClusterMatrices(data._transform.getTranslation(), data._transform.getRotation()); + const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); + if (state.clusterBuffer) { + data.updateTransform(modelTransform, offset); + } else { + // HACK: check for bugs... + AnimPose clusterMat(state.clusterMatrices[0]); + + Transform xform; + xform.setScale(clusterMat.scale); + xform.setRotation(clusterMat.rot); + xform.setTranslation(clusterMat.trans); + data.updateTransformForRigidlyBoundMesh(modelTransform, xform, offset); + } data.notifyLocationChanged(); }); } + foreach (auto itemID, _collisionRenderItems.keys()) { + pendingChanges.updateItem(itemID, [modelTransform, offset](MeshPartPayload& data) { + data.updateTransform(modelTransform, offset); + data.notifyLocationChanged(); + }); + } + scene->enqueuePendingChanges(pendingChanges); } @@ -497,8 +522,11 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr scen _isVisible = newValue; render::PendingChanges pendingChanges; - foreach (auto item, _renderItems.keys()) { - pendingChanges.resetItem(item, _renderItems[item]); + foreach (auto item, _modelMeshRenderItems.keys()) { + pendingChanges.resetItem(item, _modelMeshRenderItems[item]); + } + foreach (auto item, _collisionRenderItems.keys()) { + pendingChanges.resetItem(item, _modelMeshRenderItems[item]); } scene->enqueuePendingChanges(pendingChanges); } @@ -514,14 +542,25 @@ bool Model::addToScene(std::shared_ptr scene, render::PendingChan bool somethingAdded = false; - foreach (auto renderItem, _renderItemsSet) { + foreach (auto renderItem, _modelMeshRenderItemsSet) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [](ModelMeshPartPayload& data) { + data.notifyLocationChanged(); + }); + _modelMeshRenderItems.insert(item, renderPayload); + somethingAdded = true; + } + + foreach (auto renderItem, _collisionRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared(renderItem); pendingChanges.resetItem(item, renderPayload); pendingChanges.updateItem(item, [](MeshPartPayload& data) { data.notifyLocationChanged(); }); - _renderItems.insert(item, renderPayload); + _collisionRenderItems.insert(item, renderPayload); somethingAdded = true; } @@ -541,7 +580,19 @@ bool Model::addToScene(std::shared_ptr scene, bool somethingAdded = false; - foreach (auto renderItem, _renderItemsSet) { + foreach (auto renderItem, _modelMeshRenderItemsSet) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + renderPayload->addStatusGetters(statusGetters); + pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [](ModelMeshPartPayload& data) { + data.notifyLocationChanged(); + }); + _modelMeshRenderItems.insert(item, renderPayload); + somethingAdded = true; + } + + foreach (auto renderItem, _collisionRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared(renderItem); renderPayload->addStatusGetters(statusGetters); @@ -549,7 +600,7 @@ bool Model::addToScene(std::shared_ptr scene, pendingChanges.updateItem(item, [](MeshPartPayload& data) { data.notifyLocationChanged(); }); - _renderItems.insert(item, renderPayload); + _collisionRenderItems.insert(item, renderPayload); somethingAdded = true; } @@ -559,11 +610,16 @@ bool Model::addToScene(std::shared_ptr scene, } void Model::removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges) { - foreach (auto item, _renderItems.keys()) { + foreach (auto item, _modelMeshRenderItems.keys()) { pendingChanges.removeItem(item); } - _renderItems.clear(); - _renderItemsSet.clear(); + _modelMeshRenderItems.clear(); + _modelMeshRenderItemsSet.clear(); + foreach (auto item, _collisionRenderItems.keys()) { + pendingChanges.removeItem(item); + } + _collisionRenderItems.clear(); + _collisionRenderItemsSet.clear(); _meshGroupsKnown = false; _readyWhenAdded = false; } @@ -1191,10 +1247,14 @@ void Model::segregateMeshGroups() { return; } - Q_ASSERT(_renderItems.isEmpty()); // We should not have any existing renderItems if we enter this section of code - Q_ASSERT(_renderItemsSet.isEmpty()); // We should not have any existing renderItemsSet if we enter this section of code + // We should not have any existing renderItems if we enter this section of code + Q_ASSERT(_modelMeshRenderItems.isEmpty()); + Q_ASSERT(_modelMeshRenderItemsSet.isEmpty()); + Q_ASSERT(_collisionRenderItems.isEmpty()); + Q_ASSERT(_collisionRenderItemsSet.isEmpty()); - _renderItemsSet.clear(); + _modelMeshRenderItemsSet.clear(); + _collisionRenderItemsSet.clear(); Transform transform; transform.setTranslation(_translation); @@ -1220,9 +1280,14 @@ void Model::segregateMeshGroups() { _collisionHullMaterial->setMetallic(0.02f); _collisionHullMaterial->setRoughness(0.5f); } - _renderItemsSet << std::make_shared(networkMesh._mesh, partIndex, _collisionHullMaterial, transform, offset); + _collisionRenderItemsSet << std::make_shared(networkMesh._mesh, partIndex, _collisionHullMaterial, transform, offset); } else { - _renderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); + AABox geometrySkinnedMeshBound = _skinnedMeshBound; + + // transform bound from model into geometry space. + geometrySkinnedMeshBound.transform(Transform(glm::inverse(_rig->getGeometryToRigTransform()))); + + _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset, geometrySkinnedMeshBound); } shapeID++; @@ -1245,13 +1310,21 @@ bool Model::initWhenReady(render::ScenePointer scene) { offset.setScale(_scale); offset.postTranslate(_offset); - foreach (auto renderItem, _renderItemsSet) { + foreach (auto renderItem, _modelMeshRenderItemsSet) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + _modelMeshRenderItems.insert(item, renderPayload); + pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [transform, offset](MeshPartPayload& data) { + data.notifyLocationChanged(); + }); + } + foreach (auto renderItem, _collisionRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared(renderItem); - _renderItems.insert(item, renderPayload); + _collisionRenderItems.insert(item, renderPayload); pendingChanges.resetItem(item, renderPayload); - pendingChanges.updateItem(item, [transform,offset](MeshPartPayload& data) { - data.updateTransform(transform, offset); + pendingChanges.updateItem(item, [transform, offset](MeshPartPayload& data) { data.notifyLocationChanged(); }); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 4e51dc4f33..019795f116 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -44,6 +44,7 @@ namespace render { typedef unsigned int ItemID; } class MeshPartPayload; +class ModelMeshPartPayload; class ModelRenderLocations; inline uint qHash(const std::shared_ptr& a, uint seed) { @@ -222,6 +223,9 @@ public: const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } + // bounding box used for mesh that is influnced by multiple animated bones. + void setSkinnedMeshBound(const AABox& skinnedMeshBound) { _skinnedMeshBound = skinnedMeshBound; } + protected: void setPupilDilation(float dilation) { _pupilDilation = dilation; } @@ -371,8 +375,12 @@ protected: bool _renderCollisionHull; - QSet> _renderItemsSet; - QMap _renderItems; + QSet> _collisionRenderItemsSet; + QMap _collisionRenderItems; + + QSet> _modelMeshRenderItemsSet; + QMap _modelMeshRenderItems; + bool _readyWhenAdded { false }; bool _needsReload { true }; bool _needsUpdateClusterMatrices { true }; @@ -382,6 +390,9 @@ protected: friend class ModelMeshPartPayload; RigPointer _rig; + + // 2 meter^3 box + AABox _skinnedMeshBound { glm::vec3(-1.0, -1.0, -1.0), glm::vec3(2.0f) }; }; Q_DECLARE_METATYPE(ModelPointer) diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 0b453e6f36..786ef40257 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -17,6 +17,8 @@ #include "GeometryUtil.h" #include "NumericalConstants.h" +const glm::vec3 INFINITY_VECTOR(std::numeric_limits::infinity()); + AABox::AABox(const AACube& other) : _corner(other.getCorner()), _scale(other.getScale(), other.getScale(), other.getScale()) { } @@ -34,7 +36,7 @@ AABox::AABox(const glm::vec3& corner, const glm::vec3& dimensions) : _corner(corner), _scale(dimensions) { }; -AABox::AABox() : _corner(std::numeric_limits::infinity()), _scale(0.0f) { +AABox::AABox() : _corner(INFINITY_VECTOR), _scale(0.0f) { }; glm::vec3 AABox::calcCenter() const { @@ -475,8 +477,15 @@ AABox AABox::clamp(float min, float max) const { } AABox& AABox::operator += (const glm::vec3& point) { - _corner = glm::min(_corner, point); - _scale = glm::max(_scale, point - _corner); + + if (_corner == INFINITY_VECTOR) { + _corner = glm::min(_corner, point); + } else { + glm::vec3 maximum(_corner + _scale); + _corner = glm::min(_corner, point); + maximum = glm::max(maximum, point); + _scale = maximum - _corner; + } return (*this); } @@ -489,11 +498,25 @@ AABox& AABox::operator += (const AABox& box) { return (*this); } -void AABox::scale(const glm::vec3& scale) { +void AABox::embiggen(float scale) { + _corner += scale * (-0.5f * _scale); + _scale *= scale; +} + +void AABox::embiggen(const glm::vec3& scale) { + _corner += scale * (-0.5f * _scale); + _scale *= scale; +} + +void AABox::scale(float scale) { _corner *= scale; _scale *= scale; } +void AABox::scale(const glm::vec3& scale) { + _corner *= scale; + _scale *= scale; +} void AABox::rotate(const glm::quat& rotation) { auto minimum = _corner; diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index ec06c60121..000d4be734 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -58,7 +58,6 @@ public: const glm::vec3& getMinimumPoint() const { return _corner; } glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); } - bool contains(const glm::vec3& point) const; bool contains(const AABox& otherBox) const; bool touches(const AABox& otherBox) const; @@ -93,6 +92,10 @@ public: void scale(float scale); void scale(const glm::vec3& scale); + /// make the AABox bigger (scale about it's center) + void embiggen(float scale); + void embiggen(const glm::vec3& scale); + // Transform the extents with transform void transform(const Transform& transform); diff --git a/tests/shared/src/AABoxTests.cpp b/tests/shared/src/AABoxTests.cpp index fd709a488c..41f05616c4 100644 --- a/tests/shared/src/AABoxTests.cpp +++ b/tests/shared/src/AABoxTests.cpp @@ -151,3 +151,15 @@ void AABoxTests::testTouchesSphere() { } } +void AABoxTests::testScale() { + AABox box(glm::vec3(2.0f), glm::vec3(1.0f)); + QCOMPARE(box.contains(glm::vec3(0.0f)), false); + box.scale(glm::vec3(10.0f)); + QCOMPARE(box.contains(glm::vec3(0.0f)), false); + QCOMPARE(box.contains(glm::vec3(2.0f * 10.0f)), true); + + AABox box(glm::vec3(2.0f), glm::vec3(1.0f)); + QCOMPARE(box.contains(glm::vec3(0.0f)), false); + box.embiggen(glm::vec3(10.0f)); + QCOMPARE(box.contains(glm::vec3(0.0f)), false); +} diff --git a/tests/shared/src/AABoxTests.h b/tests/shared/src/AABoxTests.h index fe7afede57..c777f8e94f 100644 --- a/tests/shared/src/AABoxTests.h +++ b/tests/shared/src/AABoxTests.h @@ -23,6 +23,7 @@ private slots: void testCtorsAndSetters(); void testContainsPoint(); void testTouchesSphere(); + void testScale(); }; #endif // hifi_AABoxTests_h From bf433487fadac3b94187ac54ff9432d8ef689a49 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sat, 26 Mar 2016 12:09:36 -0700 Subject: [PATCH 02/29] Dynamic bound update for skinned mesh * Use all cluster matrices to compute bound for skinned mesh. This is far less expensive then doing per-vertex work, but it's not free, for avatars especially. * Remove skinnedMeshBound, compute it instead. * Compute clusterMatrices in render update, because we need them to update bounds. --- interface/src/avatar/Avatar.cpp | 1 - .../src/RenderableModelEntityItem.cpp | 4 -- .../render-utils/src/MeshPartPayload.cpp | 38 +++++--------- libraries/render-utils/src/MeshPartPayload.h | 7 +-- libraries/render-utils/src/Model.cpp | 25 +++------- libraries/render-utils/src/Model.h | 6 --- libraries/shared/src/AABox.cpp | 50 +++++++++++++++++-- libraries/shared/src/AABox.h | 7 ++- 8 files changed, 73 insertions(+), 65 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 24e786caa2..07227d2f68 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -103,7 +103,6 @@ Avatar::Avatar(RigPointer rig) : _headData = static_cast(new Head(this)); _skeletonModel = std::make_shared(this, nullptr, rig); - _skeletonModel->setSkinnedMeshBound(DEFAULT_AVATAR_SKINNED_MESH_BOUND); } Avatar::~Avatar() { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e0d6c90758..312e1fcea5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -54,8 +54,6 @@ void RenderableModelEntityItem::setModelURL(const QString& url) { // Here we reset those guards. This doesn't cause the entity values to change -- it just allows the model to match once it comes in. _model->setScaleToFit(false, getDimensions()); _model->setSnapModelToRegistrationPoint(false, getRegistrationPoint()); - AABox skinnedMeshBound(getPosition() - getDimensions() * getRegistrationPoint(), getDimensions()); - _model->setSkinnedMeshBound(skinnedMeshBound); } ModelEntityItem::setModelURL(url); @@ -78,8 +76,6 @@ void RenderableModelEntityItem::loader() { if (_model) { _model->setURL(getParsedModelURL()); _model->setCollisionModelURL(QUrl(getCompoundShapeURL())); - AABox skinnedMeshBound(getPosition() - getDimensions() * getRegistrationPoint(), getDimensions()); - _model->setSkinnedMeshBound(skinnedMeshBound); } } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 482d630cdb..bfa6c48857 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -316,11 +316,10 @@ template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, Ren } ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, - const Transform& offsetTransform, const AABox& skinnedMeshBound) : + const Transform& offsetTransform) : _model(model), _meshIndex(_meshIndex), - _shapeID(shapeIndex), - _skinnedMeshBound(skinnedMeshBound) { + _shapeID(shapeIndex) { auto& modelMesh = _model->_geometry->getMeshes().at(_meshIndex)->_mesh; updateMeshPart(modelMesh, partIndex); @@ -353,33 +352,20 @@ void ModelMeshPartPayload::notifyLocationChanged() { _model->_needsUpdateClusterMatrices = true; } -void ModelMeshPartPayload::updateTransformForRigidlyBoundMesh(const Transform& transform, const Transform& clusterTransform, const Transform& offsetTransform) { +void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const glm::mat4* clusterMatrices, size_t numClusterMatrices) { ModelMeshPartPayload::updateTransform(transform, offsetTransform); - // clusterMatrix has world rotation but not world translation. - Transform worldTranslation, geomToWorld; - worldTranslation.setTranslation(transform.getTranslation()); - Transform::mult(geomToWorld, worldTranslation, clusterTransform); + if (numClusterMatrices > 0) { - // transform the localBound into world space - _worldBound = _localBound; - _worldBound.transform(geomToWorld); -} - -void ModelMeshPartPayload::updateMeshPart(model::MeshPointer drawMesh, int partIndex) { - _drawMesh = drawMesh; - if (_drawMesh) { - auto vertexFormat = _drawMesh->getVertexFormat(); - _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); - _drawPart = _drawMesh->getPartBuffer().get(partIndex); - - // this is a skinned mesh.. - if (vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX)) { - // use the specified skinned bounding box. - _localBound = _skinnedMeshBound; - } else { - _localBound = _drawMesh->evalPartBound(partIndex); + _worldBound = AABox(); + for (size_t i = 0; i < numClusterMatrices; i++) { + AABox clusterBound = _localBound; + clusterBound.transform(clusterMatrices[i]); + _worldBound += clusterBound; } + + // clusterMatrix has world rotation but not world translation. + _worldBound.translate(transform.getTranslation()); } } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 0aadf9baf8..7b059afadd 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -75,14 +75,13 @@ namespace render { class ModelMeshPartPayload : public MeshPartPayload { public: ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, - const Transform& offsetTransform, const AABox& skinnedMeshBound); + const Transform& offsetTransform); typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - virtual void updateMeshPart(model::MeshPointer drawMesh, int partIndex) override; virtual void notifyLocationChanged() override; - void updateTransformForRigidlyBoundMesh(const Transform& transform, const Transform& jointTransform, const Transform& offsetTransform); + void updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const glm::mat4* clusterMatrices, size_t numClusterMatrices); // Render Item interface render::ItemKey getKey() const override; @@ -102,8 +101,6 @@ public: bool _isSkinned{ false }; bool _isBlendShaped{ false }; - - AABox _skinnedMeshBound; }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index e47f8caf65..b463a80403 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -146,20 +146,12 @@ void Model::enqueueLocationChange() { render::PendingChanges pendingChanges; foreach (auto itemID, _modelMeshRenderItems.keys()) { pendingChanges.updateItem(itemID, [modelTransform, offset](ModelMeshPartPayload& data) { - //data._model->updateClusterMatrices(data._transform.getTranslation(), data._transform.getRotation()); - const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); - if (state.clusterBuffer) { - data.updateTransform(modelTransform, offset); - } else { - // HACK: check for bugs... - AnimPose clusterMat(state.clusterMatrices[0]); - Transform xform; - xform.setScale(clusterMat.scale); - xform.setRotation(clusterMat.rot); - xform.setTranslation(clusterMat.trans); - data.updateTransformForRigidlyBoundMesh(modelTransform, xform, offset); - } + data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation()); + const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); + size_t numClusterMatrices = data._model->getGeometry()->getFBXGeometry().meshes.at(data._meshIndex).clusters.size(); + + data.updateTransformForSkinnedMesh(modelTransform, offset, &state.clusterMatrices[0], numClusterMatrices); data.notifyLocationChanged(); }); } @@ -1282,12 +1274,7 @@ void Model::segregateMeshGroups() { } _collisionRenderItemsSet << std::make_shared(networkMesh._mesh, partIndex, _collisionHullMaterial, transform, offset); } else { - AABox geometrySkinnedMeshBound = _skinnedMeshBound; - - // transform bound from model into geometry space. - geometrySkinnedMeshBound.transform(Transform(glm::inverse(_rig->getGeometryToRigTransform()))); - - _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset, geometrySkinnedMeshBound); + _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); } shapeID++; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 019795f116..d9eb20cb32 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -223,9 +223,6 @@ public: const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } - // bounding box used for mesh that is influnced by multiple animated bones. - void setSkinnedMeshBound(const AABox& skinnedMeshBound) { _skinnedMeshBound = skinnedMeshBound; } - protected: void setPupilDilation(float dilation) { _pupilDilation = dilation; } @@ -390,9 +387,6 @@ protected: friend class ModelMeshPartPayload; RigPointer _rig; - - // 2 meter^3 box - AABox _skinnedMeshBound { glm::vec3(-1.0, -1.0, -1.0), glm::vec3(2.0f) }; }; Q_DECLARE_METATYPE(ModelPointer) diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 786ef40257..a3afa220c9 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -17,7 +17,7 @@ #include "GeometryUtil.h" #include "NumericalConstants.h" -const glm::vec3 INFINITY_VECTOR(std::numeric_limits::infinity()); +const glm::vec3 AABox::INFINITY_VECTOR(std::numeric_limits::infinity()); AABox::AABox(const AACube& other) : _corner(other.getCorner()), _scale(other.getScale(), other.getScale(), other.getScale()) { @@ -478,7 +478,7 @@ AABox AABox::clamp(float min, float max) const { AABox& AABox::operator += (const glm::vec3& point) { - if (_corner == INFINITY_VECTOR) { + if (isInvalid()) { _corner = glm::min(_corner, point); } else { glm::vec3 maximum(_corner + _scale); @@ -493,7 +493,7 @@ AABox& AABox::operator += (const glm::vec3& point) { AABox& AABox::operator += (const AABox& box) { if (!box.isInvalid()) { (*this) += box._corner; - _scale = glm::max(_scale, box.calcTopFarLeft() - _corner); + (*this) += box.calcTopFarLeft(); } return (*this); } @@ -567,3 +567,47 @@ void AABox::transform(const Transform& transform) { rotate(transform.getRotation()); translate(transform.getTranslation()); } + +void AABox::transform(const glm::mat4& matrix) { + auto minimum = _corner; + auto maximum = _corner + _scale; + + glm::vec3 bottomLeftNear(minimum.x, minimum.y, minimum.z); + glm::vec3 bottomRightNear(maximum.x, minimum.y, minimum.z); + glm::vec3 bottomLeftFar(minimum.x, minimum.y, maximum.z); + glm::vec3 bottomRightFar(maximum.x, minimum.y, maximum.z); + glm::vec3 topLeftNear(minimum.x, maximum.y, minimum.z); + glm::vec3 topRightNear(maximum.x, maximum.y, minimum.z); + glm::vec3 topLeftFar(minimum.x, maximum.y, maximum.z); + glm::vec3 topRightFar(maximum.x, maximum.y, maximum.z); + + glm::vec3 bottomLeftNearTransformed = transformPoint(matrix, bottomLeftNear); + glm::vec3 bottomRightNearTransformed = transformPoint(matrix, bottomRightNear); + glm::vec3 bottomLeftFarTransformed = transformPoint(matrix, bottomLeftFar); + glm::vec3 bottomRightFarTransformed = transformPoint(matrix, bottomRightFar); + glm::vec3 topLeftNearTransformed = transformPoint(matrix, topLeftNear); + glm::vec3 topRightNearTransformed = transformPoint(matrix, topRightNear); + glm::vec3 topLeftFarTransformed = transformPoint(matrix, topLeftFar); + glm::vec3 topRightFarTransformed = transformPoint(matrix, topRightFar); + + minimum = glm::min(bottomLeftNearTransformed, + glm::min(bottomRightNearTransformed, + glm::min(bottomLeftFarTransformed, + glm::min(bottomRightFarTransformed, + glm::min(topLeftNearTransformed, + glm::min(topRightNearTransformed, + glm::min(topLeftFarTransformed, + topRightFarTransformed))))))); + + maximum = glm::max(bottomLeftNearTransformed, + glm::max(bottomRightNearTransformed, + glm::max(bottomLeftFarTransformed, + glm::max(bottomRightFarTransformed, + glm::max(topLeftNearTransformed, + glm::max(topRightNearTransformed, + glm::max(topLeftFarTransformed, + topRightFarTransformed))))))); + + _corner = minimum; + _scale = maximum - minimum; +} diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index 000d4be734..30bde2d717 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -99,7 +99,12 @@ public: // Transform the extents with transform void transform(const Transform& transform); - bool isInvalid() const { return _corner == glm::vec3(std::numeric_limits::infinity()); } + // Transform the extents with matrix + void transform(const glm::mat4& matrix); + + static const glm::vec3 INFINITY_VECTOR; + + bool isInvalid() const { return _corner == INFINITY_VECTOR; } private: glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const; From 49a4d104a69a316f9ed2cbc5954e61e4c2612f8d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sat, 26 Mar 2016 13:27:05 -0700 Subject: [PATCH 03/29] Minimize diff with master --- interface/src/avatar/Avatar.cpp | 2 -- .../render-utils/src/MeshPartPayload.cpp | 20 +++++++++---------- libraries/render-utils/src/MeshPartPayload.h | 6 ++---- libraries/render-utils/src/Model.cpp | 1 - 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 07227d2f68..f722210a8e 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -56,8 +56,6 @@ const float DISPLAYNAME_ALPHA = 1.0f; const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f; const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); -const AABox DEFAULT_AVATAR_SKINNED_MESH_BOUND(glm::vec3(-1.0, -1.0, -1.0), glm::vec3(2.0f)); - namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { return ItemKey::Builder::opaqueShape(); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index bfa6c48857..8d738e44d8 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -52,14 +52,6 @@ MeshPartPayload::MeshPartPayload(model::MeshPointer mesh, int partIndex, model:: updateTransform(transform, offsetTransform); } -void MeshPartPayload::updateTransform(const Transform& transform, const Transform& offsetTransform) { - _transform = transform; - _offsetTransform = offsetTransform; - Transform::mult(_drawTransform, _transform, _offsetTransform); - _worldBound = _localBound; - _worldBound.transform(_drawTransform); -} - void MeshPartPayload::updateMeshPart(model::MeshPointer drawMesh, int partIndex) { _drawMesh = drawMesh; if (_drawMesh) { @@ -70,6 +62,14 @@ void MeshPartPayload::updateMeshPart(model::MeshPointer drawMesh, int partIndex) } } +void MeshPartPayload::updateTransform(const Transform& transform, const Transform& offsetTransform) { + _transform = transform; + _offsetTransform = offsetTransform; + Transform::mult(_drawTransform, _transform, _offsetTransform); + _worldBound = _localBound; + _worldBound.transform(_drawTransform); +} + void MeshPartPayload::updateMaterial(model::MaterialPointer drawMaterial) { _drawMaterial = drawMaterial; } @@ -315,12 +315,10 @@ template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, Ren } } -ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, - const Transform& offsetTransform) : +ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : _model(model), _meshIndex(_meshIndex), _shapeID(shapeIndex) { - auto& modelMesh = _model->_geometry->getMeshes().at(_meshIndex)->_mesh; updateMeshPart(modelMesh, partIndex); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 7b059afadd..546c0938fa 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -64,7 +64,6 @@ public: bool _hasColorAttrib = false; }; - namespace render { template <> const ItemKey payloadGetKey(const MeshPartPayload::Pointer& payload); template <> const Item::Bound payloadGetBound(const MeshPartPayload::Pointer& payload); @@ -74,13 +73,12 @@ namespace render { class ModelMeshPartPayload : public MeshPartPayload { public: - ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, - const Transform& offsetTransform); + ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - virtual void notifyLocationChanged() override; + void notifyLocationChanged() override; void updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const glm::mat4* clusterMatrices, size_t numClusterMatrices); // Render Item interface diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b463a80403..38b7aa2e89 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -102,7 +102,6 @@ void Model::setRotation(const glm::quat& rotation) { void Model::setScale(const glm::vec3& scale) { setScaleInternal(scale); - // if anyone sets scale manually, then we are no longer scaled to fit _scaleToFit = false; _scaledToFit = false; From c671fe796694dc216a88dc71e222ff2534021444 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sat, 26 Mar 2016 13:36:56 -0700 Subject: [PATCH 04/29] Updated AABox tests --- tests/shared/src/AABoxTests.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/shared/src/AABoxTests.cpp b/tests/shared/src/AABoxTests.cpp index 41f05616c4..b9ab95bb09 100644 --- a/tests/shared/src/AABoxTests.cpp +++ b/tests/shared/src/AABoxTests.cpp @@ -152,14 +152,20 @@ void AABoxTests::testTouchesSphere() { } void AABoxTests::testScale() { - AABox box(glm::vec3(2.0f), glm::vec3(1.0f)); - QCOMPARE(box.contains(glm::vec3(0.0f)), false); - box.scale(glm::vec3(10.0f)); - QCOMPARE(box.contains(glm::vec3(0.0f)), false); - QCOMPARE(box.contains(glm::vec3(2.0f * 10.0f)), true); + AABox box1(glm::vec3(2.0f), glm::vec3(1.0f)); + QCOMPARE(box1.contains(glm::vec3(0.0f)), false); + box1.scale(glm::vec3(10.0f)); + QCOMPARE(box1.contains(glm::vec3(0.0f)), false); + QCOMPARE(box1.contains(glm::vec3(2.0f * 10.0f)), true); - AABox box(glm::vec3(2.0f), glm::vec3(1.0f)); - QCOMPARE(box.contains(glm::vec3(0.0f)), false); - box.embiggen(glm::vec3(10.0f)); - QCOMPARE(box.contains(glm::vec3(0.0f)), false); + AABox box2(glm::vec3(2.0f), glm::vec3(1.0f)); + QCOMPARE(box2.contains(glm::vec3(0.0f)), false); + box2.embiggen(glm::vec3(10.0f)); + QCOMPARE(box2.contains(glm::vec3(0.0f)), true); + + AABox box3; + box3 += glm::vec3(0.0f, 0.0f, 0.0f); + box3 += glm::vec3(1.0f, 1.0f, 1.0f); + box3 += glm::vec3(-1.0f, -1.0f, -1.0f); + QCOMPARE(box3.contains(glm::vec3(0.5f, 0.5f, 0.5f)), true); } From 5ac0640cbec29b4a4979360ddfe6e7e3c416de96 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Sun, 27 Mar 2016 16:33:53 -0700 Subject: [PATCH 05/29] check for time machine --- libraries/entities/src/ParticleEffectEntityItem.cpp | 6 ++++++ libraries/script-engine/src/ScriptEngine.cpp | 11 +++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 4b798cbcd9..02a86ab952 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -561,6 +561,12 @@ bool ParticleEffectEntityItem::needsToCallUpdate() const { void ParticleEffectEntityItem::update(const quint64& now) { + // we check for 'now' in the past in case users set their clock backward + if (now < _lastSimulated) { + _lastSimulated = now; + return; + } + float deltaTime = (float)(now - _lastSimulated) / (float)USECS_PER_SECOND; _lastSimulated = now; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 3f403b3677..a6541dc031 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -673,11 +673,14 @@ void ScriptEngine::run() { } qint64 now = usecTimestampNow(); - float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; - if (!_isFinished) { - if (_wantSignals) { - emit update(deltaTime); + // we check for 'now' in the past in case people set their clock back + if (lastUpdate < now) { + float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; + if (!_isFinished) { + if (_wantSignals) { + emit update(deltaTime); + } } } lastUpdate = now; From f5a86666a14a93667be7f21ed64c982240c341d5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 28 Mar 2016 09:56:15 -0700 Subject: [PATCH 06/29] Model: fix for collision mesh rendering --- libraries/render-utils/src/Model.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 38b7aa2e89..55aa000ef4 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -135,29 +135,33 @@ void Model::enqueueLocationChange() { modelTransform.setTranslation(_translation); modelTransform.setRotation(_rotation); - Transform offset; + Transform modelMeshOffset; if (_geometry && _geometry->isLoaded()) { - offset = Transform(_rig->getGeometryToRigTransform()); + modelMeshOffset = Transform(_rig->getGeometryToRigTransform()); } else { - offset.postTranslate(_offset); + modelMeshOffset.postTranslate(_offset); } + Transform collisionMeshOffset; + collisionMeshOffset.postTranslate(_offset); + + render::PendingChanges pendingChanges; foreach (auto itemID, _modelMeshRenderItems.keys()) { - pendingChanges.updateItem(itemID, [modelTransform, offset](ModelMeshPartPayload& data) { + pendingChanges.updateItem(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) { data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation()); const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); size_t numClusterMatrices = data._model->getGeometry()->getFBXGeometry().meshes.at(data._meshIndex).clusters.size(); - data.updateTransformForSkinnedMesh(modelTransform, offset, &state.clusterMatrices[0], numClusterMatrices); + data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, &state.clusterMatrices[0], numClusterMatrices); data.notifyLocationChanged(); }); } foreach (auto itemID, _collisionRenderItems.keys()) { - pendingChanges.updateItem(itemID, [modelTransform, offset](MeshPartPayload& data) { - data.updateTransform(modelTransform, offset); + pendingChanges.updateItem(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) { + data.updateTransform(modelTransform, collisionMeshOffset); data.notifyLocationChanged(); }); } From e77cf544830713c26027275b8890deb2124b28fc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 28 Mar 2016 13:29:28 -0700 Subject: [PATCH 07/29] Geometry.cpp: rename method --- libraries/model/src/model/Geometry.cpp | 2 +- libraries/model/src/model/Geometry.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index 1c4072b6f3..2bb6cfa436 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -111,7 +111,7 @@ Box Mesh::evalPartBound(int partNum) const { return box; } -Box Mesh::evalPartBounds(int partStart, int partEnd) const { +Box Mesh::evalPartsBound(int partStart, int partEnd) const { Box totalBound; auto part = _partBuffer.cbegin() + partStart; auto partItEnd = _partBuffer.cbegin() + partEnd; diff --git a/libraries/model/src/model/Geometry.h b/libraries/model/src/model/Geometry.h index b8873dba17..a3e95c68c0 100755 --- a/libraries/model/src/model/Geometry.h +++ b/libraries/model/src/model/Geometry.h @@ -109,8 +109,8 @@ public: // evaluate the bounding box of A part Box evalPartBound(int partNum) const; // evaluate the bounding boxes of the parts in the range [start, end] - // the returned box is the bounding box of ALL the evaluated part bounds. - Box evalPartBounds(int partStart, int partEnd) const; + // the returned box is the bounding box of ALL the evaluated parts bound. + Box evalPartsBound(int partStart, int partEnd) const; static gpu::Primitive topologyToPrimitive(Topology topo) { return static_cast(topo); } From c6347eb92a5950f4b8663993ef69cd92b62d4e91 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 28 Mar 2016 13:39:43 -0700 Subject: [PATCH 08/29] checkpoint for debugging/comparing edit.js --- .../src/entities/AssignmentParentFinder.cpp | 8 ++- .../src/entities/AssignmentParentFinder.h | 2 +- interface/src/Application.cpp | 62 +++++++++++++++---- interface/src/InterfaceParentFinder.cpp | 12 ++-- interface/src/InterfaceParentFinder.h | 2 +- libraries/entities/src/EntityTree.cpp | 11 +++- libraries/entities/src/EntityTree.h | 4 +- libraries/shared/src/SpatialParentFinder.h | 6 +- libraries/shared/src/SpatiallyNestable.cpp | 4 +- libraries/shared/src/SpatiallyNestable.h | 4 +- 10 files changed, 86 insertions(+), 29 deletions(-) diff --git a/assignment-client/src/entities/AssignmentParentFinder.cpp b/assignment-client/src/entities/AssignmentParentFinder.cpp index 294556383e..a0232daff4 100644 --- a/assignment-client/src/entities/AssignmentParentFinder.cpp +++ b/assignment-client/src/entities/AssignmentParentFinder.cpp @@ -11,7 +11,7 @@ #include "AssignmentParentFinder.h" -SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success) const { +SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const { SpatiallyNestableWeakPointer parent; if (parentID.isNull()) { @@ -20,7 +20,11 @@ SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& } // search entities - parent = _tree->findEntityByEntityItemID(parentID); + if (entityTree) { + parent = entityTree->findByID(parentID); + } else { + parent = _tree->findEntityByEntityItemID(parentID); + } if (parent.expired()) { success = false; } else { diff --git a/assignment-client/src/entities/AssignmentParentFinder.h b/assignment-client/src/entities/AssignmentParentFinder.h index 9a776bc7dd..0c16143143 100644 --- a/assignment-client/src/entities/AssignmentParentFinder.h +++ b/assignment-client/src/entities/AssignmentParentFinder.h @@ -25,7 +25,7 @@ class AssignmentParentFinder : public SpatialParentFinder { public: AssignmentParentFinder(EntityTreePointer tree) : _tree(tree) { } virtual ~AssignmentParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const; protected: EntityTreePointer _tree; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9bef698167..df08afcf64 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -270,7 +270,7 @@ public: void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); - +/* fixme uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us uint64_t now = usecTimestampNow(); auto lastHeartbeatAge = (now > lastHeartbeat) ? now - lastHeartbeat : 0; @@ -319,6 +319,7 @@ public: deadlockDetectionCrash(); } #endif + */ } } @@ -2790,43 +2791,78 @@ void Application::calibrateEyeTracker5Points() { } #endif +class EntityDatum { // For parent-first sorting and mapping. +public: + EntityItemPointer item; + EntityItemProperties properties; + EntityItemID originalParentID; + EntityItemID mappedID; + EntityDatum() {}; + EntityDatum(EntityItemPointer itemArg, EntityItemProperties propertiesArg, EntityItemID parentID) : + item(itemArg), properties(propertiesArg), originalParentID(parentID) { + }; +}; bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { - QVector entities; + QHash entities; auto entityTree = getEntities()->getTree(); auto exportTree = std::make_shared(); exportTree->createRootElement(); glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE); - for (auto entityID : entityIDs) { + for (auto entityID : entityIDs) { // Gather entities and properties. auto entityItem = entityTree->findEntityByEntityItemID(entityID); if (!entityItem) { + qCDebug(interfaceapp) << "Skipping export of" << entityID << "that is not in scene."; continue; } auto properties = entityItem->getProperties(); - auto position = properties.getPosition(); - + auto position = properties.getPosition(); // see setPosition, below. root.x = glm::min(root.x, position.x); root.y = glm::min(root.y, position.y); root.z = glm::min(root.z, position.z); - entities << entityItem; + qCDebug(interfaceapp) << "Exporting" << entityItem->getEntityItemID() << entityItem->getName(); + entities[entityID] = EntityDatum(entityItem, properties, properties.getParentID()); } if (entities.size() == 0) { return false; } - for (auto entityItem : entities) { - auto properties = entityItem->getProperties(); + for (EntityDatum& entityDatum : entities) { + // Recursively add the parents of entities to the exportTree, mapping their new identifiers as we go. + std::function getMapped = [&](EntityDatum& datum) { + auto originalID = datum.item->getEntityItemID(); + if (!datum.mappedID.isInvalidID()) { + qCDebug(interfaceapp) << "already mapped" << datum.properties.getName() << originalID << "=>" << datum.mappedID; + return datum.mappedID; // We are a parent that has already been added/mapped. + } + auto properties = datum.properties; + auto parentID = datum.originalParentID; + if (!datum.originalParentID.isInvalidID()) { // Recurse over ancestors, updating properties. + qCDebug(interfaceapp) << "FIXME recursing" << datum.originalParentID << "parent of" << datum.item->getEntityItemID(); + // Warning: this is not a tail-call, so exporting a REALLY deep parent hierarchy will blow the call stack. + parentID = getMapped(entities[parentID]); + properties.setParentID(parentID); + } + // The so-called root offset (which isn't) is confusing and not what content developers want. And why would queryAACube not then be offset? + // But leaving it in for bug-compatibility right now. -HRS + properties.setPosition(properties.getPosition() - root); + datum.mappedID = originalID; //EntityItemID(QUuid::createUuid()); + auto newEntity = exportTree->addEntity(datum.mappedID, properties); + qCDebug(interfaceapp) << "mapped" << properties.getName(); + qCDebug(interfaceapp) << " " << originalID << "p:" << datum.originalParentID; + qCDebug(interfaceapp) << " =>" << datum.mappedID << "p:" << parentID; - properties.setPosition(properties.getPosition() - root); - exportTree->addEntity(entityItem->getEntityItemID(), properties); + return datum.mappedID; + }; + + getMapped(entityDatum); } - // remap IDs on export so that we aren't publishing the IDs of entities in our domain - exportTree->remapIDs(); + //exportTree->remapIDs(); exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); @@ -2902,7 +2938,7 @@ bool Application::importEntities(const QString& urlOrFilename) { bool success = _entityClipboard->readFromURL(url.toString()); if (success) { - _entityClipboard->remapIDs(); + // FIXME _entityClipboard->remapIDs(); _entityClipboard->reaverageOctreeElements(); } return success; diff --git a/interface/src/InterfaceParentFinder.cpp b/interface/src/InterfaceParentFinder.cpp index de4b0ce38c..b1db63debd 100644 --- a/interface/src/InterfaceParentFinder.cpp +++ b/interface/src/InterfaceParentFinder.cpp @@ -16,7 +16,7 @@ #include "InterfaceParentFinder.h" -SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& success) const { +SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const { SpatiallyNestableWeakPointer parent; if (parentID.isNull()) { @@ -25,9 +25,13 @@ SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& s } // search entities - EntityTreeRenderer* treeRenderer = qApp->getEntities(); - EntityTreePointer tree = treeRenderer ? treeRenderer->getTree() : nullptr; - parent = tree ? tree->findEntityByEntityItemID(parentID) : nullptr; + if (entityTree) { + parent = entityTree->findByID(parentID); + } else { + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer tree = treeRenderer ? treeRenderer->getTree() : nullptr; + parent = tree ? tree->findEntityByEntityItemID(parentID) : nullptr; + } if (!parent.expired()) { success = true; return parent; diff --git a/interface/src/InterfaceParentFinder.h b/interface/src/InterfaceParentFinder.h index db579c2144..a2e9fb50e4 100644 --- a/interface/src/InterfaceParentFinder.h +++ b/interface/src/InterfaceParentFinder.h @@ -21,7 +21,7 @@ class InterfaceParentFinder : public SpatialParentFinder { public: InterfaceParentFinder() { } virtual ~InterfaceParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const; }; #endif // hifi_InterfaceParentFinder_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index af1c2e71aa..ef7af3da86 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1009,6 +1009,7 @@ void EntityTree::entityChanged(EntityItemPointer entity) { void EntityTree::fixupMissingParents() { MovingEntitiesOperator moveOperator(getThisPointer()); + if (!_missingParent.empty()) qCDebug(entities) << "HRS fixme fixupMissingParents" << _missingParent.count() << "entities"; QMutableVectorIterator iter(_missingParent); while (iter.hasNext()) { EntityItemWeakPointer entityWP = iter.next(); @@ -1027,6 +1028,7 @@ void EntityTree::fixupMissingParents() { bool doMove = false; if (entity->isParentIDValid()) { + qCDebug(entities) << "HRS fixme valid parent" << entity->getEntityItemID() << queryAACubeSuccess; // this entity's parent was previously not known, and now is. Update its location in the EntityTree... doMove = true; } else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) { @@ -1038,6 +1040,7 @@ void EntityTree::fixupMissingParents() { _childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID(); doMove = true; } + else qCDebug(entities) << "HRS fixme failed parent" << entity->getEntityItemID() << queryAACubeSuccess; if (queryAACubeSuccess && doMove) { moveOperator.addEntityToMoveList(entity, newCube); @@ -1328,19 +1331,22 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen } bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) { + qCDebug(entities) << "sendEntitiesOperation"; SendEntitiesOperationArgs* args = static_cast(extraData); EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - EntityItemID newID(QUuid::createUuid()); + EntityItemID newID = entityItem->getEntityItemID(); // FIXME (QUuid::createUuid()); args->newEntityIDs->append(newID); EntityItemProperties properties = entityItem->getProperties(); properties.setPosition(properties.getPosition() + args->root); properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity + qCDebug(entities) << "sending" << newID << properties.getName() << "parent:" << properties.getParentID(); // queue the packet to send to the server args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties); // also update the local tree instantly (note: this is not our tree, but an alternate tree) + // [Sure looks like the global application's tree to me. See callers. -HRS] if (args->localTree) { args->localTree->withWriteLock([&] { args->localTree->addEntity(newID, properties); @@ -1389,11 +1395,12 @@ bool EntityTree::readFromMap(QVariantMap& map) { } EntityItemPointer entity = addEntity(entityItemID, properties); + qCDebug(entities) << "HRS FIXME added" << entityItemID << properties.getName(); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); } } - + qCDebug(entities) << "HRS FIXME end of readFromMap"; return true; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 0b85c5f32f..8cf6a7e45b 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -16,6 +16,7 @@ #include #include +#include class EntityTree; typedef std::shared_ptr EntityTreePointer; @@ -52,7 +53,7 @@ public: }; -class EntityTree : public Octree { +class EntityTree : public Octree, public SpatialParentTree { Q_OBJECT public: EntityTree(bool shouldReaverage = false); @@ -125,6 +126,7 @@ public: EntityItemPointer findClosestEntity(glm::vec3 position, float targetRadius); EntityItemPointer findEntityByID(const QUuid& id); EntityItemPointer findEntityByEntityItemID(const EntityItemID& entityID); + SpatiallyNestablePointer EntityTree::findByID(const QUuid& id) { return findEntityByID(id); } EntityItemID assignEntityID(const EntityItemID& entityItemID); /// Assigns a known ID for a creator token ID diff --git a/libraries/shared/src/SpatialParentFinder.h b/libraries/shared/src/SpatialParentFinder.h index 9b49490fa5..ff2593c621 100644 --- a/libraries/shared/src/SpatialParentFinder.h +++ b/libraries/shared/src/SpatialParentFinder.h @@ -19,6 +19,10 @@ class SpatiallyNestable; using SpatiallyNestableWeakPointer = std::weak_ptr; using SpatiallyNestablePointer = std::shared_ptr; +class SpatialParentTree { +public: + SpatiallyNestablePointer findByID(const QUuid& id) { return nullptr; } +}; class SpatialParentFinder : public Dependency { @@ -31,7 +35,7 @@ public: SpatialParentFinder() { } virtual ~SpatialParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const = 0; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const = 0; }; #endif // hifi_SpatialParentFinder_h diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 55da4822ef..4e091694de 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -70,7 +70,7 @@ Transform SpatiallyNestable::getParentTransform(bool& success, int depth) const return result; } -SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) const { +SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success, SpatialParentTree* entityTree) const { SpatiallyNestablePointer parent = _parent.lock(); QUuid parentID = getParentID(); // used for its locking @@ -105,7 +105,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons success = false; return nullptr; } - _parent = parentFinder->find(parentID, success); + _parent = parentFinder->find(parentID, success, entityTree); if (!success) { return nullptr; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index a65c7c7f2c..8800fc5ed7 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -139,14 +139,14 @@ public: void die() { _isDead = true; } bool isDead() const { return _isDead; } - bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; } + bool isParentIDValid(SpatialParentTree* entityTree = nullptr) const { bool success = false; getParentPointer(success, entityTree); return success; } protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; QUuid _parentID; // what is this thing's transform relative to? quint16 _parentJointIndex { 0 }; // which joint of the parent is this relative to? - SpatiallyNestablePointer getParentPointer(bool& success) const; + SpatiallyNestablePointer getParentPointer(bool& success, SpatialParentTree* entityTree = nullptr) const; mutable SpatiallyNestableWeakPointer _parent; From 7a066afa26ec5e5d3f95de381700fd322b80b40b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 28 Mar 2016 14:25:51 -0700 Subject: [PATCH 09/29] Checkpoint for freakin edit.js again. --- libraries/entities/src/EntityTree.cpp | 8 ++++---- libraries/entities/src/RecurseOctreeToMapOperator.cpp | 8 +++++--- libraries/entities/src/RecurseOctreeToMapOperator.h | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ef7af3da86..f331d50893 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -90,7 +90,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { _simulation->addEntity(entity); } - if (!entity->isParentIDValid()) { + if (!entity->isParentIDValid(this)) { _missingParent.append(entity); } @@ -260,7 +260,7 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI _missingParent.append(childEntity); continue; } - if (!childEntity->isParentIDValid()) { + if (!childEntity->isParentIDValid(this)) { _missingParent.append(childEntity); } @@ -1027,7 +1027,7 @@ void EntityTree::fixupMissingParents() { } bool doMove = false; - if (entity->isParentIDValid()) { + if (entity->isParentIDValid(this)) { qCDebug(entities) << "HRS fixme valid parent" << entity->getEntityItemID() << queryAACubeSuccess; // this entity's parent was previously not known, and now is. Update its location in the EntityTree... doMove = true; @@ -1367,7 +1367,7 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer entityDescription["Entities"] = QVariantList(); } QScriptEngine scriptEngine; - RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents); + RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents, this); recurseTreeWithOperator(&theOperator); return true; } diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.cpp b/libraries/entities/src/RecurseOctreeToMapOperator.cpp index e930d5ef5f..06a7ce3f27 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToMapOperator.cpp @@ -17,13 +17,15 @@ RecurseOctreeToMapOperator::RecurseOctreeToMapOperator(QVariantMap& map, OctreeElementPointer top, QScriptEngine* engine, bool skipDefaultValues, - bool skipThoseWithBadParents) : + bool skipThoseWithBadParents, + EntityTree* tree) : RecurseOctreeOperator(), _map(map), _top(top), _engine(engine), _skipDefaultValues(skipDefaultValues), - _skipThoseWithBadParents(skipThoseWithBadParents) + _skipThoseWithBadParents(skipThoseWithBadParents), + _entityTree(tree) { // if some element "top" was given, only save information for that element and its children. if (_top) { @@ -49,7 +51,7 @@ bool RecurseOctreeToMapOperator::postRecursion(OctreeElementPointer element) { QVariantList entitiesQList = qvariant_cast(_map["Entities"]); entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - if (_skipThoseWithBadParents && !entityItem->isParentIDValid()) { + if (_skipThoseWithBadParents && !entityItem->isParentIDValid(_entityTree)) { return; // we weren't able to resolve a parent from _parentID, so don't save this entity. } diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.h b/libraries/entities/src/RecurseOctreeToMapOperator.h index c64cf91b61..a774f0f7dd 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.h +++ b/libraries/entities/src/RecurseOctreeToMapOperator.h @@ -14,7 +14,7 @@ class RecurseOctreeToMapOperator : public RecurseOctreeOperator { public: RecurseOctreeToMapOperator(QVariantMap& map, OctreeElementPointer top, QScriptEngine* engine, bool skipDefaultValues, - bool skipThoseWithBadParents); + bool skipThoseWithBadParents, EntityTree* tree); bool preRecursion(OctreeElementPointer element); bool postRecursion(OctreeElementPointer element); private: @@ -24,4 +24,5 @@ public: bool _withinTop; bool _skipDefaultValues; bool _skipThoseWithBadParents; + EntityTree* _entityTree; }; From 92ef8cafb144f5b7e0b4343ae03502dc5b5ce3fd Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 28 Mar 2016 12:08:51 -0700 Subject: [PATCH 10/29] Make sure pos/rot getters don't crash --- interface/src/Application.cpp | 14 ++++++++++++-- libraries/audio-client/src/AudioClient.cpp | 7 ++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7eb19557e5..13b8a565b4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -598,8 +598,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : audioThread->setObjectName("Audio Thread"); auto audioIO = DependencyManager::get(); - audioIO->setPositionGetter([this]{ return getMyAvatar()->getPositionForAudio(); }); - audioIO->setOrientationGetter([this]{ return getMyAvatar()->getOrientationForAudio(); }); + audioIO->setPositionGetter([]{ + auto audioIO = DependencyManager::get(); + auto myAvatar = audioIO ? audioIO->getMyAvatar() : nullptr; + + return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO; + }); + audioIO->setOrientationGetter([]{ + auto audioIO = DependencyManager::get(); + auto myAvatar = audioIO ? audioIO->getMyAvatar() : nullptr; + + return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; + }); audioIO->moveToThread(audioThread); recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 7e01196dc7..50e5b7591f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -50,6 +50,9 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100; +static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; }; +static const auto DEFAULT_ORIENTATION_GETTER = [] { return Quaternions::IDENTITY; }; + Setting::Handle dynamicJitterBuffers("dynamicJitterBuffers", DEFAULT_DYNAMIC_JITTER_BUFFERS); Setting::Handle maxFramesOverDesired("maxFramesOverDesired", DEFAULT_MAX_FRAMES_OVER_DESIRED); Setting::Handle staticDesiredJitterBufferFrames("staticDesiredJitterBufferFrames", @@ -103,7 +106,9 @@ AudioClient::AudioClient() : _outgoingAvatarAudioSequenceNumber(0), _audioOutputIODevice(_receivedAudioStream, this), _stats(&_receivedAudioStream), - _inputGate() + _inputGate(), + _positionGetter(DEFAULT_POSITION_GETTER), + _orientationGetter(DEFAULT_ORIENTATION_GETTER) { // clear the array of locally injected samples memset(_localProceduralSamples, 0, AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL); From 238131014bdd800fc87f7186689a455f9e65fae4 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 28 Mar 2016 14:36:08 -0700 Subject: [PATCH 11/29] Fix avatar manager naming --- interface/src/Application.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 13b8a565b4..1d152ef9d9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -599,14 +599,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : auto audioIO = DependencyManager::get(); audioIO->setPositionGetter([]{ - auto audioIO = DependencyManager::get(); - auto myAvatar = audioIO ? audioIO->getMyAvatar() : nullptr; + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO; }); audioIO->setOrientationGetter([]{ - auto audioIO = DependencyManager::get(); - auto myAvatar = audioIO ? audioIO->getMyAvatar() : nullptr; + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; }); From b09b9a4a0a434cf550d0e8e98b85c69bbc9c3870 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 28 Mar 2016 16:58:20 -0700 Subject: [PATCH 12/29] snapshot after using tree from entity item. --- libraries/entities/src/EntityItem.cpp | 4 ++++ libraries/entities/src/EntityItem.h | 1 + libraries/entities/src/EntityTree.cpp | 8 ++++---- libraries/entities/src/EntityTree.h | 2 +- libraries/entities/src/RecurseOctreeToMapOperator.cpp | 2 +- libraries/shared/src/SpatialParentFinder.h | 2 +- libraries/shared/src/SpatiallyNestable.cpp | 4 ++-- libraries/shared/src/SpatiallyNestable.h | 5 +++-- 8 files changed, 17 insertions(+), 11 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bbc94e9910..c953ed819e 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1010,6 +1010,10 @@ EntityTreePointer EntityItem::getTree() const { return tree; } +SpatialParentTree* EntityItem::getParentTree() const { + return getTree().get(); +} + bool EntityItem::wantTerseEditLogging() const { EntityTreePointer tree = getTree(); return tree ? tree->wantTerseEditLogging() : false; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 577bb406bc..535f2b747d 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -359,6 +359,7 @@ public: void setPhysicsInfo(void* data) { _physicsInfo = data; } EntityTreeElementPointer getElement() const { return _element; } EntityTreePointer getTree() const; + virtual SpatialParentTree* getParentTree() const; bool wantTerseEditLogging() const; glm::mat4 getEntityToWorldMatrix() const; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index f331d50893..91700d0e80 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -90,7 +90,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { _simulation->addEntity(entity); } - if (!entity->isParentIDValid(this)) { + if (!entity->isParentIDValid()) { _missingParent.append(entity); } @@ -260,7 +260,7 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI _missingParent.append(childEntity); continue; } - if (!childEntity->isParentIDValid(this)) { + if (!childEntity->isParentIDValid()) { _missingParent.append(childEntity); } @@ -1027,7 +1027,7 @@ void EntityTree::fixupMissingParents() { } bool doMove = false; - if (entity->isParentIDValid(this)) { + if (entity->isParentIDValid()) { qCDebug(entities) << "HRS fixme valid parent" << entity->getEntityItemID() << queryAACubeSuccess; // this entity's parent was previously not known, and now is. Update its location in the EntityTree... doMove = true; @@ -1040,7 +1040,7 @@ void EntityTree::fixupMissingParents() { _childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID(); doMove = true; } - else qCDebug(entities) << "HRS fixme failed parent" << entity->getEntityItemID() << queryAACubeSuccess; + else qCDebug(entities) << "HRS fixme failed parent" << entity->getEntityItemID() << queryAACubeSuccess << "parent:" << entity->getParentID() << !!findEntityByID(entity->getParentID()); if (queryAACubeSuccess && doMove) { moveOperator.addEntityToMoveList(entity, newCube); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 8cf6a7e45b..892cd86427 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -126,7 +126,7 @@ public: EntityItemPointer findClosestEntity(glm::vec3 position, float targetRadius); EntityItemPointer findEntityByID(const QUuid& id); EntityItemPointer findEntityByEntityItemID(const EntityItemID& entityID); - SpatiallyNestablePointer EntityTree::findByID(const QUuid& id) { return findEntityByID(id); } + virtual SpatiallyNestablePointer findByID(const QUuid& id) { return findEntityByID(id); } EntityItemID assignEntityID(const EntityItemID& entityItemID); /// Assigns a known ID for a creator token ID diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.cpp b/libraries/entities/src/RecurseOctreeToMapOperator.cpp index 06a7ce3f27..24b530db03 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToMapOperator.cpp @@ -51,7 +51,7 @@ bool RecurseOctreeToMapOperator::postRecursion(OctreeElementPointer element) { QVariantList entitiesQList = qvariant_cast(_map["Entities"]); entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - if (_skipThoseWithBadParents && !entityItem->isParentIDValid(_entityTree)) { + if (_skipThoseWithBadParents && !entityItem->isParentIDValid()) { return; // we weren't able to resolve a parent from _parentID, so don't save this entity. } diff --git a/libraries/shared/src/SpatialParentFinder.h b/libraries/shared/src/SpatialParentFinder.h index ff2593c621..aae7d9f040 100644 --- a/libraries/shared/src/SpatialParentFinder.h +++ b/libraries/shared/src/SpatialParentFinder.h @@ -21,7 +21,7 @@ using SpatiallyNestableWeakPointer = std::weak_ptr; using SpatiallyNestablePointer = std::shared_ptr; class SpatialParentTree { public: - SpatiallyNestablePointer findByID(const QUuid& id) { return nullptr; } + virtual SpatiallyNestablePointer findByID(const QUuid& id) { return nullptr; } }; class SpatialParentFinder : public Dependency { diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 4e091694de..13bf5d9054 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -70,7 +70,7 @@ Transform SpatiallyNestable::getParentTransform(bool& success, int depth) const return result; } -SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success, SpatialParentTree* entityTree) const { +SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) const { SpatiallyNestablePointer parent = _parent.lock(); QUuid parentID = getParentID(); // used for its locking @@ -105,7 +105,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success, Spat success = false; return nullptr; } - _parent = parentFinder->find(parentID, success, entityTree); + _parent = parentFinder->find(parentID, success, getParentTree()); if (!success) { return nullptr; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 8800fc5ed7..379f2facd7 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -139,14 +139,15 @@ public: void die() { _isDead = true; } bool isDead() const { return _isDead; } - bool isParentIDValid(SpatialParentTree* entityTree = nullptr) const { bool success = false; getParentPointer(success, entityTree); return success; } + bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; } + virtual SpatialParentTree* getParentTree() const { return nullptr; } protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; QUuid _parentID; // what is this thing's transform relative to? quint16 _parentJointIndex { 0 }; // which joint of the parent is this relative to? - SpatiallyNestablePointer getParentPointer(bool& success, SpatialParentTree* entityTree = nullptr) const; + SpatiallyNestablePointer getParentPointer(bool& success) const; mutable SpatiallyNestableWeakPointer _parent; From 115fd607a0912d8c48e2a82e60271607378c3be3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 28 Mar 2016 19:47:30 -0700 Subject: [PATCH 13/29] Address performance issues introduced with this PR. * Prevent clusterMatrices from being invalidated and re-computed in each updateItem lambda. We do this by not setting _model->_needsUpdateClusterMatrices = true; * Prevent redundant work if Model::enqueueLocationChange is called multiple times per frame. We do this by introducing a preRenderLambdas map in the Application class. Instead of adding work directly to the scene PendingChanges queue Model::enqueueLocationChange adds a lambda to the Application preRenderLambdas map. The Application ensures that only one lambda will be invoked for each model per frame. --- interface/src/Application.cpp | 15 ++++ interface/src/Application.h | 5 ++ .../src/AbstractViewStateInterface.h | 2 + .../render-utils/src/MeshPartPayload.cpp | 2 +- libraries/render-utils/src/Model.cpp | 76 +++++++++++-------- 5 files changed, 69 insertions(+), 31 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 381816e81f..84794f7c31 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2985,6 +2985,11 @@ void Application::updateLOD() { } } +void Application::pushPreRenderLambda(void* key, std::function func) { + std::unique_lock guard(_preRenderLambdasLock); + _preRenderLambdas[key] = func; +} + // Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone. // (Maybe this code should be moved there?) // The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition(). @@ -3461,6 +3466,16 @@ void Application::update(float deltaTime) { QMetaObject::invokeMethod(DependencyManager::get().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); } } + + { + PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0); + + std::unique_lock guard(_preRenderLambdasLock); + for (auto& iter : _preRenderLambdas) { + iter.second(); + } + _preRenderLambdas.clear(); + } } diff --git a/interface/src/Application.h b/interface/src/Application.h index d21e647bc7..9124755011 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -211,6 +211,8 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } + virtual void pushPreRenderLambda(void* key, std::function func) override; + const QRect& getMirrorViewRect() const { return _mirrorViewRect; } void updateMyAvatarLookAtPosition(); @@ -510,6 +512,9 @@ private: bool _cursorNeedsChanging { false }; QThread* _deadlockWatchdogThread; + + std::map> _preRenderLambdas; + std::mutex _preRenderLambdasLock; }; #endif // hifi_Application_h diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 815cb45423..7c7c263562 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -46,6 +46,8 @@ public: virtual render::ScenePointer getMain3DScene() = 0; virtual render::EnginePointer getRenderEngine() = 0; + virtual void pushPreRenderLambda(void* key, std::function func) = 0; + // FIXME - we shouldn't assume that there's a single instance of an AbstractViewStateInterface static AbstractViewStateInterface* instance(); static void setInstance(AbstractViewStateInterface* instance); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 8d738e44d8..41352f1532 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -347,7 +347,7 @@ void ModelMeshPartPayload::initCache() { void ModelMeshPartPayload::notifyLocationChanged() { - _model->_needsUpdateClusterMatrices = true; + } void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const glm::mat4* clusterMatrices, size_t numClusterMatrices) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 55aa000ef4..87534ffac1 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -128,45 +128,61 @@ void Model::setOffset(const glm::vec3& offset) { } void Model::enqueueLocationChange() { - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - Transform modelTransform; - modelTransform.setScale(_scale); - modelTransform.setTranslation(_translation); - modelTransform.setRotation(_rotation); + // queue up this work for later processing, at the end of update and just before rendering. + // the application will ensure only the last lambda is actually invoked. + void* key = (void*)this; + std::weak_ptr weakSelf = shared_from_this(); + AbstractViewStateInterface::instance()->pushPreRenderLambda(key, [weakSelf]() { - Transform modelMeshOffset; - if (_geometry && _geometry->isLoaded()) { - modelMeshOffset = Transform(_rig->getGeometryToRigTransform()); - } else { - modelMeshOffset.postTranslate(_offset); - } + // do nothing, if the model has already been destroyed. + auto self = weakSelf.lock(); + if (!self) { + return; + } - Transform collisionMeshOffset; - collisionMeshOffset.postTranslate(_offset); + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + Transform modelTransform; + modelTransform.setScale(self->_scale); + modelTransform.setTranslation(self->_translation); + modelTransform.setRotation(self->_rotation); - render::PendingChanges pendingChanges; - foreach (auto itemID, _modelMeshRenderItems.keys()) { - pendingChanges.updateItem(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) { + Transform modelMeshOffset; + if (self->_geometry && self->_geometry->isLoaded()) { + // includes model offset and unitScale. + modelMeshOffset = Transform(self->_rig->getGeometryToRigTransform()); + } else { + modelMeshOffset.postTranslate(self->_offset); + } - data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation()); - const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); - size_t numClusterMatrices = data._model->getGeometry()->getFBXGeometry().meshes.at(data._meshIndex).clusters.size(); + // only apply offset only, collision mesh does not share the same unit scale as the FBX file's mesh. + Transform collisionMeshOffset; + collisionMeshOffset.postTranslate(self->_offset); - data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, &state.clusterMatrices[0], numClusterMatrices); - data.notifyLocationChanged(); - }); - } + render::PendingChanges pendingChanges; + foreach (auto itemID, self->_modelMeshRenderItems.keys()) { + pendingChanges.updateItem(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) { - foreach (auto itemID, _collisionRenderItems.keys()) { - pendingChanges.updateItem(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) { - data.updateTransform(modelTransform, collisionMeshOffset); - data.notifyLocationChanged(); - }); - } + // lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box. + data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation()); - scene->enqueuePendingChanges(pendingChanges); + // update the model transform and bounding box for this render item. + const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); + size_t numClusterMatrices = data._model->getGeometry()->getFBXGeometry().meshes.at(data._meshIndex).clusters.size(); + data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, &state.clusterMatrices[0], numClusterMatrices); + }); + } + + foreach (auto itemID, self->_collisionRenderItems.keys()) { + pendingChanges.updateItem(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) { + // update the model transform for this render item. + data.updateTransform(modelTransform, collisionMeshOffset); + }); + } + + scene->enqueuePendingChanges(pendingChanges); + }); } void Model::initJointTransforms() { From 24ca5b3d60a7fe0919d88e74578fe71f71ede623 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 28 Mar 2016 20:29:02 -0700 Subject: [PATCH 14/29] Update after merge changes to NetworkGeometry. Also cleaned up API for ModelMeshPartPayload::updateTransformForSkinnedMesh() to pass a QVector const ref, instead of a raw pointer and a size. --- libraries/render-utils/src/MeshPartPayload.cpp | 9 ++++----- libraries/render-utils/src/MeshPartPayload.h | 2 +- libraries/render-utils/src/Model.cpp | 5 ++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 8a498e99e3..d295b07caf 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -354,15 +354,14 @@ void ModelMeshPartPayload::notifyLocationChanged() { } -void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const glm::mat4* clusterMatrices, size_t numClusterMatrices) { +void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector& clusterMatrices) { ModelMeshPartPayload::updateTransform(transform, offsetTransform); - if (numClusterMatrices > 0) { - + if (clusterMatrices.size() > 0) { _worldBound = AABox(); - for (size_t i = 0; i < numClusterMatrices; i++) { + for (auto& clusterMatrix : clusterMatrices) { AABox clusterBound = _localBound; - clusterBound.transform(clusterMatrices[i]); + clusterBound.transform(clusterMatrix); _worldBound += clusterBound; } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 08c606271c..41869ec7e3 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -79,7 +79,7 @@ public: typedef Payload::DataPointer Pointer; void notifyLocationChanged() override; - void updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const glm::mat4* clusterMatrices, size_t numClusterMatrices); + void updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector& clusterMatrices); // Render Item interface render::ItemKey getKey() const override; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index a29c090bfc..8d59d5f736 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -151,7 +151,7 @@ void Model::enqueueLocationChange() { modelTransform.setRotation(self->_rotation); Transform modelMeshOffset; - if (self->_geometry && self->_geometry->isLoaded()) { + if (self->isLoaded()) { // includes model offset and unitScale. modelMeshOffset = Transform(self->_rig->getGeometryToRigTransform()); } else { @@ -171,8 +171,7 @@ void Model::enqueueLocationChange() { // update the model transform and bounding box for this render item. const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); - size_t numClusterMatrices = data._model->getGeometry()->getFBXGeometry().meshes.at(data._meshIndex).clusters.size(); - data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, &state.clusterMatrices[0], numClusterMatrices); + data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices); }); } From 5db1c33e4dbf6e6edeb76f7b206d0f5a81d93a97 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 29 Mar 2016 09:18:29 -0700 Subject: [PATCH 15/29] kill offsets --- interface/src/Application.cpp | 5 +++-- libraries/entities/src/EntityTree.cpp | 11 ++++++----- libraries/entities/src/RecurseOctreeToMapOperator.cpp | 6 ++---- libraries/entities/src/RecurseOctreeToMapOperator.h | 3 +-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bc2bbc11e0..e5ecd9ce92 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2849,13 +2849,14 @@ bool Application::exportEntities(const QString& filename, const QVectoraddEntity(datum.mappedID, properties); qCDebug(interfaceapp) << "mapped" << properties.getName(); qCDebug(interfaceapp) << " " << originalID << "p:" << datum.originalParentID; qCDebug(interfaceapp) << " =>" << datum.mappedID << "p:" << parentID; - + qCDebug(interfaceapp) << " @" << properties.getPosition() << "/" << properties.getLocalPosition(); + return datum.mappedID; }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 91700d0e80..76e1f618ee 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1335,12 +1335,13 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra SendEntitiesOperationArgs* args = static_cast(extraData); EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - EntityItemID newID = entityItem->getEntityItemID(); // FIXME (QUuid::createUuid()); + EntityItemID newID = /*entityItem->getEntityItemID(); // FIXME*/ (QUuid::createUuid()); + // FIXME: add map to SendEntitiesOperationArgs, and recurse through parent using the map args->newEntityIDs->append(newID); EntityItemProperties properties = entityItem->getProperties(); - properties.setPosition(properties.getPosition() + args->root); + //FIXME properties.setPosition(properties.getPosition() + args->root); properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity - qCDebug(entities) << "sending" << newID << properties.getName() << "parent:" << properties.getParentID(); + qCDebug(entities) << "sending" << newID << properties.getName() << "parent:" << properties.getParentID() << "pos:" << properties.getPosition(); // queue the packet to send to the server args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties); @@ -1367,7 +1368,7 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer entityDescription["Entities"] = QVariantList(); } QScriptEngine scriptEngine; - RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents, this); + RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents); recurseTreeWithOperator(&theOperator); return true; } @@ -1395,7 +1396,7 @@ bool EntityTree::readFromMap(QVariantMap& map) { } EntityItemPointer entity = addEntity(entityItemID, properties); - qCDebug(entities) << "HRS FIXME added" << entityItemID << properties.getName(); + qCDebug(entities) << "HRS FIXME added" << entityItemID << properties.getName() << "@" << properties.getPosition(); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); } diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.cpp b/libraries/entities/src/RecurseOctreeToMapOperator.cpp index 24b530db03..e930d5ef5f 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToMapOperator.cpp @@ -17,15 +17,13 @@ RecurseOctreeToMapOperator::RecurseOctreeToMapOperator(QVariantMap& map, OctreeElementPointer top, QScriptEngine* engine, bool skipDefaultValues, - bool skipThoseWithBadParents, - EntityTree* tree) : + bool skipThoseWithBadParents) : RecurseOctreeOperator(), _map(map), _top(top), _engine(engine), _skipDefaultValues(skipDefaultValues), - _skipThoseWithBadParents(skipThoseWithBadParents), - _entityTree(tree) + _skipThoseWithBadParents(skipThoseWithBadParents) { // if some element "top" was given, only save information for that element and its children. if (_top) { diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.h b/libraries/entities/src/RecurseOctreeToMapOperator.h index a774f0f7dd..c64cf91b61 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.h +++ b/libraries/entities/src/RecurseOctreeToMapOperator.h @@ -14,7 +14,7 @@ class RecurseOctreeToMapOperator : public RecurseOctreeOperator { public: RecurseOctreeToMapOperator(QVariantMap& map, OctreeElementPointer top, QScriptEngine* engine, bool skipDefaultValues, - bool skipThoseWithBadParents, EntityTree* tree); + bool skipThoseWithBadParents); bool preRecursion(OctreeElementPointer element); bool postRecursion(OctreeElementPointer element); private: @@ -24,5 +24,4 @@ public: bool _withinTop; bool _skipDefaultValues; bool _skipThoseWithBadParents; - EntityTree* _entityTree; }; From 80dfed77d75fa3fe2b13ce34d9e310ee1b38eebf Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 29 Mar 2016 10:01:10 -0700 Subject: [PATCH 16/29] Fix for flickering eyeballs Calling glm::axis() on an identity quaternion does not result in a normalized vector. This vector was used within Rig::updateEyeJoint() to limit the rotation of the eye balls, to prevent the eyes from rolling back into the avatar's head. If the avatar was looking straight ahead, this could result in bad quaternions in the eye ball joint matrices, which in turn would cause the eye ball mesh or any mesh influenced by the eyeball joints not to render. --- libraries/animation/src/Rig.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a2b664d064..6a8f190808 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1056,7 +1056,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm // limit rotation const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat)); + if (fabsf(glm::angle(deltaQuat)) > MAX_ANGLE) { + deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat)); + } // directly set absolutePose rotation _internalPoseSet._absolutePoses[index].rot = deltaQuat * headQuat; From 29dedd55241e08bc7f6458dbfbed5e7b15f856b6 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 28 Mar 2016 19:19:16 -0700 Subject: [PATCH 17/29] Update model URL on render thread --- .../src/EntityTreeRenderer.cpp | 34 ++++-------- .../src/RenderableModelEntityItem.cpp | 53 +++++++++---------- libraries/render-utils/src/Model.h | 4 +- 3 files changed, 38 insertions(+), 53 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 07d19dbd92..65ac5197c8 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -460,14 +460,17 @@ void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const Sha ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl) { ModelPointer model = nullptr; - // Make sure we only create and delete models on the thread that owns the EntityTreeRenderer + + // Only create and delete models on the thread that owns the EntityTreeRenderer if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ModelPointer, model), - Q_ARG(const QString&, url)); + Q_ARG(const QString&, url), + Q_ARG(const QString&, collisionUrl)); return model; } + model = std::make_shared(std::make_shared()); model->init(); model->setURL(QUrl(url)); @@ -475,37 +478,20 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString return model; } -ModelPointer EntityTreeRenderer::updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl) { - ModelPointer model = nullptr; - - // The caller shouldn't call us if the URL doesn't need to change. But if they - // do, we just return their original back to them. - if (!original || (QUrl(newUrl) == original->getURL())) { - return original; - } - - // Before we do any creating or deleting, make sure we're on our renderer thread +ModelPointer EntityTreeRenderer::updateModel(ModelPointer model, const QString& newUrl, const QString& collisionUrl) { + // Only create and delete models on the thread that owns the EntityTreeRenderer if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "updateModel", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ModelPointer, model), - Q_ARG(ModelPointer, original), - Q_ARG(const QString&, newUrl)); + Q_ARG(ModelPointer, model), + Q_ARG(const QString&, newUrl), + Q_ARG(const QString&, collisionUrl)); return model; } - // at this point we know we need to replace the model, and we know we're on the - // correct thread, so we can do all our work. - if (original) { - original.reset(); // delete the old model... - } - - // create the model and correctly initialize it with the new url - model = std::make_shared(std::make_shared()); - model->init(); model->setURL(QUrl(newUrl)); model->setCollisionModelURL(QUrl(collisionUrl)); - return model; } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index ae29d1faf7..e2db598d40 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -69,14 +69,10 @@ void RenderableModelEntityItem::loader() { _needsModelReload = true; EntityTreeRenderer* renderer = DependencyManager::get().data(); assert(renderer); - if (!_model || _needsModelReload) { + { PerformanceTimer perfTimer("getModel"); getModel(renderer); } - if (_model) { - _model->setURL(getParsedModelURL()); - _model->setCollisionModelURL(QUrl(getCompoundShapeURL())); - } } void RenderableModelEntityItem::setDimensions(const glm::vec3& value) { @@ -364,13 +360,6 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (hasModel()) { if (_model) { - // check if the URL has changed - auto& currentURL = getParsedModelURL(); - if (currentURL != _model->getURL()) { - qCDebug(entitiesrenderer).noquote() << "Updating model URL: " << currentURL.toDisplayString(); - _model->setURL(currentURL); - } - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); // check to see if when we added our models to the scene they were ready, if they were not ready, then @@ -435,6 +424,15 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } }); updateModelBounds(); + + // Check if the URL has changed + // Do this last as the getModel is queued for the next frame, + // and we need to keep state directing the model to reinitialize + auto& currentURL = getParsedModelURL(); + if (currentURL != _model->getURL()) { + // Defer setting the url to the render thread + getModel(_myRenderer); + } } } } else { @@ -450,10 +448,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { - ModelPointer result = nullptr; - if (!renderer) { - return result; + return nullptr; } // make sure our renderer is setup @@ -468,21 +464,22 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { _needsModelReload = false; // this is the reload - // if we have a URL, then we will want to end up returning a model... + // If we have a URL, then we will want to end up returning a model... if (!getModelURL().isEmpty()) { - - // if we have a previously allocated model, but its URL doesn't match - // then we need to let our renderer update our model for us. - if (_model && (QUrl(getModelURL()) != _model->getURL() || - QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) { - result = _model = _myRenderer->updateModel(_model, getModelURL(), getCompoundShapeURL()); + // If we don't have a model, allocate one *immediately* + if (!_model) { + _model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL()); _needsInitialSimulation = true; - } else if (!_model) { // if we don't yet have a model, then we want our renderer to allocate one - result = _model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL()); + // If we need to change URLs, update it *after rendering* (to avoid access violations) + } else if ((QUrl(getModelURL()) != _model->getURL() || QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) { + QMetaObject::invokeMethod(_myRenderer, "updateModel", Qt::QueuedConnection, + Q_ARG(ModelPointer, _model), + Q_ARG(const QString&, getModelURL()), + Q_ARG(const QString&, getCompoundShapeURL())); _needsInitialSimulation = true; - } else { // we already have the model we want... - result = _model; } + // Else we can just return the _model + // If we have no URL, then we can delete any model we do have... } else if (_model) { // remove from scene render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); @@ -492,11 +489,11 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { // release interest _myRenderer->releaseModel(_model); - result = _model = nullptr; + _model = nullptr; _needsInitialSimulation = true; } - return result; + return _model; } bool RenderableModelEntityItem::needsToCallUpdate() const { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 744a4ce605..785b902b3a 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -73,6 +73,7 @@ public: } /// Sets the URL of the model to render. + // Should only be called from the model's rendering thread to avoid access violations of changed geometry. Q_INVOKABLE void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } @@ -133,7 +134,8 @@ public: /// Provided as a convenience, will crash if !isCollisionLoaded() const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return getCollisionGeometry()->getGeometry()->getGeometry(); } - // Set the model to use for collisions + // Set the model to use for collisions. + // Should only be called from the model's rendering thread to avoid access violations of changed geometry. Q_INVOKABLE void setCollisionModelURL(const QUrl& url); const QUrl& getCollisionURL() const { return _collisionUrl; } From a2c520a74a7b0ad982c95e8dcb1f0c948d8306e4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 29 Mar 2016 10:47:26 -0700 Subject: [PATCH 18/29] ModelOverlay: use a shared_ptr not a model member This is necessary for shared_ptr and weak_ptr to work for Models contained within a ModelOverlay. --- interface/src/ui/overlays/ModelOverlay.cpp | 44 +++++++++++----------- interface/src/ui/overlays/ModelOverlay.h | 4 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 010698800b..adf08934f0 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -18,22 +18,22 @@ QString const ModelOverlay::TYPE = "model"; ModelOverlay::ModelOverlay() - : _model(std::make_shared()), + : _model(std::make_shared(std::make_shared())), _modelTextures(QVariantMap()), _updateModel(false) { - _model.init(); + _model->init(); _isLoaded = false; } ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : Volume3DOverlay(modelOverlay), - _model(std::make_shared()), + _model(std::make_shared(std::make_shared())), _modelTextures(QVariantMap()), _url(modelOverlay->_url), _updateModel(false) { - _model.init(); + _model->init(); if (_url.isValid()) { _updateModel = true; _isLoaded = false; @@ -44,27 +44,27 @@ void ModelOverlay::update(float deltatime) { if (_updateModel) { _updateModel = false; - _model.setSnapModelToCenter(true); - _model.setScale(getDimensions()); - _model.setRotation(getRotation()); - _model.setTranslation(getPosition()); - _model.setURL(_url); - _model.simulate(deltatime, true); + _model->setSnapModelToCenter(true); + _model->setScale(getDimensions()); + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + _model->setURL(_url); + _model->simulate(deltatime, true); } else { - _model.simulate(deltatime); + _model->simulate(deltatime); } - _isLoaded = _model.isActive(); + _isLoaded = _model->isActive(); } bool ModelOverlay::addToScene(Overlay::Pointer overlay, std::shared_ptr scene, render::PendingChanges& pendingChanges) { Volume3DOverlay::addToScene(overlay, scene, pendingChanges); - _model.addToScene(scene, pendingChanges); + _model->addToScene(scene, pendingChanges); return true; } void ModelOverlay::removeFromScene(Overlay::Pointer overlay, std::shared_ptr scene, render::PendingChanges& pendingChanges) { Volume3DOverlay::removeFromScene(overlay, scene, pendingChanges); - _model.removeFromScene(scene, pendingChanges); + _model->removeFromScene(scene, pendingChanges); } void ModelOverlay::render(RenderArgs* args) { @@ -73,9 +73,9 @@ void ModelOverlay::render(RenderArgs* args) { // fix them up in the scene render::ScenePointer scene = qApp->getMain3DScene(); render::PendingChanges pendingChanges; - if (_model.needsFixupInScene()) { - _model.removeFromScene(scene, pendingChanges); - _model.addToScene(scene, pendingChanges); + if (_model->needsFixupInScene()) { + _model->removeFromScene(scene, pendingChanges); + _model->addToScene(scene, pendingChanges); } scene->enqueuePendingChanges(pendingChanges); @@ -100,7 +100,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) { setDimensions(scale); } else { - _model.setScaleToFit(true, getDimensions()); + _model->setScaleToFit(true, getDimensions()); _updateModel = true; } } @@ -120,7 +120,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { QUrl newTextureURL = textureMap[key].toUrl(); qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL; - QMetaObject::invokeMethod(&_model, "setTextureWithNameToURL", Qt::AutoConnection, + QMetaObject::invokeMethod(_model.get(), "setTextureWithNameToURL", Qt::AutoConnection, Q_ARG(const QString&, key), Q_ARG(const QUrl&, newTextureURL)); @@ -134,7 +134,7 @@ QVariant ModelOverlay::getProperty(const QString& property) { return _url.toString(); } if (property == "dimensions" || property == "scale" || property == "size") { - return vec3toVariant(_model.getScaleToFitDimensions()); + return vec3toVariant(_model->getScaleToFitDimensions()); } if (property == "textures") { if (_modelTextures.size() > 0) { @@ -155,13 +155,13 @@ bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& float& distance, BoxFace& face, glm::vec3& surfaceNormal) { QString subMeshNameTemp; - return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp); + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) { - return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); } ModelOverlay* ModelOverlay::createClone() const { diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 0b67f7ed37..36ff75cb6a 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -41,11 +41,11 @@ public: private: - Model _model; + ModelPointer _model; QVariantMap _modelTextures; QUrl _url; bool _updateModel; }; -#endif // hifi_ModelOverlay_h \ No newline at end of file +#endif // hifi_ModelOverlay_h From 848e7703ee665c6541e649190f5add87b32aa6be Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 28 Mar 2016 18:39:53 -0700 Subject: [PATCH 19/29] Test textures emptiness correctly --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index ae29d1faf7..5ec4b1bba9 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -109,7 +109,7 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) { // If textures are unset, revert to original textures - if (textures == "") { + if (textures.isEmpty()) { return _originalTextures; } From 96a35a8f43dc562fac2376b879daf92880a763a3 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 29 Mar 2016 11:33:02 -0700 Subject: [PATCH 20/29] Fix bug where a pending asset request could receive a pending message after deletion --- libraries/networking/src/AssetClient.cpp | 12 +++++++++++- libraries/networking/src/AssetClient.h | 5 +++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 8cb1f31090..446009ac23 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -228,7 +228,8 @@ bool AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end nodeList->sendPacket(std::move(packet), *assetServer); - _pendingRequests[assetServer][messageID] = { callback, progressCallback }; + _pendingRequests[assetServer][messageID] = { QSharedPointer(), callback, progressCallback }; + return true; } else { @@ -326,6 +327,9 @@ void AssetClient::handleAssetGetReply(QSharedPointer message, S if (requestIt != messageCallbackMap.end()) { auto& callbacks = requestIt->second; + // Store message in case we need to disconnect from it later. + callbacks.message = message; + if (message->isComplete()) { callbacks.completeCallback(true, error, message->readAll()); } else { @@ -550,6 +554,12 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) { auto messageMapIt = _pendingRequests.find(node); if (messageMapIt != _pendingRequests.end()) { for (const auto& value : messageMapIt->second) { + auto& message = value.second.message; + if (message) { + // Disconnect from all signals emitting from the pending message + disconnect(message.data(), nullptr, this, nullptr); + } + value.second.completeCallback(false, AssetServerError::NoError, QByteArray()); } messageMapIt->second.clear(); diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index e46c8c6524..f1890377dc 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -86,14 +86,15 @@ private: ReceivedAssetCallback callback, ProgressCallback progressCallback); bool uploadAsset(const QByteArray& data, UploadResultCallback callback); - struct GetAssetCallbacks { + struct GetAssetRequestData { + QSharedPointer message; ReceivedAssetCallback completeCallback; ProgressCallback progressCallback; }; static MessageID _currentID; std::unordered_map> _pendingMappingRequests; - std::unordered_map> _pendingRequests; + std::unordered_map> _pendingRequests; std::unordered_map> _pendingInfoRequests; std::unordered_map> _pendingUploads; From 4a28dadae57e8e8332331c9b6b7abc3e8973a3f6 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 29 Mar 2016 13:26:50 -0700 Subject: [PATCH 21/29] Working, but not cleaned up. --- interface/src/Application.cpp | 62 ++++++++++++++---------- libraries/entities/src/EntityTree.cpp | 68 +++++++++++++++++++++------ libraries/entities/src/EntityTree.h | 5 +- 3 files changed, 92 insertions(+), 43 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 83da078ddd..95990d7c63 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2791,18 +2791,18 @@ void Application::calibrateEyeTracker5Points() { } #endif -class EntityDatum { // For parent-first sorting and mapping. -public: - EntityItemPointer item; - EntityItemProperties properties; - EntityItemID originalParentID; - EntityItemID mappedID; - EntityDatum() {}; - EntityDatum(EntityItemPointer itemArg, EntityItemProperties propertiesArg, EntityItemID parentID) : - item(itemArg), properties(propertiesArg), originalParentID(parentID) { - }; -}; bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { + class EntityDatum { // For parent-first sorting and mapping. + public: + EntityItemPointer item; + EntityItemProperties properties; + EntityItemID originalParentID; + EntityItemID mappedID; + EntityDatum() {}; + EntityDatum(EntityItemPointer itemArg, EntityItemProperties propertiesArg, EntityItemID parentID) : + item(itemArg), properties(propertiesArg), originalParentID(parentID) { + }; + }; QHash entities; auto entityTree = getEntities()->getTree(); @@ -2818,13 +2818,15 @@ bool Application::exportEntities(const QString& filename, const QVectorgetProperties(); - auto position = properties.getPosition(); // see setPosition, below. - root.x = glm::min(root.x, position.x); - root.y = glm::min(root.y, position.y); - root.z = glm::min(root.z, position.z); - + EntityItemID parentID = properties.getParentID(); + if (parentID.isInvalidID() || !entityIDs.contains(parentID)) { + auto position = entityItem->getPosition(); // If parent wasn't selected, we want absolute position, which isn't in properties. + root.x = glm::min(root.x, position.x); + root.y = glm::min(root.y, position.y); + root.z = glm::min(root.z, position.z); + } qCDebug(interfaceapp) << "Exporting" << entityItem->getEntityItemID() << entityItem->getName(); - entities[entityID] = EntityDatum(entityItem, properties, properties.getParentID()); + entities[entityID] = EntityDatum(entityItem, properties, parentID); } if (entities.size() == 0) { @@ -2833,7 +2835,7 @@ bool Application::exportEntities(const QString& filename, const QVector getMapped = [&](EntityDatum& datum) { + std::function getMapped = [&](EntityDatum& datum) { // FIXME: move definition outside the loop auto originalID = datum.item->getEntityItemID(); if (!datum.mappedID.isInvalidID()) { qCDebug(interfaceapp) << "already mapped" << datum.properties.getName() << originalID << "=>" << datum.mappedID; @@ -2841,16 +2843,26 @@ bool Application::exportEntities(const QString& filename, const QVectorgetPosition(success); + if (success) { + properties.setPosition(globalPosition - root); + if (!parentID.isInvalidID()) { // There's a parent that we won't output. Make the other data global. + properties.setRotation(datum.item->getRotation()); + properties.setDimensions(datum.item->getDimensions()); + // Should we do velocities and accelerations, too? + } + } else { + properties.setPosition(datum.item->getQueryAACube().calcCenter() - root); // best we can do + } + } else {// Recurse over ancestors, updating properties. qCDebug(interfaceapp) << "FIXME recursing" << datum.originalParentID << "parent of" << datum.item->getEntityItemID(); - // Warning: this is not a tail-call, so exporting a REALLY deep parent hierarchy will blow the call stack. + // Warning: could blow the call stack if the parent hierarchy is VERY deep. parentID = getMapped(entities[parentID]); properties.setParentID(parentID); } - // The so-called root offset (which isn't) is confusing and not what content developers want. And why would queryAACube not then be offset? - // But leaving it in for bug-compatibility right now. -HRS - // FIXME properties.setPosition(properties.getPosition() - root); - datum.mappedID = originalID; //EntityItemID(QUuid::createUuid()); + datum.mappedID = originalID; // FIXME: simplify because we don't have to map ids. auto newEntity = exportTree->addEntity(datum.mappedID, properties); qCDebug(interfaceapp) << "mapped" << properties.getName(); qCDebug(interfaceapp) << " " << originalID << "p:" << datum.originalParentID; @@ -2862,8 +2874,6 @@ bool Application::exportEntities(const QString& filename, const QVectorremapIDs(); exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 76e1f618ee..2d8e20c69a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1320,26 +1320,62 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen float x, float y, float z) { SendEntitiesOperationArgs args; args.packetSender = packetSender; - args.localTree = localTree; + args.ourTree = this; + args.otherTree = localTree; args.root = glm::vec3(x, y, z); - QVector newEntityIDs; - args.newEntityIDs = &newEntityIDs; + // If this is called repeatedly (e.g., multiple pastes with the same data), the new elements will clash unless we use new identifiers. + // We need to keep a map so that we can map parent identifiers correctly. + QHash map; + args.map = ↦ recurseTreeWithOperation(sendEntitiesOperation, &args); packetSender->releaseQueuedMessages(); - return newEntityIDs; + return map.values().toVector(); } bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) { qCDebug(entities) << "sendEntitiesOperation"; SendEntitiesOperationArgs* args = static_cast(extraData); EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); - entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - EntityItemID newID = /*entityItem->getEntityItemID(); // FIXME*/ (QUuid::createUuid()); - // FIXME: add map to SendEntitiesOperationArgs, and recurse through parent using the map - args->newEntityIDs->append(newID); - EntityItemProperties properties = entityItem->getProperties(); - //FIXME properties.setPosition(properties.getPosition() + args->root); + std::function getMapped = [&](EntityItemPointer& item) -> const EntityItemID { + EntityItemID oldID = item->getEntityItemID(); + if (args->map->contains(oldID)) { // Already been handled (e.g., as a parent of somebody that we've processed). + return args->map->value(oldID); + } + EntityItemID newID = QUuid::createUuid(); + args->map->insert(oldID, newID); + EntityItemProperties properties = item->getProperties(); + EntityItemID oldParentID = properties.getParentID(); + if (oldParentID.isInvalidID()) { // no parent + properties.setPosition(properties.getPosition() + args->root); + } else { + EntityItemPointer parentEntity = args->ourTree->findEntityByEntityItemID(oldParentID); + if (parentEntity) { // map the parent + // Warning: could blow the call stack if the parent hierarchy is VERY deep. + properties.setParentID(getMapped(parentEntity)); + // But do not add root offset in this case. + } else { // Should not happen, but let's try to be helpful... + // Log what we can. + QString name = properties.getName(); + if (name.isEmpty()) { + name = EntityTypes::getEntityTypeName(properties.getType()); + } + bool success; + glm::vec3 position = item->getPosition(success); + qCWarning(entities) << "Cannot find" << oldParentID << "parent of" << oldID << name << (success ? "" : "and unable to resolve geometry"); + // Adjust geometry with absolute/global values. + if (success) { + properties.setPosition(position + args->root); + properties.setRotation(item->getRotation()); + properties.setDimensions(item->getDimensions()); + // Should we do velocities and accelerations, too? + } else { + properties.setPosition(item->getQueryAACube().calcCenter() + args->root); // best we can do + } + QUuid empty; + properties.setParentID(empty); + } + } properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity qCDebug(entities) << "sending" << newID << properties.getName() << "parent:" << properties.getParentID() << "pos:" << properties.getPosition(); @@ -1347,13 +1383,15 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties); // also update the local tree instantly (note: this is not our tree, but an alternate tree) - // [Sure looks like the global application's tree to me. See callers. -HRS] - if (args->localTree) { - args->localTree->withWriteLock([&] { - args->localTree->addEntity(newID, properties); + if (args->otherTree) { + args->otherTree->withWriteLock([&] { + args->otherTree->addEntity(newID, properties); }); } - }); + return newID; + }; + + entityTreeElement->forEachEntity(getMapped); return true; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 892cd86427..f3400feb8e 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -47,9 +47,10 @@ public: class SendEntitiesOperationArgs { public: glm::vec3 root; - EntityTreePointer localTree; + EntityTree* ourTree; + EntityTreePointer otherTree; EntityEditPacketSender* packetSender; - QVector* newEntityIDs; + QHash* map; }; From 6c7b6cd62e64cb70de408fb8206c041cf4a1aed3 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 29 Mar 2016 14:12:19 -0700 Subject: [PATCH 22/29] Return parsed tex directly as variant --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 5ec4b1bba9..5f5be5ebc8 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -124,7 +124,9 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) { qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures; return _originalTextures; } - return texturesJson.object().toVariantMap(); + + auto parsed = texturesJson.toVariant(); + return parsed.toMap(); } void RenderableModelEntityItem::remapTextures() { From bcb729eac2eaa3b0f5d2b5bcec403d5563304c94 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 29 Mar 2016 15:21:06 -0700 Subject: [PATCH 23/29] Abstract out globalizeProperties. --- interface/src/Application.cpp | 65 +++++++-------------------- libraries/entities/src/EntityItem.cpp | 22 +++++++++ libraries/entities/src/EntityItem.h | 2 + libraries/entities/src/EntityTree.cpp | 26 +---------- 4 files changed, 43 insertions(+), 72 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 95990d7c63..362658360e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2792,7 +2792,7 @@ void Application::calibrateEyeTracker5Points() { #endif bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { - class EntityDatum { // For parent-first sorting and mapping. + /* class EntityDatum { // For parent-first sorting and mapping. public: EntityItemPointer item; EntityItemProperties properties; @@ -2803,7 +2803,8 @@ bool Application::exportEntities(const QString& filename, const QVector entities; + QHash entities;*/ + QHash entities; auto entityTree = getEntities()->getTree(); auto exportTree = std::make_shared(); @@ -2813,66 +2814,34 @@ bool Application::exportEntities(const QString& filename, const QVectorfindEntityByEntityItemID(entityID); if (!entityItem) { - qCDebug(interfaceapp) << "Skipping export of" << entityID << "that is not in scene."; + qCWarning(interfaceapp) << "Skipping export of" << entityID << "that is not in scene."; continue; } - auto properties = entityItem->getProperties(); - EntityItemID parentID = properties.getParentID(); - if (parentID.isInvalidID() || !entityIDs.contains(parentID)) { + EntityItemID parentID = entityItem->getParentID(); + if (parentID.isInvalidID() || !entityIDs.contains(parentID) || !entityTree->findEntityByEntityItemID(parentID)) { auto position = entityItem->getPosition(); // If parent wasn't selected, we want absolute position, which isn't in properties. root.x = glm::min(root.x, position.x); root.y = glm::min(root.y, position.y); root.z = glm::min(root.z, position.z); } - qCDebug(interfaceapp) << "Exporting" << entityItem->getEntityItemID() << entityItem->getName(); - entities[entityID] = EntityDatum(entityItem, properties, parentID); + entities[entityID] = entityItem; // EntityDatum(entityItem, entityItem->getProperties(), parentID); } if (entities.size() == 0) { return false; } - for (EntityDatum& entityDatum : entities) { - // Recursively add the parents of entities to the exportTree, mapping their new identifiers as we go. - std::function getMapped = [&](EntityDatum& datum) { // FIXME: move definition outside the loop - auto originalID = datum.item->getEntityItemID(); - if (!datum.mappedID.isInvalidID()) { - qCDebug(interfaceapp) << "already mapped" << datum.properties.getName() << originalID << "=>" << datum.mappedID; - return datum.mappedID; // We are a parent that has already been added/mapped. - } - auto properties = datum.properties; - auto parentID = datum.originalParentID; - if (parentID.isInvalidID() || !entityIDs.contains(parentID)) { - bool success; - auto globalPosition = datum.item->getPosition(success); - if (success) { - properties.setPosition(globalPosition - root); - if (!parentID.isInvalidID()) { // There's a parent that we won't output. Make the other data global. - properties.setRotation(datum.item->getRotation()); - properties.setDimensions(datum.item->getDimensions()); - // Should we do velocities and accelerations, too? - } - } else { - properties.setPosition(datum.item->getQueryAACube().calcCenter() - root); // best we can do - } - } else {// Recurse over ancestors, updating properties. - qCDebug(interfaceapp) << "FIXME recursing" << datum.originalParentID << "parent of" << datum.item->getEntityItemID(); - // Warning: could blow the call stack if the parent hierarchy is VERY deep. - parentID = getMapped(entities[parentID]); - properties.setParentID(parentID); - } - datum.mappedID = originalID; // FIXME: simplify because we don't have to map ids. - auto newEntity = exportTree->addEntity(datum.mappedID, properties); - qCDebug(interfaceapp) << "mapped" << properties.getName(); - qCDebug(interfaceapp) << " " << originalID << "p:" << datum.originalParentID; - qCDebug(interfaceapp) << " =>" << datum.mappedID << "p:" << parentID; - qCDebug(interfaceapp) << " @" << properties.getPosition() << "/" << properties.getLocalPosition(); - - return datum.mappedID; - }; - - getMapped(entityDatum); + //for (EntityDatum& entityDatum : entities) { + for (EntityItemPointer& entityDatum : entities) { + auto properties = entityDatum->getProperties(); + EntityItemID parentID = properties.getParentID(); + if (parentID.isInvalidID()) { + properties.setPosition(properties.getPosition() - root); + } else if (!entities.contains(parentID)) { + entityDatum->globalizeProperties(properties, "Parent %3 of %2 %1 is not selected for export.", -root); + } // else valid parent -- don't offset + exportTree->addEntity(entityDatum->getEntityItemID(), properties); } exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index c953ed819e..6a016720d8 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1982,3 +1982,25 @@ void EntityItem::dimensionsChanged() { requiresRecalcBoxes(); SpatiallyNestable::dimensionsChanged(); // Do what you have to do } + +void EntityItem::globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate, const glm::vec3& offset) const { + bool success; + auto globalPosition = getPosition(success); + if (success) { + properties.setPosition(globalPosition + offset); + properties.setRotation(getRotation()); + properties.setDimensions(getDimensions()); + // Should we do velocities and accelerations, too? This could end up being quite involved, which is why the method exists. + } else { + properties.setPosition(getQueryAACube().calcCenter() + offset); // best we can do + } + if (!messageTemplate.isEmpty()) { + QString name = properties.getName(); + if (name.isEmpty()) { + name = EntityTypes::getEntityTypeName(properties.getType()); + } + qCWarning(entities) << messageTemplate.arg(getEntityItemID().toString()).arg(name).arg(properties.getParentID().toString()); + } + QUuid empty; + properties.setParentID(empty); +} \ No newline at end of file diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 535f2b747d..622f78b2d3 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -86,6 +86,8 @@ public: /// returns true if something changed virtual bool setProperties(const EntityItemProperties& properties); + // Update properties with empty parent id and globalized/absolute values (applying offset), and apply (non-empty) log template to args id, name-or-type, parent id. + void globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate = QString(), const glm::vec3& offset = glm::vec3(0.0f)) const; /// Override this in your derived class if you'd like to be informed when something about the state of the entity /// has changed. This will be called with properties change or when new data is loaded from a stream diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 2d8e20c69a..0d714eaafb 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1334,7 +1334,6 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen } bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) { - qCDebug(entities) << "sendEntitiesOperation"; SendEntitiesOperationArgs* args = static_cast(extraData); EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); std::function getMapped = [&](EntityItemPointer& item) -> const EntityItemID { @@ -1351,33 +1350,14 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra } else { EntityItemPointer parentEntity = args->ourTree->findEntityByEntityItemID(oldParentID); if (parentEntity) { // map the parent - // Warning: could blow the call stack if the parent hierarchy is VERY deep. + // Warning: (non-tail) recursion of getMapped could blow the call stack if the parent hierarchy is VERY deep. properties.setParentID(getMapped(parentEntity)); // But do not add root offset in this case. } else { // Should not happen, but let's try to be helpful... - // Log what we can. - QString name = properties.getName(); - if (name.isEmpty()) { - name = EntityTypes::getEntityTypeName(properties.getType()); - } - bool success; - glm::vec3 position = item->getPosition(success); - qCWarning(entities) << "Cannot find" << oldParentID << "parent of" << oldID << name << (success ? "" : "and unable to resolve geometry"); - // Adjust geometry with absolute/global values. - if (success) { - properties.setPosition(position + args->root); - properties.setRotation(item->getRotation()); - properties.setDimensions(item->getDimensions()); - // Should we do velocities and accelerations, too? - } else { - properties.setPosition(item->getQueryAACube().calcCenter() + args->root); // best we can do - } - QUuid empty; - properties.setParentID(empty); + item->globalizeProperties(properties, "Cannot find %3 parent of %2 %1", args->root); } } properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity - qCDebug(entities) << "sending" << newID << properties.getName() << "parent:" << properties.getParentID() << "pos:" << properties.getPosition(); // queue the packet to send to the server args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties); @@ -1434,12 +1414,10 @@ bool EntityTree::readFromMap(QVariantMap& map) { } EntityItemPointer entity = addEntity(entityItemID, properties); - qCDebug(entities) << "HRS FIXME added" << entityItemID << properties.getName() << "@" << properties.getPosition(); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); } } - qCDebug(entities) << "HRS FIXME end of readFromMap"; return true; } From ad9027f9d650bff58b1f4bdd0fd4415b4d9f3cd2 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 29 Mar 2016 15:09:27 -0700 Subject: [PATCH 24/29] Log OffscreenQml lifetime --- .../src/RenderableWebEntityItem.cpp | 2 +- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index ce3faeb196..855fd16408 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -55,8 +55,8 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { qWarning() << "Too many concurrent web views to create new view"; return false; } - qDebug() << "Building web surface"; + ++_currentWebCount; // Save the original GL context, because creating a QML surface will create a new context QOpenGLContext * currentContext = QOpenGLContext::currentContext(); diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 818b3c6ca8..1d9e6d0149 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -70,7 +70,7 @@ public: virtual bool event(QEvent *e) override; protected: - class Queue : public QQueue { + class Queue : private QQueue { public: void add(QEvent::Type type); QEvent* take(); @@ -134,12 +134,14 @@ QEvent* OffscreenQmlRenderThread::Queue::take() { } OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) { + qDebug() << "Building QML Renderer: creating context"; if (!_canvas.create(shareContext)) { static const char* error = "Failed to create OffscreenGLCanvas"; qWarning() << error; throw error; }; + qDebug() << "Building QML Renderer: creating render control"; _renderControl = new QMyQuickRenderControl(); QQuickWindow::setDefaultAlphaBuffer(true); // Create a QQuickWindow that is associated with our render control. @@ -147,19 +149,25 @@ OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, // NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events // NOTE: Must be created on the rendering thread or it will refuse to render, // so we wait until after its ctor to move object/context to this thread. + qDebug() << "Building QML Renderer: creating window"; _quickWindow = new QQuickWindow(_renderControl); _quickWindow->setColor(QColor(255, 255, 255, 0)); _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground)); // We can prepare, but we must wait to start() the thread until after the ctor + qDebug() << "Building QML Renderer: moving to own thread"; _renderControl->prepareThread(this); _canvas.getContextObject()->moveToThread(this); moveToThread(this); + qDebug() << "Building QML Renderer: complete"; + _queue.add(INIT); } void OffscreenQmlRenderThread::run() { + qDebug() << "Starting QML Renderer thread"; + while (!_quit) { QEvent* e = _queue.take(); event(e); @@ -208,12 +216,14 @@ void OffscreenQmlRenderThread::setupFbo() { } void OffscreenQmlRenderThread::init() { + qDebug() << "Initializing QML Renderer"; + connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender); connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate); if (!_canvas.makeCurrent()) { - // Failed to make GL context current, this OffscreenQmlSurface is basically dead qWarning("Failed to make context current on QML Renderer Thread"); + _quit = true; return; } @@ -360,6 +370,8 @@ void OffscreenQmlSurface::onAboutToQuit() { } void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { + qDebug() << "Building QML surface"; + _renderer = new OffscreenQmlRenderThread(this, shareContext); _renderer->moveToThread(_renderer); _renderer->setObjectName("QML Renderer Thread"); From 6ae03fe72e0fbb4783a6dc57f4ac2b3bc57fd39f Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 29 Mar 2016 15:47:00 -0700 Subject: [PATCH 25/29] Sync ordering of gl fields/reset commands --- libraries/gpu/src/gpu/GLBackend.h | 1 - libraries/gpu/src/gpu/GLBackendState.cpp | 5 +++-- libraries/gpu/src/gpu/State.h | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index d4efe7fe99..f5abacd279 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -158,7 +158,6 @@ public: ~GLState(); // The state commands to reset to default, - // WARNING depending on the order of the State::Field enum static const Commands _resetStateCommands; friend class GLBackend; diff --git a/libraries/gpu/src/gpu/GLBackendState.cpp b/libraries/gpu/src/gpu/GLBackendState.cpp index 64bd87c876..36ce1dd555 100644 --- a/libraries/gpu/src/gpu/GLBackendState.cpp +++ b/libraries/gpu/src/gpu/GLBackendState.cpp @@ -35,6 +35,7 @@ const GLBackend::GLState::Commands makeResetStateCommands(); const GLBackend::GLState::Commands GLBackend::GLState::_resetStateCommands = makeResetStateCommands(); +// NOTE: This must stay in sync with the ordering of the State::Field enum const GLBackend::GLState::Commands makeResetStateCommands() { // Since State::DEFAULT is a static defined in another .cpp the initialisation order is random // and we have a 50/50 chance that State::DEFAULT is not yet initialized. @@ -69,9 +70,9 @@ const GLBackend::GLState::Commands makeResetStateCommands() { CommandPointer(stencilCommand), CommandPointer(stencilCommand), - std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), - std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), + + std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), diff --git a/libraries/gpu/src/gpu/State.h b/libraries/gpu/src/gpu/State.h index 7e32a7280a..385edec277 100755 --- a/libraries/gpu/src/gpu/State.h +++ b/libraries/gpu/src/gpu/State.h @@ -345,6 +345,7 @@ public: uint8 getColorWriteMask() const { return _values.colorWriteMask; } // All the possible fields + // NOTE: If you change this, you must update GLBackend::GLState::_resetStateCommands enum Field { FILL_MODE, CULL_MODE, @@ -364,6 +365,7 @@ public: STENCIL_TEST_BACK, SAMPLE_MASK, + ALPHA_TO_COVERAGE_ENABLE, BLEND_FUNCTION, From 4ebf81616689e215c7478f234abf2fb0312c1491 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 29 Mar 2016 16:17:19 -0700 Subject: [PATCH 26/29] cleanup --- interface/src/Application.cpp | 66 ++++++++------------------- interface/src/Application.h | 2 +- libraries/entities/src/EntityTree.cpp | 5 +- 3 files changed, 21 insertions(+), 52 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 362658360e..8285d92004 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2791,19 +2791,7 @@ void Application::calibrateEyeTracker5Points() { } #endif -bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { - /* class EntityDatum { // For parent-first sorting and mapping. - public: - EntityItemPointer item; - EntityItemProperties properties; - EntityItemID originalParentID; - EntityItemID mappedID; - EntityDatum() {}; - EntityDatum(EntityItemPointer itemArg, EntityItemProperties propertiesArg, EntityItemID parentID) : - item(itemArg), properties(propertiesArg), originalParentID(parentID) { - }; - }; - QHash entities;*/ +bool Application::exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset) { QHash entities; auto entityTree = getEntities()->getTree(); @@ -2818,21 +2806,25 @@ bool Application::exportEntities(const QString& filename, const QVectorgetParentID(); - if (parentID.isInvalidID() || !entityIDs.contains(parentID) || !entityTree->findEntityByEntityItemID(parentID)) { - auto position = entityItem->getPosition(); // If parent wasn't selected, we want absolute position, which isn't in properties. - root.x = glm::min(root.x, position.x); - root.y = glm::min(root.y, position.y); - root.z = glm::min(root.z, position.z); + if (!givenOffset) { + EntityItemID parentID = entityItem->getParentID(); + if (parentID.isInvalidID() || !entityIDs.contains(parentID) || !entityTree->findEntityByEntityItemID(parentID)) { + auto position = entityItem->getPosition(); // If parent wasn't selected, we want absolute position, which isn't in properties. + root.x = glm::min(root.x, position.x); + root.y = glm::min(root.y, position.y); + root.z = glm::min(root.z, position.z); + } } - entities[entityID] = entityItem; // EntityDatum(entityItem, entityItem->getProperties(), parentID); + entities[entityID] = entityItem; } if (entities.size() == 0) { return false; } - //for (EntityDatum& entityDatum : entities) { + if (givenOffset) { + root = *givenOffset; + } for (EntityItemPointer& entityDatum : entities) { auto properties = entityDatum->getProperties(); EntityItemID parentID = properties.getParentID(); @@ -2852,33 +2844,14 @@ bool Application::exportEntities(const QString& filename, const QVector entities; - getEntities()->getTree()->findEntities(AACube(glm::vec3(x, y, z), scale), entities); - - if (entities.size() > 0) { - glm::vec3 root(x, y, z); - auto exportTree = std::make_shared(); - exportTree->createRootElement(); - - for (int i = 0; i < entities.size(); i++) { - EntityItemProperties properties = entities.at(i)->getProperties(); - EntityItemID id = entities.at(i)->getEntityItemID(); - properties.setPosition(properties.getPosition() - root); - exportTree->addEntity(id, properties); - } - - // remap IDs on export so that we aren't publishing the IDs of entities in our domain - exportTree->remapIDs(); - - exportTree->writeToSVOFile(filename.toLocal8Bit().constData()); - } else { - qCDebug(interfaceapp) << "No models were selected"; - return false; + QVector ids; + getEntities()->getTree()->findEntities(AACube(offset, scale), entities); + foreach(EntityItemPointer entity, entities) { + ids << entity->getEntityItemID(); } - - // restore the main window's active state - _window->activateWindow(); - return true; + return exportEntities(filename, ids, &offset); } void Application::loadSettings() { @@ -2911,7 +2884,6 @@ bool Application::importEntities(const QString& urlOrFilename) { bool success = _entityClipboard->readFromURL(urlOrFilename); if (success) { - // FIXME _entityClipboard->remapIDs(); _entityClipboard->reaverageOctreeElements(); } return success; diff --git a/interface/src/Application.h b/interface/src/Application.h index d21e647bc7..f20d72fcb6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -233,7 +233,7 @@ signals: public slots: QVector pasteEntities(float x, float y, float z); - bool exportEntities(const QString& filename, const QVector& entityIDs); + bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); bool exportEntities(const QString& filename, float x, float y, float z, float scale); bool importEntities(const QString& url); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0d714eaafb..ab5edb88d8 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1009,7 +1009,6 @@ void EntityTree::entityChanged(EntityItemPointer entity) { void EntityTree::fixupMissingParents() { MovingEntitiesOperator moveOperator(getThisPointer()); - if (!_missingParent.empty()) qCDebug(entities) << "HRS fixme fixupMissingParents" << _missingParent.count() << "entities"; QMutableVectorIterator iter(_missingParent); while (iter.hasNext()) { EntityItemWeakPointer entityWP = iter.next(); @@ -1028,7 +1027,6 @@ void EntityTree::fixupMissingParents() { bool doMove = false; if (entity->isParentIDValid()) { - qCDebug(entities) << "HRS fixme valid parent" << entity->getEntityItemID() << queryAACubeSuccess; // this entity's parent was previously not known, and now is. Update its location in the EntityTree... doMove = true; } else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) { @@ -1040,7 +1038,6 @@ void EntityTree::fixupMissingParents() { _childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID(); doMove = true; } - else qCDebug(entities) << "HRS fixme failed parent" << entity->getEntityItemID() << queryAACubeSuccess << "parent:" << entity->getParentID() << !!findEntityByID(entity->getParentID()); if (queryAACubeSuccess && doMove) { moveOperator.addEntityToMoveList(entity, newCube); @@ -1342,7 +1339,6 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra return args->map->value(oldID); } EntityItemID newID = QUuid::createUuid(); - args->map->insert(oldID, newID); EntityItemProperties properties = item->getProperties(); EntityItemID oldParentID = properties.getParentID(); if (oldParentID.isInvalidID()) { // no parent @@ -1368,6 +1364,7 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra args->otherTree->addEntity(newID, properties); }); } + args->map->insert(oldID, newID); return newID; }; From eef9de4d4f8b82755025ba121611afc2821403f5 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 29 Mar 2016 16:28:02 -0700 Subject: [PATCH 27/29] Remove obsolete remapIDs. --- libraries/entities/src/EntityTree.cpp | 5 ---- libraries/entities/src/EntityTree.h | 2 -- libraries/entities/src/RemapIDOperator.cpp | 33 ---------------------- libraries/entities/src/RemapIDOperator.h | 30 -------------------- 4 files changed, 70 deletions(-) delete mode 100644 libraries/entities/src/RemapIDOperator.cpp delete mode 100644 libraries/entities/src/RemapIDOperator.h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ab5edb88d8..458ca90435 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1372,11 +1372,6 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra return true; } -void EntityTree::remapIDs() { - RemapIDOperator theOperator; - recurseTreeWithOperator(&theOperator); -} - bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) { if (! entityDescription.contains("Entities")) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index f3400feb8e..5c5f5b4eb9 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -203,8 +203,6 @@ public: bool wantTerseEditLogging() const { return _wantTerseEditLogging; } void setWantTerseEditLogging(bool value) { _wantTerseEditLogging = value; } - void remapIDs(); - virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) override; virtual bool readFromMap(QVariantMap& entityDescription) override; diff --git a/libraries/entities/src/RemapIDOperator.cpp b/libraries/entities/src/RemapIDOperator.cpp deleted file mode 100644 index eee6e49a1c..0000000000 --- a/libraries/entities/src/RemapIDOperator.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// RemapIDOperator.cpp -// libraries/entities/src -// -// Created by Seth Alves on 2015-12-6. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#include "EntityTree.h" -#include "RemapIDOperator.h" - -QUuid RemapIDOperator::remap(const QUuid& oldID) { - if (oldID.isNull()) { - return oldID; - } - if (!_oldToNew.contains(oldID)) { - _oldToNew[oldID] = QUuid::createUuid(); - } - return _oldToNew[oldID]; -} - -bool RemapIDOperator::postRecursion(OctreeElementPointer element) { - EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); - entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - entityItem->setID(remap(entityItem->getID())); - entityItem->setParentID(remap(entityItem->getParentID())); - }); - return true; -} diff --git a/libraries/entities/src/RemapIDOperator.h b/libraries/entities/src/RemapIDOperator.h deleted file mode 100644 index 439aec28fc..0000000000 --- a/libraries/entities/src/RemapIDOperator.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// RemapIDOperator.h -// libraries/entities/src -// -// Created by Seth Alves on 2015-12-6. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_RemapIDOperator_h -#define hifi_RemapIDOperator_h - -#include "Octree.h" - -// this will change all the IDs in an EntityTree. Parent/Child relationships are maintained. - -class RemapIDOperator : public RecurseOctreeOperator { -public: - RemapIDOperator() : RecurseOctreeOperator() {} - ~RemapIDOperator() {} - virtual bool preRecursion(OctreeElementPointer element) { return true; } - virtual bool postRecursion(OctreeElementPointer element); -private: - QUuid remap(const QUuid& oldID); - QHash _oldToNew; -}; - -#endif // hifi_RemapIDOperator_h From 86a77d4f55eee99394c59343db34cb8c5552305a Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 29 Mar 2016 16:55:17 -0700 Subject: [PATCH 28/29] Remove unused/non-existent include. --- libraries/entities/src/EntityTree.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 458ca90435..5292e12ae8 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -24,7 +24,6 @@ #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" #include "LogHandler.h" -#include "RemapIDOperator.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; From 5381be6902b8fd774ba953e87610d9e098c6d7b9 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 29 Mar 2016 17:06:19 -0700 Subject: [PATCH 29/29] Whitespace --- interface/src/Application.cpp | 3 +-- libraries/entities/src/EntityItem.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8285d92004..4f1dfe77f3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -270,7 +270,7 @@ public: void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); -/* fixme + uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us uint64_t now = usecTimestampNow(); auto lastHeartbeatAge = (now > lastHeartbeat) ? now - lastHeartbeat : 0; @@ -319,7 +319,6 @@ public: deadlockDetectionCrash(); } #endif - */ } } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 6a016720d8..431d638063 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -2003,4 +2003,4 @@ void EntityItem::globalizeProperties(EntityItemProperties& properties, const QSt } QUuid empty; properties.setParentID(empty); -} \ No newline at end of file +}