From 277f5ef3a4527efd9a6c0d1f96a8ada6ee455dda Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 23 May 2015 13:35:35 -0700 Subject: [PATCH 01/34] Working experimental first version. --- .../src/EntityTreeRenderer.cpp | 19 +++++++++++++++ .../src/EntityTreeRenderer.h | 4 ++++ .../entities/src/EntityScriptingInterface.cpp | 7 ++++++ .../entities/src/EntityScriptingInterface.h | 8 +++++++ libraries/script-engine/src/ScriptEngine.cpp | 24 +++++++++++++++++++ libraries/script-engine/src/ScriptEngine.h | 8 +++++++ 6 files changed, 70 insertions(+) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c47c24f20a..a41f23a7cc 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -873,6 +873,23 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity); connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity); + connect(this, &EntityTreeRenderer::collisionWithEntity, entityScriptingInterface, &EntityScriptingInterface::collisionWithEntity); + + connect(entityScriptingInterface, &EntityScriptingInterface::addEntityEventHandler, this, &EntityTreeRenderer::addEntityEventHandler); + connect(entityScriptingInterface, &EntityScriptingInterface::removeEntityEventHandler, this, &EntityTreeRenderer::addEntityEventHandler); +} + +void EntityTreeRenderer::addEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { + ScriptEngine* engine = static_cast(handler.engine()); + if (engine) { // In case it's gone by the time we get the signal + engine->addEntityEventHandler(entityID, entityEventName, handler); + } +} +void EntityTreeRenderer::removeEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { + ScriptEngine* engine = static_cast(handler.engine()); + if (engine) { + engine->removeEntityEventHandler(entityID, entityEventName, handler); + } } QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { @@ -1187,6 +1204,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons playEntityCollisionSound(myNodeID, entityTree, idB, collision); // And now the entity scripts + emit collisionWithEntity(idA, idB, collision); QScriptValue entityScriptA = loadEntityScript(idA); if (entityScriptA.property("collisionWithEntity").isValid()) { QScriptValueList args; @@ -1196,6 +1214,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons entityScriptA.property("collisionWithEntity").call(entityScriptA, args); } + emit collisionWithEntity(idB, idB, collision); QScriptValue entityScriptB = loadEntityScript(idB); if (entityScriptB.property("collisionWithEntity").isValid()) { QScriptValueList args; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 9768d4a20a..2c841e6f06 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -107,6 +107,7 @@ signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); + void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); public slots: void addingEntity(const EntityItemID& entityID); @@ -121,6 +122,9 @@ public slots: void setDisplayModelElementProxy(bool value) { _displayModelElementProxy = value; } void setDontDoPrecisionPicking(bool value) { _dontDoPrecisionPicking = value; } + void addEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); + void removeEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); + protected: virtual Octree* createTree() { return new EntityTree(true); } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 351bbc3643..a23bb386ff 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -201,6 +201,13 @@ void EntityScriptingInterface::dumpTree() const { } } +void EntityScriptingInterface::addEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { + emit addEntityEventHandler(entityID, entityEventName, handler); +} +void EntityScriptingInterface::removeEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { + emit removeEntityEventHandler(entityID, entityEventName, handler); +} + QVector EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const { QVector result; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index f1876a836b..c04e55a8a0 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -119,8 +119,16 @@ public slots: Q_INVOKABLE void dumpTree() const; + // Register a function that will handle the given entityEventName on entityID + Q_INVOKABLE void addEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); + Q_INVOKABLE void removeEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); + signals: + void addEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); + void removeEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); + void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); + void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); void canAdjustLocksChanged(bool canAdjustLocks); void canRezChanged(bool canRez); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 427944e254..81e30d0265 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -389,6 +389,30 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func } } +void ScriptEngine::addEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { + auto entities = DependencyManager::get(); + if (!_registeredHandlers.contains(entityID)) { + _registeredHandlers[entityID] = RegisteredEventHandlers(); + } + _registeredHandlers[entityID][eventName] = handler; + if (eventName == "collisionWithEntity") { + connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, &ScriptEngine::collisionWithEntity); + } + // FIXME: deletingEntity, changingEntityID +} +void ScriptEngine::removeEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { + // FIXME +} +void ScriptEngine::collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { + if (!_registeredHandlers.contains(idA)) return; + const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[idA]; + if (!handlersOnEntity.contains("collisionWithEntity")) return; + // FIXME: Need one more level of indirection. We need to allow multiple handlers per event, registered by different scripts. + QScriptValue handlerForEvent = handlersOnEntity["collisionWithEntity"]; + QScriptValueList args = QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision); + handlerForEvent.call(QScriptValue(), args); +} + void ScriptEngine::evaluate() { if (_stoppingAllScripts) { return; // bail early diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 45e56850a7..5aab43dff0 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -31,11 +31,14 @@ #include "ScriptCache.h" #include "ScriptUUID.h" #include "Vec3.h" +#include "EntityItemID.h" const QString NO_SCRIPT(""); const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 1000) + 0.5); +typedef QHash RegisteredEventHandlers; + class ScriptEngine : public QScriptEngine, public ScriptUser { Q_OBJECT public: @@ -98,6 +101,9 @@ public: virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents); virtual void errorInLoadingScript(const QUrl& url); + void addEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + void removeEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + public slots: void loadURL(const QUrl& scriptURL); void stop(); @@ -114,6 +120,7 @@ public slots: QUrl resolvePath(const QString& path) const; void nodeKilled(SharedNodePointer node); + void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); signals: void scriptLoaded(const QString& scriptFilename); @@ -164,6 +171,7 @@ private: ArrayBufferClass* _arrayBufferClass; QHash _outgoingScriptAudioSequenceNumbers; + QHash _registeredHandlers; private: static QSet _allKnownScriptEngines; From 1dd2b7275eec685b19a16706b8f49668a18e402c Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 23 May 2015 18:11:05 -0700 Subject: [PATCH 02/34] Simpler and more uniform version that handles all the standard entity-script events. --- .../src/EntityTreeRenderer.cpp | 16 ---- .../src/EntityTreeRenderer.h | 3 - .../entities/src/EntityScriptingInterface.cpp | 11 ++- libraries/script-engine/src/ScriptEngine.cpp | 91 +++++++++++++++---- libraries/script-engine/src/ScriptEngine.h | 5 +- 5 files changed, 85 insertions(+), 41 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index a41f23a7cc..c5e0e0ccaa 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -874,22 +874,6 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity); connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity); connect(this, &EntityTreeRenderer::collisionWithEntity, entityScriptingInterface, &EntityScriptingInterface::collisionWithEntity); - - connect(entityScriptingInterface, &EntityScriptingInterface::addEntityEventHandler, this, &EntityTreeRenderer::addEntityEventHandler); - connect(entityScriptingInterface, &EntityScriptingInterface::removeEntityEventHandler, this, &EntityTreeRenderer::addEntityEventHandler); -} - -void EntityTreeRenderer::addEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { - ScriptEngine* engine = static_cast(handler.engine()); - if (engine) { // In case it's gone by the time we get the signal - engine->addEntityEventHandler(entityID, entityEventName, handler); - } -} -void EntityTreeRenderer::removeEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { - ScriptEngine* engine = static_cast(handler.engine()); - if (engine) { - engine->removeEntityEventHandler(entityID, entityEventName, handler); - } } QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 2c841e6f06..ce40363261 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -121,9 +121,6 @@ public slots: void setDisplayModelBounds(bool value) { _displayModelBounds = value; } void setDisplayModelElementProxy(bool value) { _displayModelElementProxy = value; } void setDontDoPrecisionPicking(bool value) { _dontDoPrecisionPicking = value; } - - void addEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); - void removeEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); protected: virtual Octree* createTree() { return new EntityTree(true); } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index a23bb386ff..e3b48fad61 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -17,6 +17,7 @@ #include "ModelEntityItem.h" #include "ZoneEntityItem.h" #include "EntitiesLogging.h" +#include "../../script-engine/src/ScriptEngine.h" // FIXME EntityScriptingInterface::EntityScriptingInterface() : @@ -202,10 +203,16 @@ void EntityScriptingInterface::dumpTree() const { } void EntityScriptingInterface::addEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { - emit addEntityEventHandler(entityID, entityEventName, handler); + ScriptEngine* engine = static_cast(handler.engine()); + if (engine) { // In case it's gone by the time we get the signal + engine->addEntityEventHandler(entityID, entityEventName, handler); + } } void EntityScriptingInterface::removeEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { - emit removeEntityEventHandler(entityID, entityEventName, handler); + ScriptEngine* engine = static_cast(handler.engine()); + if (engine) { + engine->removeEntityEventHandler(entityID, entityEventName, handler); + } } QVector EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const { diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 81e30d0265..df51275ce2 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -389,30 +389,85 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func } } -void ScriptEngine::addEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { - auto entities = DependencyManager::get(); - if (!_registeredHandlers.contains(entityID)) { - _registeredHandlers[entityID] = RegisteredEventHandlers(); - } - _registeredHandlers[entityID][eventName] = handler; - if (eventName == "collisionWithEntity") { - connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, &ScriptEngine::collisionWithEntity); - } - // FIXME: deletingEntity, changingEntityID +// Answer the previously registered handler for the given entityID/eventName, else an invalid QScriptValue. +QScriptValue ScriptEngine::getEntityEventHandler(const EntityItemID& entityID, const QString& eventName) { + if (!_registeredHandlers.contains(entityID)) return _illegal; + const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; + if (!handlersOnEntity.contains(eventName)) return _illegal; + // FIXME: Need one more level of indirection. We need to allow multiple handlers per event, registered by different scripts. + return handlersOnEntity[eventName]; } void ScriptEngine::removeEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { // FIXME } -void ScriptEngine::collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { - if (!_registeredHandlers.contains(idA)) return; - const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[idA]; - if (!handlersOnEntity.contains("collisionWithEntity")) return; - // FIXME: Need one more level of indirection. We need to allow multiple handlers per event, registered by different scripts. - QScriptValue handlerForEvent = handlersOnEntity["collisionWithEntity"]; - QScriptValueList args = QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision); - handlerForEvent.call(QScriptValue(), args); +// FIXME: deletingEntity, changingEntityID +void ScriptEngine::addEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { + if (!_registeredHandlers.contains(entityID)) { + _registeredHandlers[entityID] = RegisteredEventHandlers(); + } + _registeredHandlers[entityID][eventName] = handler; + + // The rest connects the Entities signal to the handler here. + auto entities = DependencyManager::get(); + + // Given thunk that generates the arglist, evaluate that thunk only if needed, and call the handler. + auto generalHandler = [=](std::function argGenerator) { + QScriptValue handlerForEvent = getEntityEventHandler(entityID, eventName); + if (handlerForEvent.isValid()) { + QScriptValueList args = argGenerator(); + handlerForEvent.call(QScriptValue(), args); + } + }; + // Two common cases of event handler, differing only in argument signature. + auto singleEntityHandler = [=](const EntityItemID& entityItemID) { + generalHandler([=]() { + return QScriptValueList() << entityItemID.toScriptValue(this); + }); + }; + auto mouseHandler = [=](const EntityItemID& entityItemID, const MouseEvent& event) { + generalHandler([=]() { + return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); + }); + }; + // This string comparison maze only happens when adding a handler, which isn't often, rather than during events. + // Nonetheless, I wish it were more DRY. Variadic signals from strings? "I know reflection. I've worked with reflection. QT is no reflection." + if (eventName == "enterEntity") { + connect(entities.data(), &EntityScriptingInterface::enterEntity, this, singleEntityHandler); + } else if (eventName == "leaveEntity") { + connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, singleEntityHandler); + + } else if (eventName == "mousePressOnEntity") { + connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, mouseHandler); + } else if (eventName == "mouseMoveOnEntity") { + connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, mouseHandler); + } else if (eventName == "mouseReleaseOnEntity") { + connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, mouseHandler); + + } else if (eventName == "clickDownOnEntity") { + connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, mouseHandler); + } else if (eventName == "holdingClickOnEntity") { + connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, mouseHandler); + } else if (eventName == "clickReleaseOnEntity") { + connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, mouseHandler); + + } else if (eventName == "hoverEnterEntity") { + connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, mouseHandler); + } else if (eventName == "hoverOverEntity") { + connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, mouseHandler); + } else if (eventName == "hoverLeaveEntity") { + connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, mouseHandler); + + } else if (eventName == "collisionWithEntity") { + connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, + [=](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { + generalHandler([=]() { + return QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision); + }); + }); + } } + void ScriptEngine::evaluate() { if (_stoppingAllScripts) { return; // bail early diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 5aab43dff0..32261be2f3 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -31,7 +31,7 @@ #include "ScriptCache.h" #include "ScriptUUID.h" #include "Vec3.h" -#include "EntityItemID.h" +#include "../../entities/src/EntityItem.h" // FIXME const QString NO_SCRIPT(""); @@ -120,7 +120,6 @@ public slots: QUrl resolvePath(const QString& path) const; void nodeKilled(SharedNodePointer node); - void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); signals: void scriptLoaded(const QString& scriptFilename); @@ -172,6 +171,8 @@ private: QHash _outgoingScriptAudioSequenceNumbers; QHash _registeredHandlers; + QScriptValue _illegal; + QScriptValue getEntityEventHandler(const EntityItemID& entityID, const QString& eventName); private: static QSet _allKnownScriptEngines; From b884d3cf90483792a9daaec91e9a8bbfc0d78883 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 24 May 2015 09:57:12 -0700 Subject: [PATCH 03/34] Handle all the signals. --- .../entities/src/EntityScriptingInterface.cpp | 2 + .../entities/src/EntityScriptingInterface.h | 1 + libraries/script-engine/src/ScriptEngine.cpp | 128 +++++++++--------- libraries/script-engine/src/ScriptEngine.h | 3 +- 4 files changed, 69 insertions(+), 65 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index e3b48fad61..152c1621ef 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -47,6 +47,7 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { if (_entityTree) { disconnect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); disconnect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); + disconnect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); disconnect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } @@ -55,6 +56,7 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { if (_entityTree) { connect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); connect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); + connect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); connect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index c04e55a8a0..508a566ed2 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -150,6 +150,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); + void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); void clearingEntities(); private: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index df51275ce2..f3ff41f19a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -389,82 +389,84 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func } } -// Answer the previously registered handler for the given entityID/eventName, else an invalid QScriptValue. -QScriptValue ScriptEngine::getEntityEventHandler(const EntityItemID& entityID, const QString& eventName) { - if (!_registeredHandlers.contains(entityID)) return _illegal; +// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args +void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator) { + if (!_registeredHandlers.contains(entityID)) return; const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; - if (!handlersOnEntity.contains(eventName)) return _illegal; + if (!handlersOnEntity.contains(eventName)) return; // FIXME: Need one more level of indirection. We need to allow multiple handlers per event, registered by different scripts. - return handlersOnEntity[eventName]; + QScriptValue handlerForEvent = handlersOnEntity[eventName]; + if (handlerForEvent.isValid()) { + QScriptValueList args = argGenerator(); + handlerForEvent.call(QScriptValue(), args); + } } +// Unregister the handler for this eventName and entityID. void ScriptEngine::removeEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { - // FIXME + if (!_registeredHandlers.contains(entityID)) return; + RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; + if (!handlersOnEntity.contains(eventName)) return; + handlersOnEntity.remove(eventName); } // FIXME: deletingEntity, changingEntityID +// Register the handler. void ScriptEngine::addEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { - if (!_registeredHandlers.contains(entityID)) { - _registeredHandlers[entityID] = RegisteredEventHandlers(); - } - _registeredHandlers[entityID][eventName] = handler; - - // The rest connects the Entities signal to the handler here. - auto entities = DependencyManager::get(); - - // Given thunk that generates the arglist, evaluate that thunk only if needed, and call the handler. - auto generalHandler = [=](std::function argGenerator) { - QScriptValue handlerForEvent = getEntityEventHandler(entityID, eventName); - if (handlerForEvent.isValid()) { - QScriptValueList args = argGenerator(); - handlerForEvent.call(QScriptValue(), args); - } - }; - // Two common cases of event handler, differing only in argument signature. - auto singleEntityHandler = [=](const EntityItemID& entityItemID) { - generalHandler([=]() { - return QScriptValueList() << entityItemID.toScriptValue(this); - }); - }; - auto mouseHandler = [=](const EntityItemID& entityItemID, const MouseEvent& event) { - generalHandler([=]() { - return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); - }); - }; - // This string comparison maze only happens when adding a handler, which isn't often, rather than during events. - // Nonetheless, I wish it were more DRY. Variadic signals from strings? "I know reflection. I've worked with reflection. QT is no reflection." - if (eventName == "enterEntity") { - connect(entities.data(), &EntityScriptingInterface::enterEntity, this, singleEntityHandler); - } else if (eventName == "leaveEntity") { - connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, singleEntityHandler); - - } else if (eventName == "mousePressOnEntity") { - connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, mouseHandler); - } else if (eventName == "mouseMoveOnEntity") { - connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, mouseHandler); - } else if (eventName == "mouseReleaseOnEntity") { - connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, mouseHandler); - - } else if (eventName == "clickDownOnEntity") { - connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, mouseHandler); - } else if (eventName == "holdingClickOnEntity") { - connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, mouseHandler); - } else if (eventName == "clickReleaseOnEntity") { - connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, mouseHandler); - - } else if (eventName == "hoverEnterEntity") { - connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, mouseHandler); - } else if (eventName == "hoverOverEntity") { - connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, mouseHandler); - } else if (eventName == "hoverLeaveEntity") { - connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, mouseHandler); - - } else if (eventName == "collisionWithEntity") { + if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script... + // Connect up ALL the handlers to the global entities object's signals. + // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) + auto entities = DependencyManager::get(); + connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, + [=](const EntityItemID& entityID) { + _registeredHandlers.remove(entityID); + }); + connect(entities.data(), &EntityScriptingInterface::changingEntityID, this, + [=](const EntityItemID& oldEntityID, const EntityItemID& newEntityID) { + if (!_registeredHandlers.contains(oldEntityID)) return; + _registeredHandlers[newEntityID] = _registeredHandlers[oldEntityID]; + _registeredHandlers.remove(oldEntityID); + }); + + // Two common cases of event handler, differing only in argument signature. + auto makeSingleEntityHandler = [=](const QString& eventName) { + return [=](const EntityItemID& entityItemID) { + generalHandler(entityItemID, eventName, [=]() { + return QScriptValueList() << entityItemID.toScriptValue(this); + }); + }; + }; + auto makeMouseHandler = [=](const QString& eventName) { + return [=](const EntityItemID& entityItemID, const MouseEvent& event) { + generalHandler(entityItemID, eventName, [=]() { + return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); + }); + }; + }; + connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); + connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); + + connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makeMouseHandler("mousePressOnEntity")); + connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makeMouseHandler("mouseMoveOnEntity")); + connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makeMouseHandler("mouseReleaseOnEntity")); + + connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makeMouseHandler("clickDownOnEntity")); + connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makeMouseHandler("holdingClickOnEntity")); + connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makeMouseHandler("clickReleaseOnEntity")); + + connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makeMouseHandler("hoverEnterEntity")); + connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); + connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); + connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, [=](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { - generalHandler([=]() { + generalHandler(idA, "collisionWithEntity", [=]() { return QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision); }); }); } + if (!_registeredHandlers.contains(entityID)) { + _registeredHandlers[entityID] = RegisteredEventHandlers(); + } + _registeredHandlers[entityID][eventName] = handler; } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 32261be2f3..28610fd65c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -171,8 +171,7 @@ private: QHash _outgoingScriptAudioSequenceNumbers; QHash _registeredHandlers; - QScriptValue _illegal; - QScriptValue getEntityEventHandler(const EntityItemID& entityID, const QString& eventName); + void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator); private: static QSet _allKnownScriptEngines; From a0d09c0a6d95125e5dd5d2f3de5b3e3112b14778 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 24 May 2015 11:48:11 -0700 Subject: [PATCH 04/34] Mulitple independent handlers for same entity/event. --- libraries/script-engine/src/ScriptEngine.cpp | 24 +++++++++++++------- libraries/script-engine/src/ScriptEngine.h | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f3ff41f19a..e1e920da59 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -395,20 +395,27 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; if (!handlersOnEntity.contains(eventName)) return; // FIXME: Need one more level of indirection. We need to allow multiple handlers per event, registered by different scripts. - QScriptValue handlerForEvent = handlersOnEntity[eventName]; - if (handlerForEvent.isValid()) { + QScriptValueList handlersForEvent = handlersOnEntity[eventName]; + if (!handlersForEvent.isEmpty()) { QScriptValueList args = argGenerator(); - handlerForEvent.call(QScriptValue(), args); + for (int i = 0; i < handlersForEvent.count(); ++i) { + handlersForEvent[i].call(QScriptValue(), args); + } } } -// Unregister the handler for this eventName and entityID. +// Unregister the handlers for this eventName and entityID. void ScriptEngine::removeEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { if (!_registeredHandlers.contains(entityID)) return; RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; - if (!handlersOnEntity.contains(eventName)) return; - handlersOnEntity.remove(eventName); + QScriptValueList& handlersForEvent = handlersOnEntity[eventName]; + // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate. + for (int i = 0; i < handlersForEvent.count(); ++i) { + if (handlersForEvent[i].equals(handler)) { + handlersForEvent.removeAt(i); + return; // Design choice: since comparison is relatively expensive, just remove the first matching handler. + } + } } -// FIXME: deletingEntity, changingEntityID // Register the handler. void ScriptEngine::addEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script... @@ -466,7 +473,8 @@ void ScriptEngine::addEntityEventHandler(const EntityItemID& entityID, const QSt if (!_registeredHandlers.contains(entityID)) { _registeredHandlers[entityID] = RegisteredEventHandlers(); } - _registeredHandlers[entityID][eventName] = handler; + QScriptValueList& handlersForEvent = _registeredHandlers[entityID][eventName]; + handlersForEvent << handler; // Note that the same handler can be added many times. See removeEntityEventHandler(). } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 28610fd65c..0fd8ed0562 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -37,7 +37,7 @@ const QString NO_SCRIPT(""); const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 1000) + 0.5); -typedef QHash RegisteredEventHandlers; +typedef QHash RegisteredEventHandlers; class ScriptEngine : public QScriptEngine, public ScriptUser { Q_OBJECT From 4266a99d786a23e2a32b615a8374a49dfa5005f9 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 11:28:33 -0700 Subject: [PATCH 05/34] Put the javascript methods on Script, not Entities, and other minimum-diff cleanup. --- .../entities-renderer/src/EntityTreeRenderer.h | 2 +- .../entities/src/EntityScriptingInterface.cpp | 15 --------------- libraries/entities/src/EntityScriptingInterface.h | 7 ------- libraries/script-engine/src/ScriptEngine.cpp | 4 ++-- libraries/script-engine/src/ScriptEngine.h | 6 +++--- 5 files changed, 6 insertions(+), 28 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index ce40363261..1f03e01e7c 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -121,7 +121,7 @@ public slots: void setDisplayModelBounds(bool value) { _displayModelBounds = value; } void setDisplayModelElementProxy(bool value) { _displayModelElementProxy = value; } void setDontDoPrecisionPicking(bool value) { _dontDoPrecisionPicking = value; } - + protected: virtual Octree* createTree() { return new EntityTree(true); } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 152c1621ef..1460e90a4d 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -17,8 +17,6 @@ #include "ModelEntityItem.h" #include "ZoneEntityItem.h" #include "EntitiesLogging.h" -#include "../../script-engine/src/ScriptEngine.h" // FIXME - EntityScriptingInterface::EntityScriptingInterface() : _entityTree(NULL) @@ -204,19 +202,6 @@ void EntityScriptingInterface::dumpTree() const { } } -void EntityScriptingInterface::addEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { - ScriptEngine* engine = static_cast(handler.engine()); - if (engine) { // In case it's gone by the time we get the signal - engine->addEntityEventHandler(entityID, entityEventName, handler); - } -} -void EntityScriptingInterface::removeEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler) { - ScriptEngine* engine = static_cast(handler.engine()); - if (engine) { - engine->removeEntityEventHandler(entityID, entityEventName, handler); - } -} - QVector EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const { QVector result; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 508a566ed2..ca6e266e98 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -119,14 +119,7 @@ public slots: Q_INVOKABLE void dumpTree() const; - // Register a function that will handle the given entityEventName on entityID - Q_INVOKABLE void addEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); - Q_INVOKABLE void removeEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); - signals: - void addEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); - void removeEntityEventHandler(EntityItemID entityID, QString entityEventName, QScriptValue handler); - void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e1e920da59..039ab8a19b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -404,7 +404,7 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e } } // Unregister the handlers for this eventName and entityID. -void ScriptEngine::removeEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { +void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { if (!_registeredHandlers.contains(entityID)) return; RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; QScriptValueList& handlersForEvent = handlersOnEntity[eventName]; @@ -417,7 +417,7 @@ void ScriptEngine::removeEntityEventHandler(const EntityItemID& entityID, const } } // Register the handler. -void ScriptEngine::addEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { +void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script... // Connect up ALL the handlers to the global entities object's signals. // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 0fd8ed0562..175eff059f 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "AbstractControllerScriptingInterface.h" #include "ArrayBufferClass.h" @@ -31,7 +32,6 @@ #include "ScriptCache.h" #include "ScriptUUID.h" #include "Vec3.h" -#include "../../entities/src/EntityItem.h" // FIXME const QString NO_SCRIPT(""); @@ -101,8 +101,8 @@ public: virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents); virtual void errorInLoadingScript(const QUrl& url); - void addEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); - void removeEntityEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); public slots: void loadURL(const QUrl& scriptURL); From 599886079d81549a4fa5e8af1a6182682c0c98c2 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 11:33:12 -0700 Subject: [PATCH 06/34] Restore blank line for minimum diff. --- libraries/entities/src/EntityScriptingInterface.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 1460e90a4d..b888a69a8b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -18,6 +18,7 @@ #include "ZoneEntityItem.h" #include "EntitiesLogging.h" + EntityScriptingInterface::EntityScriptingInterface() : _entityTree(NULL) { From 18d683bcedfcce37a69e5fb3dca123b1d73f2135 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 25 May 2015 12:22:38 -0700 Subject: [PATCH 07/34] Particle entities should avoid resetting simulation when setMaxParticles is called. This caused all particles to disappear when a network packet was received. --- .../entities/src/ParticleEffectEntityItem.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 0879e49f03..8a9212b6f2 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -490,19 +490,21 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) { } void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) { - _maxParticles = maxParticles; + if (_maxParticles != maxParticles) { + _maxParticles = maxParticles; - // TODO: try to do something smart here and preserve the state of existing particles. + // TODO: try to do something smart here and preserve the state of existing particles. - // resize vectors - _particleLifetimes.resize(_maxParticles); - _particlePositions.resize(_maxParticles); - _particleVelocities.resize(_maxParticles); + // resize vectors + _particleLifetimes.resize(_maxParticles); + _particlePositions.resize(_maxParticles); + _particleVelocities.resize(_maxParticles); - // effectivly clear all particles and start emitting new ones from scratch. - _particleHeadIndex = 0; - _particleTailIndex = 0; - _timeUntilNextEmit = 0.0f; + // effectivly clear all particles and start emitting new ones from scratch. + _particleHeadIndex = 0; + _particleTailIndex = 0; + _timeUntilNextEmit = 0.0f; + } } // because particles are in a ring buffer, this isn't trivial From 1b134d60d5f970494d6d88fb57a015510485d197 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 15:46:55 -0700 Subject: [PATCH 08/34] Fix typo that messed up the otherEntity for collisions. --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c5e0e0ccaa..a96c1014a2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1198,7 +1198,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons entityScriptA.property("collisionWithEntity").call(entityScriptA, args); } - emit collisionWithEntity(idB, idB, collision); + emit collisionWithEntity(idB, idA, collision); QScriptValue entityScriptB = loadEntityScript(idB); if (entityScriptB.property("collisionWithEntity").isValid()) { QScriptValueList args; From cbf4e09a102065f017cbf8e2bb23631b299dfacc Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 16:10:50 -0700 Subject: [PATCH 09/34] Remove global collision event. --- interface/src/Application.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8e23dd8f38..79ec3b79a9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2155,9 +2155,6 @@ void Application::init() { auto entityScriptingInterface = DependencyManager::get(); - connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, - entityScriptingInterface.data(), &EntityScriptingInterface::entityCollisionWithEntity); - // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, &_entities, &EntityTreeRenderer::entityCollisionWithEntity); From 069e6237cc6650a44b9027b2038af9831e9f81d8 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 16:52:14 -0700 Subject: [PATCH 10/34] Noop change with more declarations, in hopes of convincing MSVS how lambdas work. (Hey, it's worth a try...) --- libraries/script-engine/src/ScriptEngine.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 039ab8a19b..a8755c362c 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -434,16 +434,16 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& }); // Two common cases of event handler, differing only in argument signature. - auto makeSingleEntityHandler = [=](const QString& eventName) { - return [=](const EntityItemID& entityItemID) { - generalHandler(entityItemID, eventName, [=]() { + auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function { + return [=](const EntityItemID& entityItemID) -> void { + generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { return QScriptValueList() << entityItemID.toScriptValue(this); }); }; }; - auto makeMouseHandler = [=](const QString& eventName) { - return [=](const EntityItemID& entityItemID, const MouseEvent& event) { - generalHandler(entityItemID, eventName, [=]() { + auto makeMouseHandler = [=](const QString& eventName) -> std::function { + return [=](const EntityItemID& entityItemID, const MouseEvent& event) -> void { + generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); }); }; From 706c837886481bd5d1cb1689bd51096f2e5ed04e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:12:36 -0700 Subject: [PATCH 11/34] Use collisionSoundURL for billiards.js, instead of the global collision event. --- examples/example/games/billiards.js | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/examples/example/games/billiards.js b/examples/example/games/billiards.js index 5e08322c77..25ff5e7eae 100644 --- a/examples/example/games/billiards.js +++ b/examples/example/games/billiards.js @@ -33,8 +33,8 @@ var cuePosition; var startStroke = 0; // Sounds to use -hitSounds = []; -hitSounds.push(SoundCache.getSound(HIFI_PUBLIC_BUCKET + "Collisions-ballhitsandcatches/billiards/collision1.wav")); +var hitSound = HIFI_PUBLIC_BUCKET + "sounds/Collisions-ballhitsandcatches/billiards/collision1.wav"; +SoundCache.getSound(hitSound); HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var screenSize = Controller.getViewportDimensions(); @@ -127,6 +127,7 @@ function makeBalls(pos) { ignoreCollisions: false, damping: 0.50, shapeType: "sphere", + collisionSoundURL: hitSound, collisionsWillMove: true })); ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; ballNumber++; @@ -225,26 +226,11 @@ function update(deltaTime) { } } -function entityCollisionWithEntity(entity1, entity2, collision) { - /* - NOT WORKING YET - if ((entity1.id == cueBall.id) || (entity2.id == cueBall.id)) { - print("Cue ball collision!"); - //audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - //Audio.playSound(hitSounds[0], { position: Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())) }); - } - - else if (isObjectBall(entity1.id) || isObjectBall(entity2.id)) { - print("Object ball collision"); - } */ -} - tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation()))); makeTable(tableCenter); makeBalls(tableCenter); -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Script.scriptEnding.connect(cleanup); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); From 09e9d353ead27aea0bbde5db0434738d9d876a67 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:15:55 -0700 Subject: [PATCH 12/34] Deprecate globalCollisionsExample.js, and add entityCollisionExample.js --- examples/example/entityCollisionExample.js | 53 +++++++++++++++++++++ examples/example/globalCollisionsExample.js | 17 ++----- 2 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 examples/example/entityCollisionExample.js diff --git a/examples/example/entityCollisionExample.js b/examples/example/entityCollisionExample.js new file mode 100644 index 0000000000..dcbefe1378 --- /dev/null +++ b/examples/example/entityCollisionExample.js @@ -0,0 +1,53 @@ +// +// globalCollisionsExample.js +// examples +// +// Created by Brad Hefta-Gaub on 1/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that demonstrates use of the Controller class +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function someCollisionFunction(entityA, entityB, collision) { + print("collision: " + JSON.stringify({a: entityA, b: entityB, c: collision})); +} + +var position = Vec3.sum(MyAvatar.position, Quat.getFront(MyAvatar.orientation)); +var properties = { + type: "Box", + position: position, + collisionsWillMove: true, + color: { red: 200, green: 0, blue: 0 } +}; +var collider = Entities.addEntity(properties); +var armed = false; +function togglePrinting() { + print('togglePrinting from ' + armed + ' on ' + collider); + if (armed) { + Script.removeEventHandler(collider, "collisionWithEntity", someCollisionFunction); + } else { + Script.addEventHandler(collider, "collisionWithEntity", someCollisionFunction); + } + armed = !armed; + print("Red box " + (armed ? "will" : "will not") + " print on collision."); +} +togglePrinting(); + +properties.position.y += 0.2; +properties.color.blue += 200; +// A handy target for the collider to hit. +var target = Entities.addEntity(properties); + +properties.position.y += 0.2; +properties.color.green += 200; +var button = Entities.addEntity(properties); +Script.addEventHandler(button, "clickReleaseOnEntity", togglePrinting); + +Script.scriptEnding.connect(function () { + Entities.deleteEntity(collider); + Entities.deleteEntity(target); + Entities.deleteEntity(button); +}); diff --git a/examples/example/globalCollisionsExample.js b/examples/example/globalCollisionsExample.js index 5813cb2472..99fd72696d 100644 --- a/examples/example/globalCollisionsExample.js +++ b/examples/example/globalCollisionsExample.js @@ -12,17 +12,6 @@ // -print("hello..."); - - -function entityCollisionWithEntity(entityA, entityB, collision) { - print("entityCollisionWithParticle().."); - print(" entityA.getID()=" + entityA.id); - print(" entityB.getID()=" + entityB.id); - Vec3.print('penetration=', collision.penetration); - Vec3.print('contactPoint=', collision.contactPoint); -} - -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); - -print("here... hello..."); +print("The is obsolete. Please instead use:"); +print(" the collisionSoundURL property on entities, or"); +print(" entityCollisionExample.js"); From 590ad22d89135ff75a4102126c2f97971acab16b Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:16:35 -0700 Subject: [PATCH 13/34] Update spaceInvaders to use per-entity collision handlers. Also fix bitrot: * The 'f' key triggers a profile, after which all bets are off regarding time-dependent behavior such as lifetime and update(). (I'm moving space-invaders "fire" to the space bar as a work-around.) * You don't get a collision without a non-default shapeType property (if the object type is "Model") a truthy collisionsWillMove property * Entity handles (such as returned by Entity.addEntity) no longer have a separate 'id' property, so don't reference that. Just use the handle object itself. * When an entity's lifetime expires, Entities.getEntityProperties(theEntityHandle) returns default values. It looks like the space-invaders script was written at a time when this returned falsey. (I'm changing the script to see if the properties.type === 'Unknown'.) NOT FIXED is that the level of detail is cutting out when showing all the invaders. --- .../example/games/spaceInvadersExample.js | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/examples/example/games/spaceInvadersExample.js b/examples/example/games/spaceInvadersExample.js index 819cf6b774..08ad56c04d 100644 --- a/examples/example/games/spaceInvadersExample.js +++ b/examples/example/games/spaceInvadersExample.js @@ -21,6 +21,16 @@ var gameOver = false; var invaderStepsPerCycle = 120; // the number of update steps it takes then invaders to move one column to the right var invaderStepOfCycle = 0; // current iteration in the cycle var invaderMoveDirection = 1; // 1 for moving to right, -1 for moving to left +var LEFT = ","; +var RIGHT = "."; +var FIRE = "SPACE"; +var QUIT = "q"; + +print("Use:"); +print(LEFT + " to move left"); +print(RIGHT + " to move right"); +print(FIRE + " to fire"); +print(QUIT + " to quit"); // game length... var itemLifetimes = 60; // 1 minute @@ -65,8 +75,8 @@ var myShipProperties; // create the rows of space invaders var invaders = new Array(); -var numberOfRows = 5; -var invadersPerRow = 8; +var numberOfRows = 3 // FIXME 5; +var invadersPerRow = 3 // FIXME 8; var emptyColumns = 2; // number of invader width columns not filled with invaders var invadersBottomCorner = { x: gameAt.x, y: middleY , z: gameAt.z }; var rowHeight = ((gameAt.y + gameSize.y) - invadersBottomCorner.y) / numberOfRows; @@ -80,7 +90,6 @@ var stepsToGround = (middleY - gameAt.y) / yPerStep; var maxInvaderRowOffset=stepsToGround; // missile related items -var missileFired = false; var myMissile; // sounds @@ -174,6 +183,7 @@ function initializeInvaders() { invaderPosition = getInvaderPosition(row, column); invaders[row][column] = Entities.addEntity({ type: "Model", + shapeType: "box", position: invaderPosition, velocity: { x: 0, y: 0, z: 0 }, gravity: { x: 0, y: 0, z: 0 }, @@ -181,6 +191,7 @@ function initializeInvaders() { dimensions: { x: invaderSize * 2, y: invaderSize * 2, z: invaderSize * 2 }, color: { red: 255, green: 0, blue: 0 }, modelURL: invaderModels[row].modelURL, + collisionsWillMove: true, lifetime: itemLifetimes }); } @@ -264,17 +275,17 @@ Script.update.connect(update); function cleanupGame() { print("cleaning up game..."); Entities.deleteEntity(myShip); - print("cleanupGame() ... Entities.deleteEntity(myShip)... myShip.id="+myShip.id); + print("cleanupGame() ... Entities.deleteEntity(myShip)... myShip="+myShip); for (var row = 0; row < numberOfRows; row++) { for (var column = 0; column < invadersPerRow; column++) { Entities.deleteEntity(invaders[row][column]); - print("cleanupGame() ... Entities.deleteEntity(invaders[row][column])... invaders[row][column].id=" - +invaders[row][column].id); + print("cleanupGame() ... Entities.deleteEntity(invaders[row][column])... invaders[row][column]=" + +invaders[row][column]); } } // clean up our missile - if (missileFired) { + if (myMissile) { Entities.deleteEntity(myMissile); } @@ -293,15 +304,23 @@ function moveShipTo(position) { Entities.editEntity(myShip, { position: position }); } +function entityCollisionWithEntity(entityA, entityB, collision) { + print("entityCollisionWithEntity() a="+entityA + " b=" + entityB); + Vec3.print('entityCollisionWithEntity() penetration=', collision.penetration); + Vec3.print('entityCollisionWithEntity() contactPoint=', collision.contactPoint); + + deleteIfInvader(entityB); +} + function fireMissile() { // we only allow one missile at a time... var canFire = false; // If we've fired a missile, then check to see if it's still alive - if (missileFired) { + if (myMissile) { var missileProperties = Entities.getEntityProperties(myMissile); - if (!missileProperties) { + if (!missileProperties || (missileProperties.type === 'Unknown')) { print("canFire = true"); canFire = true; } @@ -322,11 +341,12 @@ function fireMissile() { velocity: { x: 0, y: 5, z: 0}, gravity: { x: 0, y: 0, z: 0 }, damping: 0, - dimensions: { x: missileSize * 2, y: missileSize * 2, z: missileSize * 2 }, + collisionsWillMove: true, + dimensions: { x: missileSize, y: missileSize, z: missileSize }, color: { red: 0, green: 0, blue: 255 }, lifetime: 5 }); - + Script.addEventHandler(myMissile, "collisionWithEntity", entityCollisionWithEntity); var options = {} if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, @@ -335,30 +355,30 @@ function fireMissile() { } else { options.position = missilePosition; } - + Audio.playSound(shootSound, options); - missileFired = true; } } function keyPressEvent(key) { //print("keyPressEvent key.text="+key.text); - if (key.text == ",") { + + if (key.text == LEFT) { myShipProperties.position.x -= 0.1; if (myShipProperties.position.x < gameAt.x) { myShipProperties.position.x = gameAt.x; } moveShipTo(myShipProperties.position); - } else if (key.text == ".") { + } else if (key.text == RIGHT) { myShipProperties.position.x += 0.1; if (myShipProperties.position.x > gameAt.x + gameSize.x) { myShipProperties.position.x = gameAt.x + gameSize.x; } moveShipTo(myShipProperties.position); - } else if (key.text == "f") { + } else if (key.text == FIRE) { fireMissile(); - } else if (key.text == "q") { + } else if (key.text == QUIT) { endGame(); } } @@ -370,7 +390,7 @@ Controller.captureKeyEvents({text: " "}); function deleteIfInvader(possibleInvaderEntity) { for (var row = 0; row < numberOfRows; row++) { for (var column = 0; column < invadersPerRow; column++) { - if (invaders[row][column].id && invaders[row][column].id == possibleInvaderEntity.id) { + if (invaders[row][column] == possibleInvaderEntity) { Entities.deleteEntity(possibleInvaderEntity); Entities.deleteEntity(myMissile); @@ -390,20 +410,6 @@ function deleteIfInvader(possibleInvaderEntity) { } } -function entityCollisionWithEntity(entityA, entityB, collision) { - print("entityCollisionWithEntity() a.id="+entityA.id + " b.id=" + entityB.id); - Vec3.print('entityCollisionWithEntity() penetration=', collision.penetration); - Vec3.print('entityCollisionWithEntity() contactPoint=', collision.contactPoint); - if (missileFired) { - if (myMissile.id == entityA.id) { - deleteIfInvader(entityB); - } else if (myMissile.id == entityB.id) { - deleteIfInvader(entityA); - } - } -} -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); - // initialize the game... initializeMyShip(); From 050829e442dbdd84776df090b9c1d415f7b338f4 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:23:22 -0700 Subject: [PATCH 14/34] typo --- examples/example/globalCollisionsExample.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example/globalCollisionsExample.js b/examples/example/globalCollisionsExample.js index 99fd72696d..624ad43219 100644 --- a/examples/example/globalCollisionsExample.js +++ b/examples/example/globalCollisionsExample.js @@ -12,6 +12,6 @@ // -print("The is obsolete. Please instead use:"); +print("The global collision event is obsolete. Please instead use:"); print(" the collisionSoundURL property on entities, or"); print(" entityCollisionExample.js"); From d214f128cd5061c4c70a26b57b89f50501faaa09 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:39:15 -0700 Subject: [PATCH 15/34] Miniumum change for gun to use per-entity collisions. I have *not* updated this script for other recent changes (e.g., to entityIDs). --- examples/controllers/hydra/gun.js | 43 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/examples/controllers/hydra/gun.js b/examples/controllers/hydra/gun.js index 7dd2b5974f..146f9daca3 100644 --- a/examples/controllers/hydra/gun.js +++ b/examples/controllers/hydra/gun.js @@ -153,6 +153,26 @@ if (showScore) { var BULLET_VELOCITY = 10.0; +function entityCollisionWithEntity(entity1, entity2, collision) { + if (entity2 === targetID) { + score++; + if (showScore) { + Overlays.editOverlay(text, { text: "Score: " + score } ); + } + + // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! + Script.setTimeout(deleteBulletAndTarget, 500); + + // Turn the target and the bullet white + Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); + Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); + + // play the sound near the camera so the shooter can hear it + audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + Audio.playSound(targetHitSound, audioOptions); + } +} + function shootBullet(position, velocity, grenade) { var BULLET_SIZE = 0.10; var BULLET_LIFETIME = 10.0; @@ -178,6 +198,7 @@ function shootBullet(position, velocity, grenade) { ignoreCollisions: false, collisionsWillMove: true }); + Script.addEventHandler(bulletID, "collisionWithEntity", entityCollisionWithEntity); // Play firing sounds audioOptions.position = position; @@ -310,27 +331,6 @@ function makePlatform(gravity, scale, size) { } -function entityCollisionWithEntity(entity1, entity2, collision) { - if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) && - ((entity2.id == bulletID.id) || (entity2.id == targetID.id))) { - score++; - if (showScore) { - Overlays.editOverlay(text, { text: "Score: " + score } ); - } - - // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! - Script.setTimeout(deleteBulletAndTarget, 500); - - // Turn the target and the bullet white - Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); - Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); - - // play the sound near the camera so the shooter can hear it - audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - Audio.playSound(targetHitSound, audioOptions); - } -} - function keyPressEvent(event) { // if our tools are off, then don't do anything if (event.text == "t") { @@ -505,7 +505,6 @@ function scriptEnding() { clearPose(); } -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); From bbdf9f1d9f81fbec7608d24552c03310812fb514 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 26 May 2015 11:14:23 -0700 Subject: [PATCH 16/34] Remove changingEntityID. --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 9 --------- libraries/entities-renderer/src/EntityTreeRenderer.h | 1 - libraries/entities/src/EntityScriptingInterface.cpp | 2 -- libraries/entities/src/EntityScriptingInterface.h | 1 - libraries/entities/src/EntityTree.h | 1 - libraries/script-engine/src/ScriptEngine.cpp | 6 ------ 6 files changed, 20 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index a96c1014a2..5dbe5a782d 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -113,7 +113,6 @@ void EntityTreeRenderer::init() { connect(entityTree, &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity); connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::addingEntity); connect(entityTree, &EntityTree::entityScriptChanging, this, &EntityTreeRenderer::entitySciptChanging); - connect(entityTree, &EntityTree::changingEntityID, this, &EntityTreeRenderer::changingEntityID); } void EntityTreeRenderer::shutdown() { @@ -1102,14 +1101,6 @@ void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) { } -void EntityTreeRenderer::changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID) { - if (_entityScripts.contains(oldEntityID)) { - EntityScriptDetails details = _entityScripts[oldEntityID]; - _entityScripts.remove(oldEntityID); - _entityScripts[newEntityID] = details; - } -} - void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision) { EntityItem* entity = entityTree->findEntityByEntityItemID(id); if (!entity) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 1f03e01e7c..909aaae209 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -112,7 +112,6 @@ signals: public slots: void addingEntity(const EntityItemID& entityID); void deletingEntity(const EntityItemID& entityID); - void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); void entitySciptChanging(const EntityItemID& entityID); void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index b888a69a8b..351bbc3643 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -46,7 +46,6 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { if (_entityTree) { disconnect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); disconnect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); - disconnect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); disconnect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } @@ -55,7 +54,6 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { if (_entityTree) { connect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); connect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); - connect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); connect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index ca6e266e98..d60a63ffd0 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -143,7 +143,6 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); - void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); void clearingEntities(); private: diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 43cd8780ab..3168495704 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -171,7 +171,6 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); void entityScriptChanging(const EntityItemID& entityItemID); - void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); void clearingEntities(); private: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a8755c362c..0dc7db758f 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -426,12 +426,6 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& [=](const EntityItemID& entityID) { _registeredHandlers.remove(entityID); }); - connect(entities.data(), &EntityScriptingInterface::changingEntityID, this, - [=](const EntityItemID& oldEntityID, const EntityItemID& newEntityID) { - if (!_registeredHandlers.contains(oldEntityID)) return; - _registeredHandlers[newEntityID] = _registeredHandlers[oldEntityID]; - _registeredHandlers.remove(oldEntityID); - }); // Two common cases of event handler, differing only in argument signature. auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function { From 5608892a159d2818095f6f9f745996511a3033a2 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 26 May 2015 11:16:11 -0700 Subject: [PATCH 17/34] Remove obsolete comment. --- libraries/script-engine/src/ScriptEngine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 0dc7db758f..e93c55c4aa 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -394,7 +394,6 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e if (!_registeredHandlers.contains(entityID)) return; const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; if (!handlersOnEntity.contains(eventName)) return; - // FIXME: Need one more level of indirection. We need to allow multiple handlers per event, registered by different scripts. QScriptValueList handlersForEvent = handlersOnEntity[eventName]; if (!handlersForEvent.isEmpty()) { QScriptValueList args = argGenerator(); From 3a7a290094cc82d095b3d7147e7fd2b42130df77 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 26 May 2015 11:38:36 -0700 Subject: [PATCH 18/34] Uglify conditional returns. :-) --- libraries/script-engine/src/ScriptEngine.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e93c55c4aa..91a4e3c397 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -391,9 +391,13 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator) { - if (!_registeredHandlers.contains(entityID)) return; + if (!_registeredHandlers.contains(entityID)) { + return; + } const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; - if (!handlersOnEntity.contains(eventName)) return; + if (!handlersOnEntity.contains(eventName)) { + return; + } QScriptValueList handlersForEvent = handlersOnEntity[eventName]; if (!handlersForEvent.isEmpty()) { QScriptValueList args = argGenerator(); @@ -404,7 +408,9 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e } // Unregister the handlers for this eventName and entityID. void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { - if (!_registeredHandlers.contains(entityID)) return; + if (!_registeredHandlers.contains(entityID)) { + return; + } RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; QScriptValueList& handlersForEvent = handlersOnEntity[eventName]; // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate. From ae73e68f7bdf0282bbf2eda322da8492efb25942 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 26 May 2015 13:27:37 -0700 Subject: [PATCH 19/34] Update header. --- examples/example/entityCollisionExample.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/example/entityCollisionExample.js b/examples/example/entityCollisionExample.js index dcbefe1378..de50d52753 100644 --- a/examples/example/entityCollisionExample.js +++ b/examples/example/entityCollisionExample.js @@ -1,11 +1,11 @@ // -// globalCollisionsExample.js +// entityCollisionExample.js // examples // -// Created by Brad Hefta-Gaub on 1/29/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Howard Stearns on 5/25/15. +// Copyright 2015 High Fidelity, Inc. // -// This is an example script that demonstrates use of the Controller class +// This is an example script that demonstrates use of the per-entity event handlers. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -20,7 +20,7 @@ var properties = { type: "Box", position: position, collisionsWillMove: true, - color: { red: 200, green: 0, blue: 0 } + color: { red: 200, green: 0, blue: 0 } }; var collider = Entities.addEntity(properties); var armed = false; From 7194d4a4daf63fb494601792cfba3a24dfff82bd Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Tue, 26 May 2015 16:40:47 -0700 Subject: [PATCH 20/34] modified pointer script to allow users to draw on surfaces --- examples/pointer.js | 103 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/examples/pointer.js b/examples/pointer.js index cdfb93f2d3..ddaf84fcde 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -1,9 +1,50 @@ +//Dimensions property for lines is the offset of the position + var lineEntityID = null; var lineIsRezzed = false; var altHeld = false; var lineCreated = false; +var position, positionOffset, prevPosition; +var nearLinePosition; +var strokes = []; +var STROKE_ADJUST = 0.005; +var DISTANCE_DRAW_THRESHOLD = .03; +var drawDistance = 0; -function nearLinePoint(targetPosition) { +var userCanDraw = true; + +var BUTTON_SIZE = 32; +var PADDING = 3; + +var buttonOffColor = {red: 250, green: 10, blue: 10}; +var buttonOnColor = {red: 10, green: 200, blue: 100}; + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var screenSize = Controller.getViewportDimensions(); + +var drawButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/pencil.png?v2", + color: buttonOnColor, + alpha: 1 +}); + + + + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(2.0, Quat.getFront(Camera.getOrientation()))); +center.y += 0.5; +var whiteBoard = Entities.addEntity({ + type: "Box", + position: center, + dimensions: {x: 1, y: 1, z: .001}, + color: {red: 255, green: 255, blue: 255} +}); + +function calculateNearLinePosition(targetPosition) { var handPosition = MyAvatar.getRightPalmPosition(); var along = Vec3.subtract(targetPosition, handPosition); along = Vec3.normalize(along); @@ -27,19 +68,27 @@ function createOrUpdateLine(event) { var props = Entities.getEntityProperties(intersection.entityID); if (intersection.intersects) { - var dim = Vec3.subtract(intersection.intersection, nearLinePoint(intersection.intersection)); + startPosition = intersection.intersection; + var subtractVec = Vec3.multiply(Vec3.normalize(pickRay.direction), STROKE_ADJUST); + startPosition = Vec3.subtract(startPosition, subtractVec); + nearLinePosition = calculateNearLinePosition(intersection.intersection); + positionOffset= Vec3.subtract(startPosition, nearLinePosition); if (lineIsRezzed) { Entities.editEntity(lineEntityID, { - position: nearLinePoint(intersection.intersection), - dimensions: dim, + position: nearLinePosition, + dimensions: positionOffset, lifetime: 15 + props.lifespan // renew lifetime }); + if(userCanDraw){ + draw(); + } } else { lineIsRezzed = true; + prevPosition = startPosition; lineEntityID = Entities.addEntity({ type: "Line", - position: nearLinePoint(intersection.intersection), - dimensions: dim, + position: nearLinePosition, + dimensions: positionOffset, color: { red: 255, green: 255, @@ -53,8 +102,39 @@ function createOrUpdateLine(event) { } } +function draw(){ + + //We only want to draw line if distance between starting and previous point is large enough + drawDistance = Vec3.distance(startPosition, prevPosition); + if( drawDistance < DISTANCE_DRAW_THRESHOLD){ + return; + } + + var offset = Vec3.subtract(startPosition, prevPosition); + strokes.push(Entities.addEntity({ + type: "Line", + position: prevPosition, + dimensions: offset, + color: {red: 200, green: 40, blue: 200}, + // lifetime: 20 + })); + prevPosition = startPosition; +} function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == drawButton) { + userCanDraw = !userCanDraw; + if(userCanDraw === true){ + Overlays.editOverlay(drawButton, {color: buttonOnColor}); + } else { + Overlays.editOverlay(drawButton, {color: buttonOffColor}); + } + } + if (!event.isLeftButton || altHeld) { return; } @@ -69,6 +149,7 @@ function mouseMoveEvent(event) { } + function mouseReleaseEvent(event) { if (!lineCreated) { return; @@ -91,7 +172,17 @@ function keyReleaseEvent(event) { } +function cleanup(){ + Entities.deleteEntity(whiteBoard); + for(var i =0; i < strokes.length; i++){ + Entities.deleteEntity(strokes[i]); + } + Overlays.deleteOverlay(drawButton); +} + + +Script.scriptEnding.connect(cleanup); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); From 579c62420d09e649c6963f23547dca42d742a4fd Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Tue, 26 May 2015 16:43:34 -0700 Subject: [PATCH 21/34] added header to pointer script --- examples/pointer.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/pointer.js b/examples/pointer.js index ddaf84fcde..9ca20504b4 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -1,4 +1,14 @@ -//Dimensions property for lines is the offset of the position +// pointer.js +// examples +// +// Created by Eric Levin on May 26, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Provides a pointer with option to draw on surfaces +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var lineEntityID = null; var lineIsRezzed = false; From 3fb5476968c38f3bfd6a58b9d4f1dfa87152a6ad Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Tue, 26 May 2015 16:46:01 -0700 Subject: [PATCH 22/34] added blockWorld script to generate floor of tiles and drop jenga blocks at random points above floor --- examples/blockWorld.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/blockWorld.js diff --git a/examples/blockWorld.js b/examples/blockWorld.js new file mode 100644 index 0000000000..e69de29bb2 From efbb9b0238dfe5479b2a4fb49b4cdc09331aa06b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 26 May 2015 22:48:45 -0700 Subject: [PATCH 23/34] rewritten MassProperties with complete unit tests --- libraries/physics/src/MeshInfo.cpp | 150 ------ libraries/physics/src/MeshInfo.h | 33 -- libraries/physics/src/MeshMassProperties.cpp | 321 +++++++++++++ libraries/physics/src/MeshMassProperties.h | 60 +++ tests/physics/src/MeshInfoTests.cpp | 237 --------- tests/physics/src/MeshInfoTests.h | 21 - tests/physics/src/MeshMassPropertiesTests.cpp | 452 ++++++++++++++++++ tests/physics/src/MeshMassPropertiesTests.h | 22 + tests/physics/src/main.cpp | 4 +- 9 files changed, 857 insertions(+), 443 deletions(-) delete mode 100644 libraries/physics/src/MeshInfo.cpp delete mode 100644 libraries/physics/src/MeshInfo.h create mode 100644 libraries/physics/src/MeshMassProperties.cpp create mode 100644 libraries/physics/src/MeshMassProperties.h delete mode 100644 tests/physics/src/MeshInfoTests.cpp delete mode 100644 tests/physics/src/MeshInfoTests.h create mode 100644 tests/physics/src/MeshMassPropertiesTests.cpp create mode 100644 tests/physics/src/MeshMassPropertiesTests.h diff --git a/libraries/physics/src/MeshInfo.cpp b/libraries/physics/src/MeshInfo.cpp deleted file mode 100644 index 29ddc74a98..0000000000 --- a/libraries/physics/src/MeshInfo.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// -// MeshInfo.cpp -// libraries/physics/src -// -// Created by Virendra Singh 2015.02.28 -// Copyright 2014 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 "MeshInfo.h" -#include -using namespace meshinfo; - -//class to compute volume, mass, center of mass, and inertia tensor of a mesh. -//origin is the default reference point for generating the tetrahedron from each triangle of the mesh. - -MeshInfo::MeshInfo(vector *vertices, vector *triangles) :\ - _vertices(vertices), - _centerOfMass(Vertex(0.0, 0.0, 0.0)), - _triangles(triangles) -{ - -} - -MeshInfo::~MeshInfo(){ - - _vertices = NULL; - _triangles = NULL; - -} - -inline float MeshInfo::getVolume(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const{ - glm::mat4 tet = { glm::vec4(p1.x, p2.x, p3.x, p4.x), glm::vec4(p1.y, p2.y, p3.y, p4.y), glm::vec4(p1.z, p2.z, p3.z, p4.z), - glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; - return glm::determinant(tet) / 6.0f; -} - -Vertex MeshInfo::getMeshCentroid() const{ - return _centerOfMass; -} - -vector MeshInfo::computeMassProperties(){ - vector volumeAndInertia = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; - Vertex origin(0.0, 0.0, 0.0); - glm::mat3 identity; - float meshVolume = 0.0f; - glm::mat3 globalInertiaTensors(0.0); - - for (unsigned int i = 0; i < _triangles->size(); i += 3){ - Vertex p1 = _vertices->at(_triangles->at(i)); - Vertex p2 = _vertices->at(_triangles->at(i + 1)); - Vertex p3 = _vertices->at(_triangles->at(i + 2)); - float volume = getVolume(p1, p2, p3, origin); - Vertex com = 0.25f * (p1 + p2 + p3); - meshVolume += volume; - _centerOfMass += com * volume; - - //centroid is used for calculating inertia tensor relative to center of mass. - // translate the tetrahedron to its center of mass using P = P - centroid - Vertex p0 = origin - com; - p1 = p1 - com; - p2 = p2 - com; - p3 = p3 - com; - - //Calculate inertia tensor based on Tonon's Formulae given in the paper mentioned below. - //http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf - //Explicit exact formulas for the 3-D tetrahedron inertia tensor in terms of its vertex coordinates - F.Tonon - - float i11 = (volume * 0.1f) * ( - p0.y*p0.y + p0.y*p1.y + p0.y*p2.y + p0.y*p3.y + - p1.y*p1.y + p1.y*p2.y + p1.y*p3.y + - p2.y*p2.y + p2.y*p3.y + - p3.y*p3.y + - p0.z*p0.z + p0.z*p1.z + p0.z*p2.z + p0.z*p3.z + - p1.z*p1.z + p1.z*p2.z + p1.z*p3.z + - p2.z*p2.z + p2.z*p3.z + - p3.z*p3.z); - - float i22 = (volume * 0.1f) * ( - p0.x*p0.x + p0.x*p1.x + p0.x*p2.x + p0.x*p3.x + - p1.x*p1.x + p1.x*p2.x + p1.x*p3.x + - p2.x*p2.x + p2.x*p3.x + - p3.x*p3.x + - p0.z*p0.z + p0.z*p1.z + p0.z*p2.z + p0.z*p3.z + - p1.z*p1.z + p1.z*p2.z + p1.z*p3.z + - p2.z*p2.z + p2.z*p3.z + - p3.z*p3.z); - - float i33 = (volume * 0.1f) * ( - p0.x*p0.x + p0.x*p1.x + p0.x*p2.x + p0.x*p3.x + - p1.x*p1.x + p1.x*p2.x + p1.x*p3.x + - p2.x*p2.x + p2.x*p3.x + - p3.x*p3.x + - p0.y*p0.y + p0.y*p1.y + p0.y*p2.y + p0.y*p3.y + - p1.y*p1.y + p1.y*p2.y + p1.y*p3.y + - p2.y*p2.y + p2.y*p3.y + - p3.y*p3.y); - - float i23 = -(volume * 0.05f) * (2.0f * (p0.y*p0.z + p1.y*p1.z + p2.y*p2.z + p3.y*p3.z) + - p0.y*p1.z + p0.y*p2.z + p0.y*p3.z + - p1.y*p0.z + p1.y*p2.z + p1.y*p3.z + - p2.y*p0.z + p2.y*p1.z + p2.y*p3.z + - p3.y*p0.z + p3.y*p1.z + p3.y*p2.z); - - float i21 = -(volume * 0.05f) * (2.0f * (p0.x*p0.z + p1.x*p1.z + p2.x*p2.z + p3.x*p3.z) + - p0.x*p1.z + p0.x*p2.z + p0.x*p3.z + - p1.x*p0.z + p1.x*p2.z + p1.x*p3.z + - p2.x*p0.z + p2.x*p1.z + p2.x*p3.z + - p3.x*p0.z + p3.x*p1.z + p3.x*p2.z); - - float i31 = -(volume * 0.05f) * (2.0f * (p0.x*p0.y + p1.x*p1.y + p2.x*p2.y + p3.x*p3.y) + - p0.x*p1.y + p0.x*p2.y + p0.x*p3.y + - p1.x*p0.y + p1.x*p2.y + p1.x*p3.y + - p2.x*p0.y + p2.x*p1.y + p2.x*p3.y + - p3.x*p0.y + p3.x*p1.y + p3.x*p2.y); - - //3x3 of local inertia tensors of each tetrahedron. Inertia tensors are the diagonal elements - // | I11 -I12 -I13 | - // I = | -I21 I22 -I23 | - // | -I31 -I32 I33 | - glm::mat3 localInertiaTensors = { Vertex(i11, i21, i31), Vertex(i21, i22, i23), - Vertex(i31, i23, i33) }; - - //Translate the inertia tensors from center of mass to origin - //Parallel axis theorem J = I * m[(R.R)*Identity - RxR] where x is outer cross product - globalInertiaTensors += localInertiaTensors + volume * ((glm::dot(com, com) * identity) - - glm::outerProduct(com, com)); - } - - //Translate accumulated center of mass from each tetrahedron to mesh's center of mass using parallel axis theorem - if (meshVolume == 0){ - return volumeAndInertia; - } - _centerOfMass = (_centerOfMass / meshVolume); - - //Translate the inertia tensors from origin to mesh's center of mass. - globalInertiaTensors = globalInertiaTensors - meshVolume * ((glm::dot(_centerOfMass, _centerOfMass) * - identity) - glm::outerProduct(_centerOfMass, _centerOfMass)); - - volumeAndInertia[0] = meshVolume; - volumeAndInertia[1] = globalInertiaTensors[0][0]; //i11 - volumeAndInertia[2] = globalInertiaTensors[1][1]; //i22 - volumeAndInertia[3] = globalInertiaTensors[2][2]; //i33 - volumeAndInertia[4] = -globalInertiaTensors[2][1]; //i23 or i32 - volumeAndInertia[5] = -globalInertiaTensors[1][0]; //i21 or i12 - volumeAndInertia[6] = -globalInertiaTensors[2][0]; //i13 or i31 - return volumeAndInertia; -} \ No newline at end of file diff --git a/libraries/physics/src/MeshInfo.h b/libraries/physics/src/MeshInfo.h deleted file mode 100644 index 67dbc8b0d6..0000000000 --- a/libraries/physics/src/MeshInfo.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// MeshInfo.h -// libraries/physics/src -// -// Created by Virendra Singh 2015.02.28 -// Copyright 2014 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_MeshInfo_h -#define hifi_MeshInfo_h -#include -#include -#include -using namespace std; -namespace meshinfo{ - typedef glm::vec3 Vertex; - class MeshInfo{ - private: - inline float getVolume(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const; - vector computeVolumeAndInertia(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const; - public: - vector *_vertices; - Vertex _centerOfMass; - vector *_triangles; - MeshInfo(vector *vertices, vector *triangles); - ~MeshInfo(); - Vertex getMeshCentroid() const; - vector computeMassProperties(); - }; -} -#endif // hifi_MeshInfo_h \ No newline at end of file diff --git a/libraries/physics/src/MeshMassProperties.cpp b/libraries/physics/src/MeshMassProperties.cpp new file mode 100644 index 0000000000..e5d4f4e5af --- /dev/null +++ b/libraries/physics/src/MeshMassProperties.cpp @@ -0,0 +1,321 @@ +// +// MeshMassProperties.cpp +// libraries/physics/src +// +// Created by Andrew Meadows 2015.05.25 +// 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 +#include + +#include "MeshMassProperties.h" + +// this method is included for unit test verification +void computeBoxInertia(btScalar mass, const btVector3& diagonal, btMatrix3x3& inertia) { + // formula for box inertia tensor: + // + // | y^2 + z^2 0 0 | + // | | + // inertia = M/12 * | 0 z^2 + x^2 0 | + // | | + // | 0 0 x^2 + y^2 | + // + + mass = mass / btScalar(12.0f); + btScalar x = diagonal[0]; + x = mass * x * x; + btScalar y = diagonal[1]; + y = mass * y * y; + btScalar z = diagonal[2]; + z = mass * z * z; + inertia.setIdentity(); + inertia[0][0] = y + z; + inertia[1][1] = z + x; + inertia[2][2] = x + y; +} + +void computeTetrahedronInertia(btScalar mass, btVector3* points, btMatrix3x3& inertia) { + // Computes the inertia tensor of a tetrahedron about its center of mass. + // The tetrahedron is defined by array of four points in its center of mass frame. + // + // The analytic formulas were obtained from Tonon's paper: + // http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf + // http://thescipub.com/PDF/jmssp.2005.8.11.pdf + // + // The inertia tensor has the following form: + // + // | a f e | + // | | + // inertia = | f b d | + // | | + // | e d c | + + const btVector3& p0 = points[0]; + const btVector3& p1 = points[1]; + const btVector3& p2 = points[2]; + const btVector3& p3 = points[3]; + + for (uint32_t i = 0; i < 3; ++i ) { + uint32_t j = (i + 1) % 3; + uint32_t k = (j + 1) % 3; + + // compute diagonal + inertia[i][i] = mass * btScalar(0.1f) * + ( p0[j] * (p0[j] + p1[j] + p2[j] + p3[j]) + + p1[j] * (p1[j] + p2[j] + p3[j]) + + p2[j] * (p2[j] + p3[j]) + + p3[j] * p3[j] + + p0[k] * (p0[k] + p1[k] + p2[k] + p3[k]) + + p1[k] * (p1[k] + p2[k] + p3[k]) + + p2[k] * (p2[k] + p3[k]) + + p3[k] * p3[k] ); + + // compute off-diagonals + inertia[j][k] = inertia[k][j] = - mass * btScalar(0.05f) * + ( btScalar(2.0f) * ( p0[j] * p0[k] + p1[j] * p1[k] + p2[j] * p2[k] + p3[j] * p3[k] ) + + p0[j] * (p1[k] + p2[k] + p3[k]) + + p1[j] * (p0[k] + p2[k] + p3[k]) + + p2[j] * (p0[k] + p1[k] + p3[k]) + + p3[j] * (p0[k] + p1[k] + p2[k]) ); + } +} + +// helper function +void computePointInertia(const btVector3& point, btScalar mass, btMatrix3x3& inertia) { + btScalar distanceSquared = point.length2(); + if (distanceSquared > 0.0f) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar pointi = point[i]; + inertia[i][i] = mass * (distanceSquared - (pointi * pointi)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = - mass * pointi * point[j]; + inertia[i][j] = offDiagonal; + inertia[j][i] = offDiagonal; + } + } + } +} + +// this method is included for unit test verification +void computeTetrahedronInertiaByBruteForce(btVector3* points, btMatrix3x3& inertia) { + // Computes the approximate inertia tensor of a tetrahedron (about frame's origin) + // by integration over the "point" masses. This is numerically expensive so it may + // take a while to complete. + + VectorOfIndices triangles = { + 0, 2, 1, + 0, 3, 2, + 0, 1, 3, + 1, 2, 3 }; + + for (int i = 0; i < 3; ++i) { + inertia[i].setZero(); + } + + // compute normals + btVector3 center = btScalar(0.25f) * (points[0] + points[1] + points[2] + points[3]); + btVector3 normals[4]; + btVector3 pointsOnPlane[4]; + for (int i = 0; i < 4; ++i) { + int t = 3 * i; + btVector3& p0 = points[triangles[t]]; + btVector3& p1 = points[triangles[t + 1]]; + btVector3& p2 = points[triangles[t + 2]]; + normals[i] = ((p1 - p0).cross(p2 - p1)).normalized(); + // make sure normal points away from center + if (normals[i].dot(p0 - center) < btScalar(0.0f)) { + normals[i] *= btScalar(-1.0f); + } + pointsOnPlane[i] = p0; + } + + // compute bounds of integration + btVector3 boxMax = points[0]; + btVector3 boxMin = points[0]; + for (int i = 1; i < 4; ++i) { + for(int j = 0; j < 3; ++j) { + if (points[i][j] > boxMax[j]) { + boxMax[j] = points[i][j]; + } + if (points[i][j] < boxMin[j]) { + boxMin[j] = points[i][j]; + } + } + } + + // compute step size + btVector3 diagonal = boxMax - boxMin; + btScalar maxDimension = diagonal[0]; + if (diagonal[1] > maxDimension) { + maxDimension = diagonal[1]; + } + if (diagonal[2] > maxDimension) { + maxDimension = diagonal[2]; + } + btScalar resolutionOfIntegration = btScalar(400.0f); + btScalar delta = maxDimension / resolutionOfIntegration; + btScalar deltaVolume = delta * delta * delta; + + // integrate over three dimensions + btMatrix3x3 deltaInertia; + btScalar XX = boxMax[0]; + btScalar YY = boxMax[1]; + btScalar ZZ = boxMax[2]; + btScalar x = boxMin[0]; + while(x < XX) { + btScalar y = boxMin[1]; + while (y < YY) { + btScalar z = boxMin[2]; + while (z < ZZ) { + btVector3 p(x, y, z); + // the point is inside the shape if it is behind all face planes + bool pointInside = true; + for (int i = 0; i < 4; ++i) { + if ((p - pointsOnPlane[i]).dot(normals[i]) > btScalar(0.0f)) { + pointInside = false; + break; + } + } + if (pointInside) { + // this point contributes to the total + computePointInertia(p, deltaVolume, deltaInertia); + inertia += deltaInertia; + } + z += delta; + } + y += delta; + } + x += delta; + } +} + +btScalar computeTetrahedronVolume(btVector3* points) { + // Assumes triangle {1, 2, 3} is wound according to the right-hand-rule. + // NOTE: volume may be negative, in which case the tetrahedron contributes negatively to totals + // volume = (face_area * face_normal).dot(face_to_far_point) / 3.0 + // (face_area * face_normal) = side0.cross(side1) / 2.0 + return ((points[2] - points[1]).cross(points[3] - points[2])).dot(points[3] - points[0]) / btScalar(6.0f); +} + +void applyParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { + // Parallel Axis Theorem says: + // + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // + // where R*R = inside product + // R(x)R = outside product + // E = identity matrix + + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar shifti = shift[i]; + inertia[i][i] += mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = mass * shifti * shift[j]; + inertia[i][j] -= offDiagonal; + inertia[j][i] -= offDiagonal; + } + } + } +} + +// helper function +void applyInverseParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { + // Parallel Axis Theorem says: + // + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // + // So the inverse would be: + // + // Icm = Ishifted - M * [ (R*R)E - R(x)R ] + + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar shifti = shift[i]; + inertia[i][i] -= mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = mass * shifti * shift[j]; + inertia[i][j] += offDiagonal; + inertia[j][i] += offDiagonal; + } + } + } +} + +MeshMassProperties::MeshMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices) { + computeMassProperties(points, triangleIndices); +} + +void MeshMassProperties::computeMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices) { + // We process the mesh one triangle at a time. Each triangle defines a tetrahedron + // relative to some local point p0 (which we chose to be the local origin for convenience). + // Each tetrahedron contributes to the three totals: volume, centerOfMass, and inertiaTensor. + // + // We assume the mesh triangles are wound using the right-hand-rule, such that the + // triangle's points circle counter-clockwise about its face normal. + // + + // initialize the totals + m_volume = btScalar(0.0f); + btVector3 weightedCenter; + weightedCenter.setZero(); + for (uint32_t i = 0; i < 3; ++i) { + m_inertia[i].setZero(); + } + + // create some variables to hold temporary results + uint32_t numPoints = points.size(); + const btVector3 p0(0.0f, 0.0f, 0.0f); + btMatrix3x3 tetraInertia; + btMatrix3x3 doubleDebugInertia; + btVector3 tetraPoints[4]; + btVector3 center; + + // loop over triangles + uint32_t numTriangles = triangleIndices.size() / 3; + for (uint32_t i = 0; i < numTriangles; ++i) { + uint32_t t = 3 * i; + assert(triangleIndices[t] < numPoints); + assert(triangleIndices[t + 1] < numPoints); + assert(triangleIndices[t + 2] < numPoints); + + // extract raw vertices + tetraPoints[0] = p0; + tetraPoints[1] = points[triangleIndices[t]]; + tetraPoints[2] = points[triangleIndices[t + 1]]; + tetraPoints[3] = points[triangleIndices[t + 2]]; + + // compute volume + btScalar volume = computeTetrahedronVolume(tetraPoints); + + // compute center + // NOTE: since tetraPoints[0] is the origin, we don't include it in the sum + center = btScalar(0.25f) * (tetraPoints[1] + tetraPoints[2] + tetraPoints[3]); + + // shift vertices so that center of mass is at origin + tetraPoints[0] -= center; + tetraPoints[1] -= center; + tetraPoints[2] -= center; + tetraPoints[3] -= center; + + // compute inertia tensor then shift it to origin-frame + computeTetrahedronInertia(volume, tetraPoints, tetraInertia); + applyParallelAxisTheorem(tetraInertia, center, volume); + + // tally results + weightedCenter += volume * center; + m_volume += volume; + m_inertia += tetraInertia; + } + + m_centerOfMass = weightedCenter / m_volume; + + applyInverseParallelAxisTheorem(m_inertia, m_centerOfMass, m_volume); +} + diff --git a/libraries/physics/src/MeshMassProperties.h b/libraries/physics/src/MeshMassProperties.h new file mode 100644 index 0000000000..a500cb28e8 --- /dev/null +++ b/libraries/physics/src/MeshMassProperties.h @@ -0,0 +1,60 @@ +// +// MeshMassProperties.h +// libraries/physics/src +// +// Created by Andrew Meadows 2015.05.25 +// 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_MeshMassProperties_h +#define hifi_MeshMassProperties_h + +#include + +#include + +typedef std::vector VectorOfPoints; +typedef std::vector VectorOfIndices; + +#define EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +#ifdef EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +void computeBoxInertia(btScalar mass, const btVector3& diagonal, btMatrix3x3& I); + +// mass = input mass of tetrahedron +// points = input array of four points with center of mass at origin +// I = output inertia of tetrahedron about its center of mass +void computeTetrahedronInertia(btScalar mass, btVector3* points, btMatrix3x3& I); +void computeTetrahedronInertiaByBruteForce(btVector3* points, btMatrix3x3& I); + +btScalar computeTetrahedronVolume(btVector3* points); + +void applyParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass); +#endif // EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST + +// Given a closed mesh with right-hand triangles a MeshMassProperties instance will compute +// its mass properties: +// +// volume +// center-of-mass +// normalized interia tensor about center of mass +// +class MeshMassProperties { +public: + + // the mass properties calculation is done in the constructor, so if the mesh is complex + // then the construction could be computationally expensiuve. + MeshMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices); + + // compute the mass properties of a new mesh + void computeMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices); + + // harveste the mass properties from these public data members + btScalar m_volume = 1.0f; + btVector3 m_centerOfMass = btVector3(0.0f, 0.0f, 0.0f); + btMatrix3x3 m_inertia = btMatrix3x3(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); +}; + +#endif // _hifi_MeshMassProperties_h diff --git a/tests/physics/src/MeshInfoTests.cpp b/tests/physics/src/MeshInfoTests.cpp deleted file mode 100644 index 221ffa117c..0000000000 --- a/tests/physics/src/MeshInfoTests.cpp +++ /dev/null @@ -1,237 +0,0 @@ -// -// MeshInfoTests.cpp -// tests/physics/src -// -// Created by Virendra Singh on 2015.03.02 -// Copyright 2014 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 -#include -#include - -#include "MeshInfoTests.h" -const float epsilon = 0.015f; -void MeshInfoTests::testWithTetrahedron(){ - glm::vec3 p0(8.33220, -11.86875, 0.93355); - glm::vec3 p1(0.75523, 5.00000, 16.37072); - glm::vec3 p2(52.61236, 5.00000, -5.38580); - glm::vec3 p3(2.00000, 5.00000, 3.00000); - glm::vec3 centroid(15.92492, 0.782813, 3.72962); - - //translate the tetrahedron so that its apex is on origin - glm::vec3 p11 = p1 - p0; - glm::vec3 p22 = p2 - p0; - glm::vec3 p33 = p3 - p0; - vector vertices = { p11, p22, p33 }; - vector triangles = { 0, 1, 2 }; - - float volume = 1873.233236f; - float inertia_a = 43520.33257f; - //actual should be 194711.28938f. But for some reason it becomes 194711.296875 during - //runtime due to how floating points are stored. - float inertia_b = 194711.289f; - float inertia_c = 191168.76173f; - float inertia_aa = 4417.66150f; - float inertia_bb = -46343.16662f; - float inertia_cc = 11996.20119f; - - meshinfo::MeshInfo meshinfo(&vertices,&triangles); - vector voumeAndInertia = meshinfo.computeMassProperties(); - glm::vec3 tetCenterOfMass = meshinfo.getMeshCentroid(); - - //get original center of mass - tetCenterOfMass = tetCenterOfMass + p0; - glm::vec3 diff = centroid - tetCenterOfMass; - std::cout << std::setprecision(12); - //test if centroid is correct - if (diff.x > epsilon || diff.y > epsilon || diff.z > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Centroid is incorrect : Expected = " << centroid.x << " " << - centroid.y << " " << centroid.z << ", actual = " << tetCenterOfMass.x << " " << tetCenterOfMass.y << - " " << tetCenterOfMass.z << std::endl; - } - - //test if volume is correct - if (abs(volume - voumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << " " << - ", actual = " << voumeAndInertia.at(0) << std::endl; - } - - //test if moment of inertia with respect to x axis is correct - if (abs(inertia_a - (voumeAndInertia.at(1))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to x axis is incorrect : Expected = " << - inertia_a << " " << ", actual = " << voumeAndInertia.at(1) << std::endl; - } - - //test if moment of inertia with respect to y axis is correct - if (abs(inertia_b - voumeAndInertia.at(2)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to y axis is incorrect : Expected = " << - inertia_b << " " << ", actual = " << (voumeAndInertia.at(2)) << std::endl; - } - - //test if moment of inertia with respect to z axis is correct - if (abs(inertia_c - (voumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to z axis is incorrect : Expected = " << - inertia_c << " " << ", actual = " << (voumeAndInertia.at(3)) << std::endl; - } - - //test if product of inertia with respect to x axis is correct - if (abs(inertia_aa - (voumeAndInertia.at(4))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to x axis is incorrect : Expected = " << - inertia_aa << " " << ", actual = " << (voumeAndInertia.at(4)) << std::endl; - } - - //test if product of inertia with respect to y axis is correct - if (abs(inertia_bb - (voumeAndInertia.at(5))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to y axis is incorrect : Expected = " << - inertia_bb << " " << ", actual = " << (voumeAndInertia.at(5)) << std::endl; - } - - //test if product of inertia with respect to z axis is correct - if (abs(inertia_cc - (voumeAndInertia.at(6))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to z axis is incorrect : Expected = " << - inertia_cc << " " << ", actual = " << (voumeAndInertia.at(6)) << std::endl; - } - -} - -void MeshInfoTests::testWithTetrahedronAsMesh(){ - glm::vec3 p0(8.33220, -11.86875, 0.93355); - glm::vec3 p1(0.75523, 5.00000, 16.37072); - glm::vec3 p2(52.61236, 5.00000, -5.38580); - glm::vec3 p3(2.00000, 5.00000, 3.00000); - glm::vec3 centroid(15.92492, 0.782813, 3.72962); - /* TODO: actually test inertia/volume calculations here - //float volume = 1873.233236f; - //runtime due to how floating points are stored. - float inertia_a = 43520.33257f; - float inertia_b = 194711.289f; - float inertia_c = 191168.76173f; - float inertia_aa = 4417.66150f; - float inertia_bb = -46343.16662f; - float inertia_cc = 11996.20119f; - */ - std::cout << std::setprecision(12); - vector vertices = { p0, p1, p2, p3 }; - vector triangles = { 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 3 }; - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - std::cout << volumeAndInertia[0] << " " << volumeAndInertia[1] << " " << volumeAndInertia[2] - << " " << volumeAndInertia[3] - << " " << volumeAndInertia[4] - << " " << volumeAndInertia[5] << " " << volumeAndInertia[6] << std::endl; - - //translate the tetrahedron so that the model is placed at origin i.e. com is at origin - p0 -= centroid; - p1 -= centroid; - p2 -= centroid; - p3 -= centroid; -} - -void MeshInfoTests::testWithCube(){ - glm::vec3 p0(1.0, -1.0, -1.0); - glm::vec3 p1(1.0, -1.0, 1.0); - glm::vec3 p2(-1.0, -1.0, 1.0); - glm::vec3 p3(-1.0, -1.0, -1.0); - glm::vec3 p4(1.0, 1.0, -1.0); - glm::vec3 p5(1.0, 1.0, 1.0); - glm::vec3 p6(-1.0, 1.0, 1.0); - glm::vec3 p7(-1.0, 1.0, -1.0); - vector vertices; - vertices.push_back(p0); - vertices.push_back(p1); - vertices.push_back(p2); - vertices.push_back(p3); - vertices.push_back(p4); - vertices.push_back(p5); - vertices.push_back(p6); - vertices.push_back(p7); - std::cout << std::setprecision(10); - vector triangles = { 0, 1, 2, 0, 2, 3, 4, 7, 6, 4, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, - 7, 2, 7, 3, 4, 0, 3, 4, 3, 7 }; - glm::vec3 centerOfMass(0.0, 0.0, 0.0); - double volume = 8.0; - double side = 2.0; - double inertia = (volume * side * side) / 6.0; //inertia of a unit cube is (mass * side * side) /6 - - //test with origin as reference point - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - if (abs(centerOfMass.x - massProp.getMeshCentroid().x) > epsilon || abs(centerOfMass.y - massProp.getMeshCentroid().y) > epsilon || - abs(centerOfMass.z - massProp.getMeshCentroid().z) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Center of mass is incorrect : Expected = " << centerOfMass.x << " " << - centerOfMass.y << " " << centerOfMass.z << ", actual = " << massProp.getMeshCentroid().x << " " << - massProp.getMeshCentroid().y << " " << massProp.getMeshCentroid().z << std::endl; - } - - if (abs(volume - volumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << - ", actual = " << volumeAndInertia.at(0) << std::endl; - } - - if (abs(inertia - (volumeAndInertia.at(1))) > epsilon || abs(inertia - (volumeAndInertia.at(2))) > epsilon || - abs(inertia - (volumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia is incorrect : Expected = " << inertia << " " << - inertia << " " << inertia << ", actual = " << (volumeAndInertia.at(1)) << " " << (volumeAndInertia.at(2)) << - " " << (volumeAndInertia.at(3)) << std::endl; - } -} - -void MeshInfoTests::testWithUnitCube() -{ - glm::vec3 p0(0, 0, 1); - glm::vec3 p1(1, 0, 1); - glm::vec3 p2(0, 1, 1); - glm::vec3 p3(1, 1, 1); - glm::vec3 p4(0, 0, 0); - glm::vec3 p5(1, 0, 0); - glm::vec3 p6(0, 1, 0); - glm::vec3 p7(1, 1, 0); - vector vertices; - vertices.push_back(p0); - vertices.push_back(p1); - vertices.push_back(p2); - vertices.push_back(p3); - vertices.push_back(p4); - vertices.push_back(p5); - vertices.push_back(p6); - vertices.push_back(p7); - vector triangles = { 0, 1, 2, 1, 3, 2, 2, 3, 7, 2, 7, 6, 1, 7, 3, 1, 5, 7, 6, 7, 4, 7, 5, 4, 0, 4, 1, - 1, 4, 5, 2, 6, 4, 0, 2, 4 }; - glm::vec3 centerOfMass(0.5, 0.5, 0.5); - double volume = 1.0; - double side = 1.0; - double inertia = (volume * side * side) / 6.0; //inertia of a unit cube is (mass * side * side) /6 - std::cout << std::setprecision(10); - - //test with origin as reference point - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - if (abs(centerOfMass.x - massProp.getMeshCentroid().x) > epsilon || abs(centerOfMass.y - massProp.getMeshCentroid().y) > - epsilon || abs(centerOfMass.z - massProp.getMeshCentroid().z) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Center of mass is incorrect : Expected = " << centerOfMass.x << - " " << centerOfMass.y << " " << centerOfMass.z << ", actual = " << massProp.getMeshCentroid().x << " " << - massProp.getMeshCentroid().y << " " << massProp.getMeshCentroid().z << std::endl; - } - - if (abs(volume - volumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << - ", actual = " << volumeAndInertia.at(0) << std::endl; - } - - if (abs(inertia - (volumeAndInertia.at(1))) > epsilon || abs(inertia - (volumeAndInertia.at(2))) > epsilon || - abs(inertia - (volumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia is incorrect : Expected = " << inertia << " " << - inertia << " " << inertia << ", actual = " << (volumeAndInertia.at(1)) << " " << (volumeAndInertia.at(2)) << - " " << (volumeAndInertia.at(3)) << std::endl; - } -} -void MeshInfoTests::runAllTests(){ - testWithTetrahedron(); - testWithTetrahedronAsMesh(); - testWithUnitCube(); - testWithCube(); -} diff --git a/tests/physics/src/MeshInfoTests.h b/tests/physics/src/MeshInfoTests.h deleted file mode 100644 index 0ddd8d0944..0000000000 --- a/tests/physics/src/MeshInfoTests.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MeshInfoTests.h -// tests/physics/src -// -// Created by Virendra Singh on 2015.03.02 -// Copyright 2014 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_MeshInfoTests_h -#define hifi_MeshInfoTests_h -namespace MeshInfoTests{ - void testWithTetrahedron(); - void testWithUnitCube(); - void testWithCube(); - void runAllTests(); - void testWithTetrahedronAsMesh(); -} -#endif // hifi_MeshInfoTests_h \ No newline at end of file diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp new file mode 100644 index 0000000000..6434bd07ff --- /dev/null +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -0,0 +1,452 @@ +// +// MeshMassProperties.cpp +// tests/physics/src +// +// Created by Virendra Singh on 2015.03.02 +// Copyright 2014 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 +#include + +#include "MeshMassPropertiesTests.h" + +//#define VERBOSE_UNIT_TESTS + +const btScalar acceptableRelativeError(1.0e-5f); +const btScalar acceptableAbsoluteError(1.0e-4f); + +void printMatrix(const std::string& name, const btMatrix3x3& matrix) { + std::cout << name << " = [" << std::endl; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + std::cout << " " << matrix[i][j]; + } + std::cout << std::endl; + } + std::cout << "]" << std::endl; +} + +void MeshMassPropertiesTests::testParallelAxisTheorem() { +#ifdef EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST + // verity we can compute the inertia tensor of a box in two different ways: + // (a) as one box + // (b) as a combination of two partial boxes. +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + btScalar bigBoxX = 7.0f; + btScalar bigBoxY = 9.0f; + btScalar bigBoxZ = 11.0f; + btScalar bigBoxMass = bigBoxX * bigBoxY * bigBoxZ; + btMatrix3x3 bitBoxInertia; + computeBoxInertia(bigBoxMass, btVector3(bigBoxX, bigBoxY, bigBoxZ), bitBoxInertia); + + btScalar smallBoxX = bigBoxX / 2.0f; + btScalar smallBoxY = bigBoxY; + btScalar smallBoxZ = bigBoxZ; + btScalar smallBoxMass = smallBoxX * smallBoxY * smallBoxZ; + btMatrix3x3 smallBoxI; + computeBoxInertia(smallBoxMass, btVector3(smallBoxX, smallBoxY, smallBoxZ), smallBoxI); + + btVector3 smallBoxOffset(smallBoxX / 2.0f, 0.0f, 0.0f); + + btMatrix3x3 smallBoxShiftedRight = smallBoxI; + applyParallelAxisTheorem(smallBoxShiftedRight, smallBoxOffset, smallBoxMass); + + btMatrix3x3 smallBoxShiftedLeft = smallBoxI; + applyParallelAxisTheorem(smallBoxShiftedLeft, -smallBoxOffset, smallBoxMass); + + btMatrix3x3 twoSmallBoxesInertia = smallBoxShiftedRight + smallBoxShiftedLeft; + + // verify bigBox same as twoSmallBoxes + btScalar error; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = bitBoxInertia[i][j] - twoSmallBoxesInertia[i][j]; + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : box inertia[" << i << "][" << j << "] off by = " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + printMatrix("expected inertia", bitBoxInertia); + printMatrix("computed inertia", twoSmallBoxesInertia); +#endif // VERBOSE_UNIT_TESTS +#endif // EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +} + +void MeshMassPropertiesTests::testTetrahedron(){ + // given the four vertices of a tetrahedron verify the analytic formula for inertia + // agrees with expected results +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + btVector3 points[4]; + points[0] = btVector3(8.33220f, -11.86875f, 0.93355f); + points[1] = btVector3(0.75523f, 5.00000f, 16.37072f); + points[2] = btVector3(52.61236f, 5.00000f, -5.38580f); + points[3] = btVector3(2.00000f, 5.00000f, 3.00000f); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + // compute volume + btScalar volume = computeTetrahedronVolume(points); + btScalar error = (volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + btVector3 centerOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + // compute inertia tensor + // (shift the points so that tetrahedron's local centerOfMass is at origin) + for (int i = 0; i < 4; ++i) { + points[i] -= centerOfMass; + } + btMatrix3x3 inertia; + computeTetrahedronInertia(volume, points, inertia); + + // verify + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", inertia); + + // when building VERBOSE you might be instrested in the results from the brute force method: + btMatrix3x3 bruteInertia; + computeTetrahedronInertiaByBruteForce(points, bruteInertia); + printMatrix("brute inertia", bruteInertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testOpenTetrahedonMesh() { + // given the simplest possible mesh (open, with one triangle) + // verify MeshMassProperties computes the right nubers +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + VectorOfPoints points; + points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f)); + points.push_back(btVector3(0.75523f, 5.00000f, 16.37072f)); + points.push_back(btVector3(52.61236f, 5.00000f, -5.38580f)); + points.push_back(btVector3(2.00000f, 5.00000f, 3.00000f)); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + // test as an open mesh with one triangle + VectorOfPoints shiftedPoints; + shiftedPoints.push_back(points[0] - points[0]); + shiftedPoints.push_back(points[1] - points[0]); + shiftedPoints.push_back(points[2] - points[0]); + shiftedPoints.push_back(points[3] - points[0]); + VectorOfIndices triangles = { 1, 2, 3 }; + btVector3 expectedCenterOfMass = 0.25f * (shiftedPoints[0] + shiftedPoints[1] + shiftedPoints[2] + shiftedPoints[3]); + + // compute mass properties + MeshMassProperties mesh(shiftedPoints, triangles); + + // verify + btScalar error = (mesh.m_volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh.m_volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh.m_inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testClosedTetrahedronMesh() { + // given a tetrahedron as a closed mesh of four tiangles + // verify MeshMassProperties computes the right nubers +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + VectorOfPoints points; + points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f)); + points.push_back(btVector3(0.75523f, 5.00000f, 16.37072f)); + points.push_back(btVector3(52.61236f, 5.00000f, -5.38580f)); + points.push_back(btVector3(2.00000f, 5.00000f, 3.00000f)); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + btVector3 expectedCenterOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + VectorOfIndices triangles = { + 0, 2, 1, + 0, 3, 2, + 0, 1, 3, + 1, 2, 3 }; + + // compute mass properties + MeshMassProperties mesh(points, triangles); + + // verify + btScalar error; + error = (mesh.m_volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "(a) tetrahedron as mesh" << std::endl; + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh.m_volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh.m_inertia); +#endif // VERBOSE_UNIT_TESTS + + // test again, but this time shift the points so that the origin is definitely OUTSIDE the mesh + btVector3 shift = points[0] + expectedCenterOfMass; + for (int i = 0; i < (int)points.size(); ++i) { + points[i] += shift; + } + expectedCenterOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + // compute mass properties + mesh.computeMassProperties(points, triangles); + + // verify + error = (mesh.m_volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "(b) shifted tetrahedron as mesh" << std::endl; + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh.m_volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh.m_inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testBoxAsMesh() { + // verify that a mesh box produces the same mass properties as the analytic box. +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + + // build a box: + // / + // y + // / + // 6-------------------------7 + // /| /| + // / | / | + // / 2----------------------/--3 + // / / / / + // | 4-------------------------5 / --x-- + // z | / | / + // | |/ |/ + // 0 ------------------------1 + + btScalar x(5.0f); + btScalar y(3.0f); + btScalar z(2.0f); + + VectorOfPoints points; + points.reserve(8); + + points.push_back(btVector3(0.0f, 0.0f, 0.0f)); + points.push_back(btVector3(x, 0.0f, 0.0f)); + points.push_back(btVector3(0.0f, y, 0.0f)); + points.push_back(btVector3(x, y, 0.0f)); + points.push_back(btVector3(0.0f, 0.0f, z)); + points.push_back(btVector3(x, 0.0f, z)); + points.push_back(btVector3(0.0f, y, z)); + points.push_back(btVector3(x, y, z)); + + VectorOfIndices triangles = { + 0, 1, 4, + 1, 5, 4, + 1, 3, 5, + 3, 7, 5, + 2, 0, 6, + 0, 4, 6, + 3, 2, 7, + 2, 6, 7, + 4, 5, 6, + 5, 7, 6, + 0, 2, 1, + 2, 3, 1 + }; + + // compute expected mass properties analytically + btVector3 expectedCenterOfMass = 0.5f * btVector3(x, y, z); + btScalar expectedVolume = x * y * z; + btMatrix3x3 expectedInertia; + computeBoxInertia(expectedVolume, btVector3(x, y, z), expectedInertia); + + // compute the mass properties using the mesh + MeshMassProperties mesh(points, triangles); + + // verify + btScalar error; + error = (mesh.m_volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + if (expectedInertia [i][j] == btScalar(0.0f)) { + error = mesh.m_inertia[i][j] - expectedInertia[i][j]; + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << " absolute"<< std::endl; + } + } else { + error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "expected center of mass = < " + << expectedCenterOfMass[0] << ", " + << expectedCenterOfMass[1] << ", " + << expectedCenterOfMass[2] << "> " << std::endl; + std::cout << "computed center of mass = < " + << mesh.m_centerOfMass[0] << ", " + << mesh.m_centerOfMass[1] << ", " + << mesh.m_centerOfMass[2] << "> " << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh.m_inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::runAllTests() { + testParallelAxisTheorem(); + testTetrahedron(); + testOpenTetrahedonMesh(); + testClosedTetrahedronMesh(); + testBoxAsMesh(); + //testWithCube(); +} diff --git a/tests/physics/src/MeshMassPropertiesTests.h b/tests/physics/src/MeshMassPropertiesTests.h new file mode 100644 index 0000000000..ab352bfce2 --- /dev/null +++ b/tests/physics/src/MeshMassPropertiesTests.h @@ -0,0 +1,22 @@ +// +// MeshMassPropertiesTests.h +// tests/physics/src +// +// Created by Virendra Singh on 2015.03.02 +// Copyright 2014 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_MeshMassPropertiesTests_h +#define hifi_MeshMassPropertiesTests_h +namespace MeshMassPropertiesTests{ + void testParallelAxisTheorem(); + void testTetrahedron(); + void testOpenTetrahedonMesh(); + void testClosedTetrahedronMesh(); + void testBoxAsMesh(); + void runAllTests(); +} +#endif // hifi_MeshMassPropertiesTests_h diff --git a/tests/physics/src/main.cpp b/tests/physics/src/main.cpp index 0f35ed5002..f63925bb34 100644 --- a/tests/physics/src/main.cpp +++ b/tests/physics/src/main.cpp @@ -12,13 +12,13 @@ #include "ShapeInfoTests.h" #include "ShapeManagerTests.h" #include "BulletUtilTests.h" -#include "MeshInfoTests.h" +#include "MeshMassPropertiesTests.h" int main(int argc, char** argv) { ShapeColliderTests::runAllTests(); ShapeInfoTests::runAllTests(); ShapeManagerTests::runAllTests(); BulletUtilTests::runAllTests(); - MeshInfoTests::runAllTests(); + MeshMassPropertiesTests::runAllTests(); return 0; } From 708203089c38d3f9306cc72cd34ae1493dd0ebed Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 26 May 2015 22:53:57 -0700 Subject: [PATCH 24/34] replace tabs with spaces --- libraries/physics/src/MeshMassProperties.cpp | 76 +++++++++---------- tests/physics/src/MeshMassPropertiesTests.cpp | 2 +- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/libraries/physics/src/MeshMassProperties.cpp b/libraries/physics/src/MeshMassProperties.cpp index e5d4f4e5af..721941a360 100644 --- a/libraries/physics/src/MeshMassProperties.cpp +++ b/libraries/physics/src/MeshMassProperties.cpp @@ -86,18 +86,18 @@ void computeTetrahedronInertia(btScalar mass, btVector3* points, btMatrix3x3& in // helper function void computePointInertia(const btVector3& point, btScalar mass, btMatrix3x3& inertia) { - btScalar distanceSquared = point.length2(); - if (distanceSquared > 0.0f) { - for (uint32_t i = 0; i < 3; ++i) { + btScalar distanceSquared = point.length2(); + if (distanceSquared > 0.0f) { + for (uint32_t i = 0; i < 3; ++i) { btScalar pointi = point[i]; - inertia[i][i] = mass * (distanceSquared - (pointi * pointi)); - for (uint32_t j = i + 1; j < 3; ++j) { + inertia[i][i] = mass * (distanceSquared - (pointi * pointi)); + for (uint32_t j = i + 1; j < 3; ++j) { btScalar offDiagonal = - mass * pointi * point[j]; - inertia[i][j] = offDiagonal; - inertia[j][i] = offDiagonal; - } - } - } + inertia[i][j] = offDiagonal; + inertia[j][i] = offDiagonal; + } + } + } } // this method is included for unit test verification @@ -204,48 +204,48 @@ btScalar computeTetrahedronVolume(btVector3* points) { void applyParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { // Parallel Axis Theorem says: // - // Ishifted = Icm + M * [ (R*R)E - R(x)R ] - // - // where R*R = inside product - // R(x)R = outside product - // E = identity matrix + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // + // where R*R = inside product + // R(x)R = outside product + // E = identity matrix - btScalar distanceSquared = shift.length2(); - if (distanceSquared > btScalar(0.0f)) { - for (uint32_t i = 0; i < 3; ++i) { + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { btScalar shifti = shift[i]; - inertia[i][i] += mass * (distanceSquared - (shifti * shifti)); - for (uint32_t j = i + 1; j < 3; ++j) { + inertia[i][i] += mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { btScalar offDiagonal = mass * shifti * shift[j]; - inertia[i][j] -= offDiagonal; - inertia[j][i] -= offDiagonal; - } - } - } + inertia[i][j] -= offDiagonal; + inertia[j][i] -= offDiagonal; + } + } + } } // helper function void applyInverseParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { // Parallel Axis Theorem says: // - // Ishifted = Icm + M * [ (R*R)E - R(x)R ] - // + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // // So the inverse would be: // - // Icm = Ishifted - M * [ (R*R)E - R(x)R ] + // Icm = Ishifted - M * [ (R*R)E - R(x)R ] - btScalar distanceSquared = shift.length2(); - if (distanceSquared > btScalar(0.0f)) { - for (uint32_t i = 0; i < 3; ++i) { + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { btScalar shifti = shift[i]; - inertia[i][i] -= mass * (distanceSquared - (shifti * shifti)); - for (uint32_t j = i + 1; j < 3; ++j) { + inertia[i][i] -= mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { btScalar offDiagonal = mass * shifti * shift[j]; - inertia[i][j] += offDiagonal; - inertia[j][i] += offDiagonal; - } - } - } + inertia[i][j] += offDiagonal; + inertia[j][i] += offDiagonal; + } + } + } } MeshMassProperties::MeshMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices) { diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index 6434bd07ff..442abedebe 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -446,7 +446,7 @@ void MeshMassPropertiesTests::runAllTests() { testParallelAxisTheorem(); testTetrahedron(); testOpenTetrahedonMesh(); - testClosedTetrahedronMesh(); + testClosedTetrahedronMesh(); testBoxAsMesh(); //testWithCube(); } From 39d8244255f7df518bd67ff6c450bad8519ca51c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 26 May 2015 22:59:29 -0700 Subject: [PATCH 25/34] use highfideltiy style for class data member names --- libraries/physics/src/MeshMassProperties.cpp | 12 ++--- libraries/physics/src/MeshMassProperties.h | 6 +-- tests/physics/src/MeshMassPropertiesTests.cpp | 48 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/libraries/physics/src/MeshMassProperties.cpp b/libraries/physics/src/MeshMassProperties.cpp index 721941a360..ee2945422d 100644 --- a/libraries/physics/src/MeshMassProperties.cpp +++ b/libraries/physics/src/MeshMassProperties.cpp @@ -262,11 +262,11 @@ void MeshMassProperties::computeMassProperties(const VectorOfPoints& points, con // // initialize the totals - m_volume = btScalar(0.0f); + _volume = btScalar(0.0f); btVector3 weightedCenter; weightedCenter.setZero(); for (uint32_t i = 0; i < 3; ++i) { - m_inertia[i].setZero(); + _inertia[i].setZero(); } // create some variables to hold temporary results @@ -310,12 +310,12 @@ void MeshMassProperties::computeMassProperties(const VectorOfPoints& points, con // tally results weightedCenter += volume * center; - m_volume += volume; - m_inertia += tetraInertia; + _volume += volume; + _inertia += tetraInertia; } - m_centerOfMass = weightedCenter / m_volume; + _centerOfMass = weightedCenter / _volume; - applyInverseParallelAxisTheorem(m_inertia, m_centerOfMass, m_volume); + applyInverseParallelAxisTheorem(_inertia, _centerOfMass, _volume); } diff --git a/libraries/physics/src/MeshMassProperties.h b/libraries/physics/src/MeshMassProperties.h index a500cb28e8..d78c50db00 100644 --- a/libraries/physics/src/MeshMassProperties.h +++ b/libraries/physics/src/MeshMassProperties.h @@ -52,9 +52,9 @@ public: void computeMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices); // harveste the mass properties from these public data members - btScalar m_volume = 1.0f; - btVector3 m_centerOfMass = btVector3(0.0f, 0.0f, 0.0f); - btMatrix3x3 m_inertia = btMatrix3x3(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); + btScalar _volume = 1.0f; + btVector3 _centerOfMass = btVector3(0.0f, 0.0f, 0.0f); + btMatrix3x3 _inertia = btMatrix3x3(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); }; #endif // _hifi_MeshMassProperties_h diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index 442abedebe..dac13ef4c3 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -191,13 +191,13 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { MeshMassProperties mesh(shiftedPoints, triangles); // verify - btScalar error = (mesh.m_volume - expectedVolume) / expectedVolume; + btScalar error = (mesh._volume - expectedVolume) / expectedVolume; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " << error << std::endl; } - error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + error = (mesh._centerOfMass - expectedCenterOfMass).length(); if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " << error << std::endl; @@ -205,7 +205,7 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << std::endl; @@ -215,9 +215,9 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { #ifdef VERBOSE_UNIT_TESTS std::cout << "expected volume = " << expectedVolume << std::endl; - std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; printMatrix("expected inertia", expectedInertia); - printMatrix("computed inertia", mesh.m_inertia); + printMatrix("computed inertia", mesh._inertia); #endif // VERBOSE_UNIT_TESTS } @@ -261,13 +261,13 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { // verify btScalar error; - error = (mesh.m_volume - expectedVolume) / expectedVolume; + error = (mesh._volume - expectedVolume) / expectedVolume; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " << error << std::endl; } - error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + error = (mesh._centerOfMass - expectedCenterOfMass).length(); if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " << error << std::endl; @@ -275,7 +275,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << std::endl; @@ -286,9 +286,9 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { #ifdef VERBOSE_UNIT_TESTS std::cout << "(a) tetrahedron as mesh" << std::endl; std::cout << "expected volume = " << expectedVolume << std::endl; - std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; printMatrix("expected inertia", expectedInertia); - printMatrix("computed inertia", mesh.m_inertia); + printMatrix("computed inertia", mesh._inertia); #endif // VERBOSE_UNIT_TESTS // test again, but this time shift the points so that the origin is definitely OUTSIDE the mesh @@ -302,13 +302,13 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { mesh.computeMassProperties(points, triangles); // verify - error = (mesh.m_volume - expectedVolume) / expectedVolume; + error = (mesh._volume - expectedVolume) / expectedVolume; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " << error << std::endl; } - error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + error = (mesh._centerOfMass - expectedCenterOfMass).length(); if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " << error << std::endl; @@ -316,7 +316,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << std::endl; @@ -327,9 +327,9 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { #ifdef VERBOSE_UNIT_TESTS std::cout << "(b) shifted tetrahedron as mesh" << std::endl; std::cout << "expected volume = " << expectedVolume << std::endl; - std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; printMatrix("expected inertia", expectedInertia); - printMatrix("computed inertia", mesh.m_inertia); + printMatrix("computed inertia", mesh._inertia); #endif // VERBOSE_UNIT_TESTS } @@ -396,13 +396,13 @@ void MeshMassPropertiesTests::testBoxAsMesh() { // verify btScalar error; - error = (mesh.m_volume - expectedVolume) / expectedVolume; + error = (mesh._volume - expectedVolume) / expectedVolume; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " << error << std::endl; } - error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + error = (mesh._centerOfMass - expectedCenterOfMass).length(); if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " << error << std::endl; @@ -411,13 +411,13 @@ void MeshMassPropertiesTests::testBoxAsMesh() { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { if (expectedInertia [i][j] == btScalar(0.0f)) { - error = mesh.m_inertia[i][j] - expectedInertia[i][j]; + error = mesh._inertia[i][j] - expectedInertia[i][j]; if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << " absolute"<< std::endl; } } else { - error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << std::endl; @@ -428,17 +428,17 @@ void MeshMassPropertiesTests::testBoxAsMesh() { #ifdef VERBOSE_UNIT_TESTS std::cout << "expected volume = " << expectedVolume << std::endl; - std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; std::cout << "expected center of mass = < " << expectedCenterOfMass[0] << ", " << expectedCenterOfMass[1] << ", " << expectedCenterOfMass[2] << "> " << std::endl; std::cout << "computed center of mass = < " - << mesh.m_centerOfMass[0] << ", " - << mesh.m_centerOfMass[1] << ", " - << mesh.m_centerOfMass[2] << "> " << std::endl; + << mesh._centerOfMass[0] << ", " + << mesh._centerOfMass[1] << ", " + << mesh._centerOfMass[2] << "> " << std::endl; printMatrix("expected inertia", expectedInertia); - printMatrix("computed inertia", mesh.m_inertia); + printMatrix("computed inertia", mesh._inertia); #endif // VERBOSE_UNIT_TESTS } From 2a6955ce1207993cfb18f7af7e36a8eecdae27d5 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 May 2015 09:14:11 -0700 Subject: [PATCH 26/34] fix windows build --- libraries/physics/src/MeshMassProperties.h | 1 + tests/physics/src/MeshMassPropertiesTests.cpp | 47 +++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/libraries/physics/src/MeshMassProperties.h b/libraries/physics/src/MeshMassProperties.h index d78c50db00..208955d3a6 100644 --- a/libraries/physics/src/MeshMassProperties.h +++ b/libraries/physics/src/MeshMassProperties.h @@ -13,6 +13,7 @@ #define hifi_MeshMassProperties_h #include +#include #include diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index dac13ef4c3..ebb762aa55 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include "MeshMassPropertiesTests.h" @@ -30,6 +31,12 @@ void printMatrix(const std::string& name, const btMatrix3x3& matrix) { std::cout << "]" << std::endl; } +void pushTriangle(VectorOfIndices& indices, uint32_t a, uint32_t b, uint32_t c) { + indices.push_back(a); + indices.push_back(b); + indices.push_back(c); +} + void MeshMassPropertiesTests::testParallelAxisTheorem() { #ifdef EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST // verity we can compute the inertia tensor of a box in two different ways: @@ -184,7 +191,8 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { shiftedPoints.push_back(points[1] - points[0]); shiftedPoints.push_back(points[2] - points[0]); shiftedPoints.push_back(points[3] - points[0]); - VectorOfIndices triangles = { 1, 2, 3 }; + VectorOfIndices triangles; + pushTriangle(triangles, 1, 2, 3); btVector3 expectedCenterOfMass = 0.25f * (shiftedPoints[0] + shiftedPoints[1] + shiftedPoints[2] + shiftedPoints[3]); // compute mass properties @@ -250,11 +258,11 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { btVector3 expectedCenterOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); - VectorOfIndices triangles = { - 0, 2, 1, - 0, 3, 2, - 0, 1, 3, - 1, 2, 3 }; + VectorOfIndices triangles; + pushTriangle(triangles, 0, 2, 1); + pushTriangle(triangles, 0, 3, 2); + pushTriangle(triangles, 0, 1, 3); + pushTriangle(triangles, 1, 2, 3); // compute mass properties MeshMassProperties mesh(points, triangles); @@ -370,20 +378,19 @@ void MeshMassPropertiesTests::testBoxAsMesh() { points.push_back(btVector3(0.0f, y, z)); points.push_back(btVector3(x, y, z)); - VectorOfIndices triangles = { - 0, 1, 4, - 1, 5, 4, - 1, 3, 5, - 3, 7, 5, - 2, 0, 6, - 0, 4, 6, - 3, 2, 7, - 2, 6, 7, - 4, 5, 6, - 5, 7, 6, - 0, 2, 1, - 2, 3, 1 - }; + VectorOfIndices triangles; + pushTriangle(triangles, 0, 1, 4); + pushTriangle(triangles, 1, 5, 4); + pushTriangle(triangles, 1, 3, 5); + pushTriangle(triangles, 3, 7, 5); + pushTriangle(triangles, 2, 0, 6); + pushTriangle(triangles, 0, 4, 6); + pushTriangle(triangles, 3, 2, 7); + pushTriangle(triangles, 2, 6, 7); + pushTriangle(triangles, 4, 5, 6); + pushTriangle(triangles, 5, 7, 6); + pushTriangle(triangles, 0, 2, 1); + pushTriangle(triangles, 2, 3, 1); // compute expected mass properties analytically btVector3 expectedCenterOfMass = 0.5f * btVector3(x, y, z); From eb0f35e041e391563c1985a31923c910b214582b Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Wed, 27 May 2015 11:44:37 -0700 Subject: [PATCH 27/34] removed const keyword from qinvokables for avatar getter methods such as getVelocity --- interface/src/avatar/Avatar.h | 6 +++--- libraries/avatars/src/AvatarData.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 0cdaf36099..939161327b 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -147,9 +147,9 @@ public: Q_INVOKABLE glm::vec3 getNeckPosition() const; - Q_INVOKABLE const glm::vec3& getAcceleration() const { return _acceleration; } - Q_INVOKABLE const glm::vec3& getAngularVelocity() const { return _angularVelocity; } - Q_INVOKABLE const glm::vec3& getAngularAcceleration() const { return _angularAcceleration; } + Q_INVOKABLE glm::vec3 getAcceleration() { return _acceleration; } + Q_INVOKABLE glm::vec3 getAngularVelocity() { return _angularVelocity; } + Q_INVOKABLE glm::vec3 getAngularAcceleration() { return _angularAcceleration; } /// Scales a world space position vector relative to the avatar position and scale diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 603b5d76ea..a27e4256ef 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -301,7 +301,7 @@ public: int getReceiveRate() const; void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } - Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; } + Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; } const glm::vec3& getTargetVelocity() const { return _targetVelocity; } bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } From a2272d3f42b381cecc5d721be7e5cb1b31127acf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 May 2015 11:49:00 -0700 Subject: [PATCH 28/34] Fix Windows C4351 build warning VS2013 warns about default initialization of arrays because it behaved differently in previous versions. Default initialization is what we expect now that we're using VS2013 so we can disable this warning globally. --- CMakeLists.txt | 3 ++- interface/src/devices/DdeFaceTracker.cpp | 11 ----------- libraries/networking/src/Assignment.cpp | 6 ------ 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 347341efa0..e57e33e3b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,8 @@ if (WIN32) endif () message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH}) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") # /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory. # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables # TODO: Remove when building 64-bit. diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index fba37736e3..27e15cea0e 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -141,13 +141,6 @@ static const float STARTING_DDE_MESSAGE_TIME = 0.033f; static const float DEFAULT_DDE_EYE_CLOSING_THRESHOLD = 0.8f; static const int CALIBRATION_SAMPLES = 150; -#ifdef WIN32 -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeBlinks' will be default initialized -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_filteredEyeBlinks' will be default initialized -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeCoefficients' will be default initialized -#pragma warning(disable:4351) -#endif - DdeFaceTracker::DdeFaceTracker() : DdeFaceTracker(QHostAddress::Any, DDE_SERVER_PORT, DDE_CONTROL_PORT) { @@ -214,10 +207,6 @@ DdeFaceTracker::~DdeFaceTracker() { } } -#ifdef WIN32 -#pragma warning(default:4351) -#endif - void DdeFaceTracker::init() { FaceTracker::init(); setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) && !_isMuted); diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 944041730e..a4fa246c93 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -32,12 +32,6 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { } } -#ifdef WIN32 -//warning C4351: new behavior: elements of array 'Assignment::_payload' will be default initialized -// We're disabling this warning because the new behavior which is to initialize the array with 0 is acceptable to us. -#pragma warning(disable:4351) -#endif - Assignment::Assignment() : _uuid(), _command(Assignment::RequestCommand), From ba0467aafe8359add5b4b8cc28d1c5ab56fc900b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 May 2015 14:10:45 -0700 Subject: [PATCH 29/34] =?UTF-8?q?Revert=20"removed=20const=20keyword=20fro?= =?UTF-8?q?m=20Q=5FINVOKABLE=20for=20avatar=20getter=20methods=20such?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface/src/avatar/Avatar.h | 6 +++--- libraries/avatars/src/AvatarData.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 939161327b..0cdaf36099 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -147,9 +147,9 @@ public: Q_INVOKABLE glm::vec3 getNeckPosition() const; - Q_INVOKABLE glm::vec3 getAcceleration() { return _acceleration; } - Q_INVOKABLE glm::vec3 getAngularVelocity() { return _angularVelocity; } - Q_INVOKABLE glm::vec3 getAngularAcceleration() { return _angularAcceleration; } + Q_INVOKABLE const glm::vec3& getAcceleration() const { return _acceleration; } + Q_INVOKABLE const glm::vec3& getAngularVelocity() const { return _angularVelocity; } + Q_INVOKABLE const glm::vec3& getAngularAcceleration() const { return _angularAcceleration; } /// Scales a world space position vector relative to the avatar position and scale diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a27e4256ef..603b5d76ea 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -301,7 +301,7 @@ public: int getReceiveRate() const; void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } - Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; } + Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; } const glm::vec3& getTargetVelocity() const { return _targetVelocity; } bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } From bfa0e9c2346a5d0c04ede930e9bfbf4605fb00d3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 May 2015 14:44:23 -0700 Subject: [PATCH 30/34] recreate PR #4973 with some additional changes -- removed const keyword from qinvokables for avatar getter methods such as getVelocity --- interface/src/avatar/Avatar.h | 6 +++--- interface/src/avatar/AvatarMotionState.cpp | 6 +++--- interface/src/avatar/AvatarMotionState.h | 6 +++--- libraries/avatars/src/AvatarData.h | 2 +- libraries/physics/src/EntityMotionState.h | 6 +++--- libraries/physics/src/ObjectMotionState.h | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 0cdaf36099..3c784fb925 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -147,9 +147,9 @@ public: Q_INVOKABLE glm::vec3 getNeckPosition() const; - Q_INVOKABLE const glm::vec3& getAcceleration() const { return _acceleration; } - Q_INVOKABLE const glm::vec3& getAngularVelocity() const { return _angularVelocity; } - Q_INVOKABLE const glm::vec3& getAngularAcceleration() const { return _angularAcceleration; } + Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; } + Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; } + Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; } /// Scales a world space position vector relative to the avatar position and scale diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 03a49c069e..530225b319 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -123,17 +123,17 @@ glm::quat AvatarMotionState::getObjectRotation() const { } // virtual -const glm::vec3& AvatarMotionState::getObjectLinearVelocity() const { +glm::vec3 AvatarMotionState::getObjectLinearVelocity() const { return _avatar->getVelocity(); } // virtual -const glm::vec3& AvatarMotionState::getObjectAngularVelocity() const { +glm::vec3 AvatarMotionState::getObjectAngularVelocity() const { return _avatar->getAngularVelocity(); } // virtual -const glm::vec3& AvatarMotionState::getObjectGravity() const { +glm::vec3 AvatarMotionState::getObjectGravity() const { return _avatar->getAcceleration(); } diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index a4fc9db20a..68c687d65b 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -48,9 +48,9 @@ public: virtual glm::vec3 getObjectPosition() const; virtual glm::quat getObjectRotation() const; - virtual const glm::vec3& getObjectLinearVelocity() const; - virtual const glm::vec3& getObjectAngularVelocity() const; - virtual const glm::vec3& getObjectGravity() const; + virtual glm::vec3 getObjectLinearVelocity() const; + virtual glm::vec3 getObjectAngularVelocity() const; + virtual glm::vec3 getObjectGravity() const; virtual const QUuid& getObjectID() const; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 603b5d76ea..a27e4256ef 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -301,7 +301,7 @@ public: int getReceiveRate() const; void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } - Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; } + Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; } const glm::vec3& getTargetVelocity() const { return _targetVelocity; } bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 80b56ccf2a..65279dc01a 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -61,9 +61,9 @@ public: virtual glm::vec3 getObjectPosition() const { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); } virtual glm::quat getObjectRotation() const { return _entity->getRotation(); } - virtual const glm::vec3& getObjectLinearVelocity() const { return _entity->getVelocity(); } - virtual const glm::vec3& getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } - virtual const glm::vec3& getObjectGravity() const { return _entity->getGravity(); } + virtual glm::vec3 getObjectLinearVelocity() const { return _entity->getVelocity(); } + virtual glm::vec3 getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } + virtual glm::vec3 getObjectGravity() const { return _entity->getGravity(); } virtual const QUuid& getObjectID() const { return _entity->getID(); } diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 337f09e719..246ed16627 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -112,9 +112,9 @@ public: virtual glm::vec3 getObjectPosition() const = 0; virtual glm::quat getObjectRotation() const = 0; - virtual const glm::vec3& getObjectLinearVelocity() const = 0; - virtual const glm::vec3& getObjectAngularVelocity() const = 0; - virtual const glm::vec3& getObjectGravity() const = 0; + virtual glm::vec3 getObjectLinearVelocity() const = 0; + virtual glm::vec3 getObjectAngularVelocity() const = 0; + virtual glm::vec3 getObjectGravity() const = 0; virtual const QUuid& getObjectID() const = 0; From ca85401b77dbfb493007ca9bc73fca6258040068 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Wed, 27 May 2015 15:08:52 -0700 Subject: [PATCH 31/34] added button so user can toggle pointer on/off --- examples/pointer.js | 101 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 20 deletions(-) diff --git a/examples/pointer.js b/examples/pointer.js index 9ca20504b4..722f1d4f11 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -18,22 +18,33 @@ var position, positionOffset, prevPosition; var nearLinePosition; var strokes = []; var STROKE_ADJUST = 0.005; -var DISTANCE_DRAW_THRESHOLD = .03; +var DISTANCE_DRAW_THRESHOLD = .02; var drawDistance = 0; +var LINE_WIDTH = 20; + var userCanDraw = true; +var userCanPoint = true; var BUTTON_SIZE = 32; var PADDING = 3; -var buttonOffColor = {red: 250, green: 10, blue: 10}; -var buttonOnColor = {red: 10, green: 200, blue: 100}; +var buttonOffColor = { + red: 250, + green: 10, + blue: 10 +}; +var buttonOnColor = { + red: 10, + green: 200, + blue: 100 +}; HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var screenSize = Controller.getViewportDimensions(); var drawButton = Overlays.addOverlay("image", { - x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + x: screenSize.x / 2 - BUTTON_SIZE + PADDING * 2, y: screenSize.y - (BUTTON_SIZE + PADDING), width: BUTTON_SIZE, height: BUTTON_SIZE, @@ -42,6 +53,15 @@ var drawButton = Overlays.addOverlay("image", { alpha: 1 }); +var pointerButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/laser.png", + color: buttonOnColor, + alpha: 1 +}) @@ -50,8 +70,16 @@ center.y += 0.5; var whiteBoard = Entities.addEntity({ type: "Box", position: center, - dimensions: {x: 1, y: 1, z: .001}, - color: {red: 255, green: 255, blue: 255} + dimensions: { + x: 1, + y: 1, + z: .001 + }, + color: { + red: 255, + green: 255, + blue: 255 + } }); function calculateNearLinePosition(targetPosition) { @@ -73,6 +101,9 @@ function removeLine() { function createOrUpdateLine(event) { + if (!userCanPoint) { + return; + } var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking var props = Entities.getEntityProperties(intersection.entityID); @@ -82,14 +113,13 @@ function createOrUpdateLine(event) { var subtractVec = Vec3.multiply(Vec3.normalize(pickRay.direction), STROKE_ADJUST); startPosition = Vec3.subtract(startPosition, subtractVec); nearLinePosition = calculateNearLinePosition(intersection.intersection); - positionOffset= Vec3.subtract(startPosition, nearLinePosition); + positionOffset = Vec3.subtract(startPosition, nearLinePosition); if (lineIsRezzed) { Entities.editEntity(lineEntityID, { position: nearLinePosition, dimensions: positionOffset, - lifetime: 15 + props.lifespan // renew lifetime }); - if(userCanDraw){ + if (userCanDraw) { draw(); } } else { @@ -104,7 +134,6 @@ function createOrUpdateLine(event) { green: 255, blue: 255 }, - lifetime: 15 // if someone crashes while pointing, don't leave the line there forever. }); } } else { @@ -112,11 +141,11 @@ function createOrUpdateLine(event) { } } -function draw(){ +function draw() { //We only want to draw line if distance between starting and previous point is large enough drawDistance = Vec3.distance(startPosition, prevPosition); - if( drawDistance < DISTANCE_DRAW_THRESHOLD){ + if (drawDistance < DISTANCE_DRAW_THRESHOLD) { return; } @@ -125,8 +154,12 @@ function draw(){ type: "Line", position: prevPosition, dimensions: offset, - color: {red: 200, green: 40, blue: 200}, - // lifetime: 20 + color: { + red: 200, + green: 40, + blue: 200 + }, + lineWidth: LINE_WIDTH })); prevPosition = startPosition; } @@ -138,12 +171,38 @@ function mousePressEvent(event) { }); if (clickedOverlay == drawButton) { userCanDraw = !userCanDraw; - if(userCanDraw === true){ - Overlays.editOverlay(drawButton, {color: buttonOnColor}); + if (userCanDraw === true) { + Overlays.editOverlay(drawButton, { + color: buttonOnColor + }); } else { - Overlays.editOverlay(drawButton, {color: buttonOffColor}); + Overlays.editOverlay(drawButton, { + color: buttonOffColor + }); } - } + } + + if (clickedOverlay == pointerButton) { + userCanPoint = !userCanPoint; + if (userCanPoint === true) { + Overlays.editOverlay(pointerButton, { + color: buttonOnColor + }); + if (userCanDraw === true) { + + Overlays.editOverlay(drawButton, { + color: buttonOnColor + }); + } + } else { + Overlays.editOverlay(pointerButton, { + color: buttonOffColor + }); + Overlays.editOverlay(drawButton, { + color: buttonOffColor + }); + } + } if (!event.isLeftButton || altHeld) { return; @@ -154,6 +213,7 @@ function mousePressEvent(event) { } + function mouseMoveEvent(event) { createOrUpdateLine(event); } @@ -182,13 +242,14 @@ function keyReleaseEvent(event) { } -function cleanup(){ +function cleanup() { Entities.deleteEntity(whiteBoard); - for(var i =0; i < strokes.length; i++){ + for (var i = 0; i < strokes.length; i++) { Entities.deleteEntity(strokes[i]); } Overlays.deleteOverlay(drawButton); + Overlays.deleteOverlay(pointerButton); } From 07c8fb6c025b354e291dd6b221439e1856ab8994 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Wed, 27 May 2015 15:11:16 -0700 Subject: [PATCH 32/34] turned pointer and draw funcitonality off be default --- examples/pointer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/pointer.js b/examples/pointer.js index 722f1d4f11..eebe4ec5be 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -23,8 +23,8 @@ var drawDistance = 0; var LINE_WIDTH = 20; -var userCanDraw = true; -var userCanPoint = true; +var userCanPoint = false; +var userCanDraw = false; var BUTTON_SIZE = 32; var PADDING = 3; @@ -49,7 +49,7 @@ var drawButton = Overlays.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/pencil.png?v2", - color: buttonOnColor, + color: buttonOffColor, alpha: 1 }); @@ -59,7 +59,7 @@ var pointerButton = Overlays.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/laser.png", - color: buttonOnColor, + color: buttonOffColor, alpha: 1 }) From 4fffe05b63fe583c78b40f6827e4b2db721a847e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 27 May 2015 16:25:11 -0700 Subject: [PATCH 33/34] Play still sound for still objects and those just created in a script. --- .../entities-renderer/src/EntityTreeRenderer.cpp | 11 +++++++++++ libraries/entities/src/EntityScriptingInterface.cpp | 1 + 2 files changed, 12 insertions(+) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 7b25d20ef3..4a4fd4a0d7 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1107,6 +1107,17 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT return; } QUuid simulatorID = entity->getSimulatorID(); + if (simulatorID.isNull()) { + // Can be null if it has never moved since being created or coming out of persistence. + // However, for there to be a collission, one of the two objects must be moving. + const EntityItemID& otherID = (id == collision.idA) ? collision.idB : collision.idA; + EntityItemPointer otherEntity = entityTree->findEntityByEntityItemID(otherID); + if (!otherEntity) { + return; + } + simulatorID = otherEntity->getSimulatorID(); + } + if (simulatorID.isNull() || (simulatorID != myNodeID)) { return; // Only one injector per simulation, please. } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 7d286a3710..e652c77d78 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -83,6 +83,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties entity->setLastBroadcast(usecTimestampNow()); // This Node is creating a new object. If it's in motion, set this Node as the simulator. bidForSimulationOwnership(propertiesWithSimID); + entity->setSimulatorID(propertiesWithSimID.getSimulatorID()); // and make note of it now, so we can act on it right away. } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; From aba539928db40714e7f1f3d2d1bdfdf1bbd5c444 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 27 May 2015 18:05:59 -0700 Subject: [PATCH 34/34] fix grab glitches and failure to own simulation --- libraries/entities/src/EntityItem.cpp | 561 ++++++++++---------- libraries/physics/src/EntityMotionState.cpp | 10 +- 2 files changed, 277 insertions(+), 294 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index e8210c7e79..104ce168bb 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -318,15 +318,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef return 0; } - // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. - glm::vec3 savePosition = _position; - glm::quat saveRotation = _rotation; - // glm::vec3 saveVelocity = _velocity; - // glm::vec3 saveAngularVelocity = _angularVelocity; - // glm::vec3 saveGravity = _gravity; - // glm::vec3 saveAcceleration = _acceleration; - - // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] @@ -337,299 +328,308 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef const int MINIMUM_HEADER_BYTES = 27; int bytesRead = 0; - if (bytesLeftToRead >= MINIMUM_HEADER_BYTES) { + if (bytesLeftToRead < MINIMUM_HEADER_BYTES) { + return 0; + } - int originalLength = bytesLeftToRead; - QByteArray originalDataBuffer((const char*)data, originalLength); + // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. + glm::vec3 savePosition = _position; + glm::quat saveRotation = _rotation; + glm::vec3 saveVelocity = _velocity; + glm::vec3 saveAngularVelocity = _angularVelocity; - int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; + int originalLength = bytesLeftToRead; + QByteArray originalDataBuffer((const char*)data, originalLength); - const unsigned char* dataAt = data; + int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; - // id - QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size - _id = QUuid::fromRfc4122(encodedID); - dataAt += encodedID.size(); - bytesRead += encodedID.size(); - - // type - QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded typeCoder = encodedType; - encodedType = typeCoder; // determine true length - dataAt += encodedType.size(); - bytesRead += encodedType.size(); - quint32 type = typeCoder; - _type = (EntityTypes::EntityType)type; + const unsigned char* dataAt = data; - bool overwriteLocalData = true; // assume the new content overwrites our local data + // id + QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size + _id = QUuid::fromRfc4122(encodedID); + dataAt += encodedID.size(); + bytesRead += encodedID.size(); + + // type + QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded typeCoder = encodedType; + encodedType = typeCoder; // determine true length + dataAt += encodedType.size(); + bytesRead += encodedType.size(); + quint32 type = typeCoder; + _type = (EntityTypes::EntityType)type; - // _created - quint64 createdFromBuffer = 0; - memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); - dataAt += sizeof(createdFromBuffer); - bytesRead += sizeof(createdFromBuffer); + bool overwriteLocalData = true; // assume the new content overwrites our local data - quint64 now = usecTimestampNow(); - if (_created == UNKNOWN_CREATED_TIME) { - // we don't yet have a _created timestamp, so we accept this one - createdFromBuffer -= clockSkew; - if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { - createdFromBuffer = now; - } - _created = createdFromBuffer; + // _created + quint64 createdFromBuffer = 0; + memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); + dataAt += sizeof(createdFromBuffer); + bytesRead += sizeof(createdFromBuffer); + + quint64 now = usecTimestampNow(); + if (_created == UNKNOWN_CREATED_TIME) { + // we don't yet have a _created timestamp, so we accept this one + createdFromBuffer -= clockSkew; + if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { + createdFromBuffer = now; } + _created = createdFromBuffer; + } + #ifdef WANT_DEBUG + quint64 lastEdited = getLastEdited(); + float editedAgo = getEditedAgo(); + QString agoAsString = formatSecondsElapsed(editedAgo); + QString ageAsString = formatSecondsElapsed(getAge()); + qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; + qCDebug(entities) << "------------------------------------------"; + debugDump(); + qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << " _created =" << _created; + qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; + qCDebug(entities) << " lastEdited =" << lastEdited; + qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; + #endif + + quint64 lastEditedFromBuffer = 0; + quint64 lastEditedFromBufferAdjusted = 0; + + // TODO: we could make this encoded as a delta from _created + // _lastEdited + memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); + dataAt += sizeof(lastEditedFromBuffer); + bytesRead += sizeof(lastEditedFromBuffer); + lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; + if (lastEditedFromBufferAdjusted > now) { + lastEditedFromBufferAdjusted = now; + } + + bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); + + #ifdef WANT_DEBUG + qCDebug(entities) << "data from server **************** "; + qCDebug(entities) << " entityItemID:" << getEntityItemID(); + qCDebug(entities) << " now:" << now; + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); + qCDebug(entities) << " clockSkew:" << debugTimeOnly(clockSkew); + qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); + qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); + qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; + #endif + + bool ignoreServerPacket = false; // assume we'll use this server packet + + // If this packet is from the same server edit as the last packet we accepted from the server + // we probably want to use it. + if (fromSameServerEdit) { + // If this is from the same sever packet, then check against any local changes since we got + // the most recent packet from this server time + if (_lastEdited > _lastEditedFromRemote) { + ignoreServerPacket = true; + } + } else { + // If this isn't from the same sever packet, then honor our skew adjusted times... + // If we've changed our local tree more recently than the new data from this packet + // then we will not be changing our values, instead we just read and skip the data + if (_lastEdited > lastEditedFromBufferAdjusted) { + ignoreServerPacket = true; + } + } + + if (ignoreServerPacket) { + overwriteLocalData = false; #ifdef WANT_DEBUG - quint64 lastEdited = getLastEdited(); - float editedAgo = getEditedAgo(); - QString agoAsString = formatSecondsElapsed(editedAgo); - QString ageAsString = formatSecondsElapsed(getAge()); - qCDebug(entities) << "------------------------------------------"; - qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; - qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << "IGNORING old data from server!!! ****************"; debugDump(); - qCDebug(entities) << "------------------------------------------"; - qCDebug(entities) << " _created =" << _created; - qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; - qCDebug(entities) << " lastEdited =" << lastEdited; - qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; #endif - - quint64 lastEditedFromBuffer = 0; - quint64 lastEditedFromBufferAdjusted = 0; - - // TODO: we could make this encoded as a delta from _created - // _lastEdited - memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); - dataAt += sizeof(lastEditedFromBuffer); - bytesRead += sizeof(lastEditedFromBuffer); - lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; - if (lastEditedFromBufferAdjusted > now) { - lastEditedFromBufferAdjusted = now; - } - - bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); - + } else { #ifdef WANT_DEBUG - qCDebug(entities) << "data from server **************** "; - qCDebug(entities) << " entityItemID:" << getEntityItemID(); - qCDebug(entities) << " now:" << now; - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); - qCDebug(entities) << " clockSkew:" << debugTimeOnly(clockSkew); - qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); - qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); - qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); - qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; + qCDebug(entities) << "USING NEW data from server!!! ****************"; + debugDump(); #endif - bool ignoreServerPacket = false; // assume we'll use this server packet - - // If this packet is from the same server edit as the last packet we accepted from the server - // we probably want to use it. - if (fromSameServerEdit) { - // If this is from the same sever packet, then check against any local changes since we got - // the most recent packet from this server time - if (_lastEdited > _lastEditedFromRemote) { - ignoreServerPacket = true; - } - } else { - // If this isn't from the same sever packet, then honor our skew adjusted times... - // If we've changed our local tree more recently than the new data from this packet - // then we will not be changing our values, instead we just read and skip the data - if (_lastEdited > lastEditedFromBufferAdjusted) { - ignoreServerPacket = true; - } - } + // don't allow _lastEdited to be in the future + _lastEdited = lastEditedFromBufferAdjusted; + _lastEditedFromRemote = now; + _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - if (ignoreServerPacket) { - overwriteLocalData = false; - #ifdef WANT_DEBUG - qCDebug(entities) << "IGNORING old data from server!!! ****************"; - debugDump(); - #endif - } else { + // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed + // the properties out of the bitstream (see below)) + somethingChangedNotification(); // notify derived classes that something has changed + } - #ifdef WANT_DEBUG - qCDebug(entities) << "USING NEW data from server!!! ****************"; - debugDump(); - #endif - - // don't allow _lastEdited to be in the future - _lastEdited = lastEditedFromBufferAdjusted; - _lastEditedFromRemote = now; - _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - - // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed - // the properties out of the bitstream (see below)) - somethingChangedNotification(); // notify derived classes that something has changed - } - - // last updated is stored as ByteCountCoded delta from lastEdited - QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded updateDeltaCoder = encodedUpdateDelta; - quint64 updateDelta = updateDeltaCoder; + // last updated is stored as ByteCountCoded delta from lastEdited + QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded updateDeltaCoder = encodedUpdateDelta; + quint64 updateDelta = updateDeltaCoder; + if (overwriteLocalData) { + _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that + #ifdef WANT_DEBUG + qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); + qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); + qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + #endif + } + encodedUpdateDelta = updateDeltaCoder; // determine true length + dataAt += encodedUpdateDelta.size(); + bytesRead += encodedUpdateDelta.size(); + + // Newer bitstreams will have a last simulated and a last updated value + if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { + // last simulated is stored as ByteCountCoded delta from lastEdited + QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded simulatedDeltaCoder = encodedSimulatedDelta; + quint64 simulatedDelta = simulatedDeltaCoder; if (overwriteLocalData) { - _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that + _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that #ifdef WANT_DEBUG - qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); + qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); #endif } - encodedUpdateDelta = updateDeltaCoder; // determine true length - dataAt += encodedUpdateDelta.size(); - bytesRead += encodedUpdateDelta.size(); - - // Newer bitstreams will have a last simulated and a last updated value - if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { - // last simulated is stored as ByteCountCoded delta from lastEdited - QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded simulatedDeltaCoder = encodedSimulatedDelta; - quint64 simulatedDelta = simulatedDeltaCoder; + encodedSimulatedDelta = simulatedDeltaCoder; // determine true length + dataAt += encodedSimulatedDelta.size(); + bytesRead += encodedSimulatedDelta.size(); + } + + #ifdef WANT_DEBUG + if (overwriteLocalData) { + qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now); + qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); + } + #endif + + + // Property Flags + QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size + EntityPropertyFlags propertyFlags = encodedPropertyFlags; + dataAt += propertyFlags.getEncodedLength(); + bytesRead += propertyFlags.getEncodedLength(); + bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + } else { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); + } + + // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS + if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { + if (propertyFlags.getHasProperty(PROP_RADIUS)) { + float fromBuffer; + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); + dataAt += sizeof(fromBuffer); + bytesRead += sizeof(fromBuffer); if (overwriteLocalData) { - _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that - #ifdef WANT_DEBUG - qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); - qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); - qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); - #endif + setRadius(fromBuffer); } - encodedSimulatedDelta = simulatedDeltaCoder; // determine true length - dataAt += encodedSimulatedDelta.size(); - bytesRead += encodedSimulatedDelta.size(); } - - #ifdef WANT_DEBUG - if (overwriteLocalData) { - qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now); - qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); - } - #endif - - - // Property Flags - QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size - EntityPropertyFlags propertyFlags = encodedPropertyFlags; - dataAt += propertyFlags.getEncodedLength(); - bytesRead += propertyFlags.getEncodedLength(); - bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS); + } else { if (useMeters) { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); } else { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); - } - - // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS - if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { - if (propertyFlags.getHasProperty(PROP_RADIUS)) { - float fromBuffer; - memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); - dataAt += sizeof(fromBuffer); - bytesRead += sizeof(fromBuffer); - if (overwriteLocalData) { - setRadius(fromBuffer); - } - } - } else { - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); - } else { - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); - } - } - - READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); - READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); - } else { - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); - } - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); - } - - READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); - READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); - READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); - READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); - READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); - READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); - } else { - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); - } - READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); - READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); - READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); - READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); - READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); - READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); - - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); - } - - if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - } - - READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); - READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); - bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); - - //////////////////////////////////// - // WARNING: Do not add stream content here after the subclass. Always add it before the subclass - // - // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover - // by doing this parsing here... but it's not likely going to fully recover the content. - // - // TODO: Remove this conde once we've sufficiently migrated content past this damaged version - if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - } - - if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { - // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more - // closely match where the entities should be if they'd stepped forward in time to "now". The server - // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore - // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and - // use our simulation helper routine to get a best estimate of where the entity should be. - const float MIN_TIME_SKIP = 0.0f; - const float MAX_TIME_SKIP = 1.0f; // in seconds - float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND), - MIN_TIME_SKIP, MAX_TIME_SKIP); - if (skipTimeForward > 0.0f) { - #ifdef WANT_DEBUG - qCDebug(entities) << "skipTimeForward:" << skipTimeForward; - #endif - - // we want to extrapolate the motion forward to compensate for packet travel time, but - // we don't want the side effect of flag setting. - simulateKinematicMotion(skipTimeForward, false); - } - _lastSimulated = now; + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); } } + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); + READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); + } else { + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); + } + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); + } + + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); + } else { + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); + } + READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); + READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); + READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); + READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); + READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + // we always accept the server's notion of simulatorID, so we fake overwriteLocalData as true + // before we try to READ_ENTITY_PROPERTY it + bool temp = overwriteLocalData; + overwriteLocalData = true; + READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); + overwriteLocalData = temp; + } + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + } + + READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); + READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); + + //////////////////////////////////// + // WARNING: Do not add stream content here after the subclass. Always add it before the subclass + // + // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover + // by doing this parsing here... but it's not likely going to fully recover the content. + // + // TODO: Remove this conde once we've sufficiently migrated content past this damaged version + if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + } + + if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { + // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more + // closely match where the entities should be if they'd stepped forward in time to "now". The server + // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore + // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and + // use our simulation helper routine to get a best estimate of where the entity should be. + const float MIN_TIME_SKIP = 0.0f; + const float MAX_TIME_SKIP = 1.0f; // in seconds + float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND), + MIN_TIME_SKIP, MAX_TIME_SKIP); + if (skipTimeForward > 0.0f) { + #ifdef WANT_DEBUG + qCDebug(entities) << "skipTimeForward:" << skipTimeForward; + #endif + + // we want to extrapolate the motion forward to compensate for packet travel time, but + // we don't want the side effect of flag setting. + simulateKinematicMotion(skipTimeForward, false); + } + _lastSimulated = now; + } auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); - if (_simulatorID == myNodeID && !_simulatorID.isNull()) { - // the packet that produced this bitstream originally came from physics simulations performed by - // this node, so our version has to be newer than what the packet contained. + if (overwriteLocalData && _simulatorID == myNodeID && !_simulatorID.isNull()) { + // we own the simulation, so we keep our transform+velocities and remove any related dirty flags + // rather than accept the values in the packet _position = savePosition; _rotation = saveRotation; - // _velocity = saveVelocity; - // _angularVelocity = saveAngularVelocity; - // _gravity = saveGravity; - // _acceleration = saveAcceleration; + _velocity = saveVelocity; + _angularVelocity = saveAngularVelocity; + _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); } return bytesRead; @@ -948,40 +948,25 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); if (somethingChanged) { - somethingChangedNotification(); // notify derived classes that something has changed uint64_t now = usecTimestampNow(); #ifdef WANT_DEBUG int elapsed = now - getLastEdited(); qCDebug(entities) << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " getLastEdited()=" << getLastEdited(); #endif - if (_created != UNKNOWN_CREATED_TIME) { - setLastEdited(now); + setLastEdited(now); + somethingChangedNotification(); // notify derived classes that something has changed + if (_created == UNKNOWN_CREATED_TIME) { + _created = now; } if (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES)) { - // TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases. + // anything that sets the transform or velocity must update _lastSimulated which is used + // for kinematic extrapolation (e.g. we want to extrapolate forward from this moment + // when position and/or velocity was changed). _lastSimulated = now; } } - // timestamps - quint64 timestamp = properties.getCreated(); - if (_created == UNKNOWN_CREATED_TIME && timestamp != UNKNOWN_CREATED_TIME) { - quint64 now = usecTimestampNow(); - if (timestamp > now) { - timestamp = now; - } - _created = timestamp; - - timestamp = properties.getLastEdited(); - if (timestamp > now) { - timestamp = now; - } else if (timestamp < _created) { - timestamp = _created; - } - _lastEdited = timestamp; - } - return somethingChanged; } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 6c4b3dad75..9a24aabb34 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -132,7 +132,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP; _entity->simulateKinematicMotion(dt); - _entity->setLastSimulated(usecTimestampNow()); // bypass const-ness so we can remember the step const_cast(this)->_lastKinematicStep = thisStep; @@ -401,13 +400,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); - // we only update lastEdited when we're sending new physics data - quint64 lastSimulated = _entity->getLastSimulated(); - _entity->setLastEdited(lastSimulated); - properties.setLastEdited(lastSimulated); + // set the LastEdited of the properties but NOT the entity itself + quint64 now = usecTimestampNow(); + properties.setLastEdited(now); #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); + quint64 lastSimulated = _entity->getLastSimulated(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------";