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 c6669303a8..de9b134b83 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 avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + + return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO; + }); + audioIO->setOrientationGetter([]{ + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + + return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; + }); audioIO->moveToThread(audioThread); recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { @@ -2790,43 +2800,50 @@ void Application::calibrateEyeTracker5Points() { } #endif -bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { - QVector entities; +bool Application::exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset) { + 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) { + qCWarning(interfaceapp) << "Skipping export of" << entityID << "that is not in scene."; continue; } - auto properties = entityItem->getProperties(); - auto position = properties.getPosition(); - - 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; + 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; } if (entities.size() == 0) { return false; } - for (auto entityItem : entities) { - auto properties = entityItem->getProperties(); - - properties.setPosition(properties.getPosition() - root); - exportTree->addEntity(entityItem->getEntityItemID(), properties); + if (givenOffset) { + root = *givenOffset; + } + 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); } - - // remap IDs on export so that we aren't publishing the IDs of entities in our domain - exportTree->remapIDs(); exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); @@ -2836,33 +2853,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() { @@ -2895,7 +2893,6 @@ bool Application::importEntities(const QString& urlOrFilename) { bool success = _entityClipboard->readFromURL(urlOrFilename); if (success) { - _entityClipboard->remapIDs(); _entityClipboard->reaverageOctreeElements(); } return success; @@ -2978,6 +2975,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(). @@ -3454,6 +3456,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..31c865ad90 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(); @@ -233,7 +235,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); @@ -510,6 +512,9 @@ private: bool _cursorNeedsChanging { false }; QThread* _deadlockWatchdogThread; + + std::map> _preRenderLambdas; + std::mutex _preRenderLambdasLock; }; #endif // hifi_Application_h 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/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 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; 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/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); 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..13942dc282 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) { @@ -109,7 +105,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; } @@ -124,7 +120,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() { @@ -364,13 +362,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 +426,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 +450,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 +466,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 +491,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/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/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bbc94e9910..431d638063 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; @@ -1978,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); +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 577bb406bc..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 @@ -359,6 +361,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 af1c2e71aa..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; @@ -1317,42 +1316,59 @@ 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) { SendEntitiesOperationArgs* args = static_cast(extraData); EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); - entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { - EntityItemID newID(QUuid::createUuid()); - args->newEntityIDs->append(newID); - EntityItemProperties properties = entityItem->getProperties(); - 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(); + 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: (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... + 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 // 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) - if (args->localTree) { - args->localTree->withWriteLock([&] { - args->localTree->addEntity(newID, properties); + if (args->otherTree) { + args->otherTree->withWriteLock([&] { + args->otherTree->addEntity(newID, properties); }); } - }); - return true; -} + args->map->insert(oldID, newID); + return newID; + }; -void EntityTree::remapIDs() { - RemapIDOperator theOperator; - recurseTreeWithOperator(&theOperator); + entityTreeElement->forEachEntity(getMapped); + return true; } bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, @@ -1393,7 +1409,6 @@ bool EntityTree::readFromMap(QVariantMap& map) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); } } - return true; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 0b85c5f32f..5c5f5b4eb9 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; @@ -46,13 +47,14 @@ public: class SendEntitiesOperationArgs { public: glm::vec3 root; - EntityTreePointer localTree; + EntityTree* ourTree; + EntityTreePointer otherTree; EntityEditPacketSender* packetSender; - QVector* newEntityIDs; + QHash* map; }; -class EntityTree : public Octree { +class EntityTree : public Octree, public SpatialParentTree { Q_OBJECT public: EntityTree(bool shouldReaverage = false); @@ -125,6 +127,7 @@ public: EntityItemPointer findClosestEntity(glm::vec3 position, float targetRadius); EntityItemPointer findEntityByID(const QUuid& id); EntityItemPointer findEntityByEntityItemID(const EntityItemID& entityID); + virtual SpatiallyNestablePointer findByID(const QUuid& id) { return findEntityByID(id); } EntityItemID assignEntityID(const EntityItemID& entityItemID); /// Assigns a known ID for a creator token ID @@ -200,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/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/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 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"); 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, diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index df86ba2a8f..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, Boxes& bounds) 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 f9d9b0eeb4..a3e95c68c0 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 - // the returned box is the bounding box of ALL the evaluated part bounds. - Box evalPartBounds(int partStart, int partEnd, Boxes& bounds) const; + // evaluate the bounding boxes of the parts in the range [start, end] + // 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); } 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; 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 854630abf0..d295b07caf 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -58,7 +58,6 @@ void MeshPartPayload::updateMeshPart(const std::shared_ptr& d auto vertexFormat = _drawMesh->getVertexFormat(); _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); _drawPart = _drawMesh->getPartBuffer().get(partIndex); - _localBound = _drawMesh->evalPartBound(partIndex); } } @@ -352,7 +351,23 @@ void ModelMeshPartPayload::initCache() { void ModelMeshPartPayload::notifyLocationChanged() { - _model->_needsUpdateClusterMatrices = true; + +} + +void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector& clusterMatrices) { + ModelMeshPartPayload::updateTransform(transform, offsetTransform); + + if (clusterMatrices.size() > 0) { + _worldBound = AABox(); + for (auto& clusterMatrix : clusterMatrices) { + AABox clusterBound = _localBound; + clusterBound.transform(clusterMatrix); + _worldBound += clusterBound; + } + + // clusterMatrix has world rotation but not world translation. + _worldBound.translate(transform.getTranslation()); + } } ItemKey ModelMeshPartPayload::getKey() const { @@ -377,12 +392,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 { assert(_model->isLoaded()); const FBXGeometry& geometry = _model->getFBXGeometry(); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 9f73a46a93..41869ec7e3 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -74,15 +74,15 @@ namespace render { class ModelMeshPartPayload : public MeshPartPayload { public: ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); - + typedef render::Payload Payload; typedef Payload::DataPointer Pointer; void notifyLocationChanged() override; + void updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector& clusterMatrices); // 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; @@ -101,4 +101,11 @@ public: bool _isBlendShaped{ false }; }; +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 545d239549..8d59d5f736 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -130,25 +130,60 @@ void Model::setOffset(const glm::vec3& offset) { } void Model::enqueueLocationChange() { - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - Transform transform; - transform.setTranslation(_translation); - transform.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 offset; - offset.setScale(_scale); - offset.postTranslate(_offset); + // do nothing, if the model has already been destroyed. + auto self = weakSelf.lock(); + if (!self) { + return; + } - render::PendingChanges pendingChanges; - foreach (auto itemID, _renderItems.keys()) { - pendingChanges.updateItem(itemID, [transform, offset](MeshPartPayload& data) { - data.updateTransform(transform, offset); - data.notifyLocationChanged(); - }); - } + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - scene->enqueuePendingChanges(pendingChanges); + Transform modelTransform; + modelTransform.setScale(self->_scale); + modelTransform.setTranslation(self->_translation); + modelTransform.setRotation(self->_rotation); + + Transform modelMeshOffset; + if (self->isLoaded()) { + // includes model offset and unitScale. + modelMeshOffset = Transform(self->_rig->getGeometryToRigTransform()); + } else { + modelMeshOffset.postTranslate(self->_offset); + } + + // 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); + + render::PendingChanges pendingChanges; + foreach (auto itemID, self->_modelMeshRenderItems.keys()) { + pendingChanges.updateItem(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) { + + // 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()); + + // update the model transform and bounding box for this render item. + const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); + data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices); + }); + } + + 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() { @@ -497,8 +532,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 +552,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 +590,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 +610,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 +620,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; } @@ -1175,10 +1241,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); @@ -1204,9 +1274,9 @@ void Model::segregateMeshGroups() { _collisionHullMaterial->setMetallic(0.02f); _collisionHullMaterial->setRoughness(0.5f); } - _renderItemsSet << std::make_shared(networkMesh, partIndex, _collisionHullMaterial, transform, offset); + _collisionRenderItemsSet << std::make_shared(networkMesh, partIndex, _collisionHullMaterial, transform, offset); } else { - _renderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); + _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); } shapeID++; @@ -1229,13 +1299,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 744a4ce605..e4a8fa3b36 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) { @@ -73,6 +74,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 +135,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; } @@ -373,8 +376,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 }; 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; diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 0b453e6f36..a3afa220c9 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 AABox::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 (isInvalid()) { + _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); } @@ -484,17 +493,31 @@ 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); } +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; auto maximum = _corner + _scale; @@ -544,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 ec06c60121..30bde2d717 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,10 +92,19 @@ 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); - 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; diff --git a/libraries/shared/src/SpatialParentFinder.h b/libraries/shared/src/SpatialParentFinder.h index 9b49490fa5..aae7d9f040 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: + virtual 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..13bf5d9054 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -105,7 +105,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons success = false; return nullptr; } - _parent = parentFinder->find(parentID, success); + _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 a65c7c7f2c..379f2facd7 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -140,6 +140,7 @@ public: bool isDead() const { return _isDead; } bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; } + virtual SpatialParentTree* getParentTree() const { return nullptr; } protected: const NestableType _nestableType; // EntityItem or an AvatarData diff --git a/tests/shared/src/AABoxTests.cpp b/tests/shared/src/AABoxTests.cpp index fd709a488c..b9ab95bb09 100644 --- a/tests/shared/src/AABoxTests.cpp +++ b/tests/shared/src/AABoxTests.cpp @@ -151,3 +151,21 @@ void AABoxTests::testTouchesSphere() { } } +void AABoxTests::testScale() { + 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 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); +} 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