From fee13d4375c53688f3b3beee2f09b641244e86d4 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 17 Nov 2015 20:06:25 -0800 Subject: [PATCH 01/29] Rebuild on a new branch after original stopped building. --- interface/resources/qml/Stats.qml | 2 +- interface/src/Application.cpp | 19 +++++++++++- interface/src/Application.h | 6 ++++ interface/src/avatar/Avatar.cpp | 34 ++++++++++++++++++-- interface/src/avatar/Avatar.h | 3 ++ interface/src/avatar/AvatarManager.cpp | 43 ++++++++++++++++++++++++++ interface/src/avatar/AvatarManager.h | 19 ++++++++++++ interface/src/ui/Stats.cpp | 2 ++ interface/src/ui/Stats.h | 4 +++ 9 files changed, 127 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index c98d4741b0..336b8ff577 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -40,7 +40,7 @@ Item { Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Avatars: " + root.avatarCount + text: "Avatars: " + root.avatarCount + ", " + root.avatarRenderedCount + " w/in " + root.avatarRenderDistance + "m" } Text { color: root.fontColor; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 96b8ab74a8..f33cd763ca 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1061,8 +1061,10 @@ void Application::paintGL() { uint64_t now = usecTimestampNow(); static uint64_t lastPaintBegin{ now }; uint64_t diff = now - lastPaintBegin; + float instantaneousFps = 0.0f; if (diff != 0) { - _framesPerSecond.updateAverage((float)USECS_PER_SECOND / (float)diff); + instantaneousFps = (float)USECS_PER_SECOND / (float)diff; + _framesPerSecond.updateAverage(_lastInstantaneousFps); } lastPaintBegin = now; @@ -1326,6 +1328,7 @@ void Application::paintGL() { // Ensure all operations from the previous context are complete before we try to read the fbo glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); glDeleteSync(sync); + uint64_t displayStart = usecTimestampNow(); { PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); @@ -1338,6 +1341,20 @@ void Application::paintGL() { PerformanceTimer perfTimer("bufferSwap"); displayPlugin->finishFrame(); } + uint64_t displayEnd = usecTimestampNow(); + // Store together, without Application::idle happening in between setting fps and period. + _lastInstantaneousFps = instantaneousFps; + const float displayPeriodUsec = (float)(displayEnd - displayStart); // usecs + _lastPaintWait = displayPeriodUsec / (float)USECS_PER_SECOND; + const float targetPeriod = isHMDMode() ? 1.0f/75.0f : 1.0f/60.0f; + const float actualPeriod = 1.0f / instantaneousFps; + const float nSyncsByFrameRate = round(actualPeriod / targetPeriod); + const float accuracyAllowance = 0.0005; + const float nSyncsByPaintWait = floor((_lastPaintWait + accuracyAllowance) / targetPeriod); // sometimes paint goes over and it isn't reflected in actualPeriod + const float modularPeriod = (nSyncsByFrameRate + nSyncsByPaintWait) * targetPeriod; + const float deducedNonVSyncPeriod = modularPeriod - _lastPaintWait; + _lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod; + } { diff --git a/interface/src/Application.h b/interface/src/Application.h index 39e3879707..01d0c72645 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -159,6 +159,9 @@ public: bool isForeground() const { return _isForeground; } float getFps() const { return _fps; } + float getLastInstanteousFps() const { return _lastInstantaneousFps; } + float getLastPaintWait() const { return _lastPaintWait; }; + float getLastDeducedNonVSyncFps() const { return _lastDeducedNonVSyncFps; } float getFieldOfView() { return _fieldOfView.get(); } void setFieldOfView(float fov); @@ -430,6 +433,9 @@ private: float _fps; QElapsedTimer _timerStart; QElapsedTimer _lastTimeUpdated; + float _lastInstantaneousFps { 0.0f }; + float _lastPaintWait { 0.0f }; + float _lastDeducedNonVSyncFps { 0.0f }; ShapeManager _shapeManager; PhysicalEntitySimulation _entitySimulation; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b979334383..14824ad118 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -97,6 +97,7 @@ Avatar::Avatar(RigPointer rig) : _moving(false), _initialized(false), _shouldRenderBillboard(true), + _shouldSkipRender(true), _voiceSphereID(GeometryCache::UNKNOWN_ID) { // we may have been created in the network thread, but we live in the main thread @@ -114,7 +115,7 @@ Avatar::~Avatar() { } } -const float BILLBOARD_LOD_DISTANCE = 40.0f; +/*const fixme*/ float BILLBOARD_LOD_DISTANCE = 40.0f; void Avatar::init() { getHead()->init(); @@ -181,12 +182,36 @@ void Avatar::simulate(float deltaTime) { // update the billboard render flag const float BILLBOARD_HYSTERESIS_PROPORTION = 0.1f; + BILLBOARD_LOD_DISTANCE = DependencyManager::get()->getFIXMEupdate(); if (_shouldRenderBillboard) { if (getLODDistance() < BILLBOARD_LOD_DISTANCE * (1.0f - BILLBOARD_HYSTERESIS_PROPORTION)) { _shouldRenderBillboard = false; + qCDebug(interfaceapp) << "Unbillboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); } } else if (getLODDistance() > BILLBOARD_LOD_DISTANCE * (1.0f + BILLBOARD_HYSTERESIS_PROPORTION)) { _shouldRenderBillboard = true; + qCDebug(interfaceapp) << "Billboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); + } +#define PID_TUNING 1 +#ifdef PID_TUNING + const float SKIP_HYSTERESIS_PROPORTION = 0.0f; +#else + const float SKIP_HYSTERESIS_PROPORTION = BILLBOARD_HYSTERESIS_PROPORTION; +#endif + float renderDistance = DependencyManager::get()->getRenderDistance(); + float distance = glm::distance(qApp->getCamera()->getPosition(), _position); + if (_shouldSkipRender) { + if (distance < renderDistance * (1.0f - SKIP_HYSTERESIS_PROPORTION)) { + _shouldSkipRender = false; +#ifndef PID_TUNING + qCDebug(interfaceapp) << "Rerendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); +#endif + } + } else if (distance > renderDistance * (1.0f + SKIP_HYSTERESIS_PROPORTION)) { + _shouldSkipRender = true; +#ifndef PID_TUNING + qCDebug(interfaceapp) << "Unrendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); +#endif } // simple frustum check @@ -199,7 +224,7 @@ void Avatar::simulate(float deltaTime) { getHand()->simulate(deltaTime, false); } - if (!_shouldRenderBillboard && inViewFrustum) { + if (!_shouldRenderBillboard && !_shouldSkipRender && inViewFrustum) { { PerformanceTimer perfTimer("skeleton"); for (int i = 0; i < _jointData.size(); i++) { @@ -585,10 +610,13 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { } void Avatar::fixupModelsInScene() { - // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene render::ScenePointer scene = qApp->getMain3DScene(); + _skeletonModel.setVisibleInScene(!_shouldSkipRender, scene); + if (_shouldSkipRender) { + return; + } render::PendingChanges pendingChanges; if (_skeletonModel.isRenderable() && _skeletonModel.needsFixupInScene()) { _skeletonModel.removeFromScene(scene, pendingChanges); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 44b5d91015..cb238dc82c 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -140,6 +140,8 @@ public: Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; } Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; } + Q_INVOKABLE bool getShouldSkipRendering() const { return _shouldSkipRender; } + /// Scales a world space position vector relative to the avatar position and scale /// \param vector position to be scaled. Will store the result void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const; @@ -226,6 +228,7 @@ private: bool _initialized; NetworkTexturePointer _billboardTexture; bool _shouldRenderBillboard; + bool _shouldSkipRender { false }; bool _isLookAtTarget; void renderBillboard(RenderArgs* renderArgs); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 4b9bfd21a3..b2a2e27999 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -61,12 +61,34 @@ void AvatarManager::registerMetaTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType >(engine); } +#define TARGET_FPS 60.0f +#define TARGET_PERIOD_MS (1000.0f / TARGET_FPS) AvatarManager::AvatarManager(QObject* parent) : _avatarFades() { // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType >("NodeWeakPointer"); _myAvatar = std::make_shared(std::make_shared()); + _renderDistanceController.setMeasuredValueSetpoint(TARGET_FPS); //FIXME + const float TREE_SCALE = 32768.0f; // Not in shared library, alas. + const float SMALLEST_REASONABLE_HORIZON = 0.5f; // FIXME 5 + _renderDistanceController.setControlledValueHighLimit(1.0f/SMALLEST_REASONABLE_HORIZON); + _renderDistanceController.setControlledValueLowLimit(1.0f/TREE_SCALE); + + // Advice for tuning parameters: + // See PIDController.h. There's a sectionon tuning in the reference. + // Turn off HYSTERESIS_PROPORTION and extra logging by defining PID_TUNING in Avatar.cpp. + // Turn on logging with the following: + _renderDistanceController.setHistorySize("avatar render", TARGET_FPS * 4); // FIXME + // KP is usually tuned by setting the other constants to zero, finding the maximum value that doesn't oscillate, + // and taking about 0.6 of that. A typical osciallation would be with error=37fps with avatars 10m away, so + // KP*37=1/10 => KP(oscillating)=0.1/37 = 0.0027 + _renderDistanceController.setKP(0.0012f); + // alt: + // Our anti-windup limits accumulated error to 10*targetFrameRate, so the sanity check on KI is + // KI*750=controlledValueHighLimit=1 => KI=1/750. + _renderDistanceController.setKI(0.001); + _renderDistanceController.setKD(0.0001); // a touch of kd increases the speed by which we get there auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); @@ -117,9 +139,26 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); PerformanceTimer perfTimer("otherAvatars"); + const float FEED_FORWARD_RANGE = 2; + const float fps = qApp->getLastInstanteousFps(); + const float paintWait = qApp->getLastPaintWait(); + //const float modularizedPeriod = floor((1000.0f / std::min(fps, TARGET_FPS)) / TARGET_PERIOD_MS) * TARGET_PERIOD_MS; + // measured value: 1) bigger => more desirable plant activity (i.e., more rendering), 2) setpoint=TARGET_PERIOD_MS=13.333 + // single vsync: no load=>1or2. high load=>12or13 + // over vsync: just over: 13. way over: 14...15...16 + //const float effective = ((1000.0f / fps) < TARGET_PERIOD_MS) ? (TARGET_PERIOD_MS - paintWait) : ((2.0f * TARGET_PERIOD_MS) - paintWait); + const float deduced = qApp->getLastDeducedNonVSyncFps(); + const bool isAtSetpoint = false; //FIXME fabsf(effectiveFps - _renderDistanceController.getMeasuredValueSetpoint()) < FEED_FORWARD_RANGE; + const float distance = 1.0f / _renderDistanceController.update(deduced + (isAtSetpoint ? _renderFeedForward : 0.0f), deltaTime, isAtSetpoint, fps, paintWait); + + const float RENDER_DISTANCE_DEADBAND = 0.0f; //FIXME 0.3f; // meters + if (fabsf(distance - _renderDistance) > RENDER_DISTANCE_DEADBAND) { + _renderDistance = distance; + } // simulate avatars AvatarHash::iterator avatarIterator = _avatarHash.begin(); + int renderableCount = 0; while (avatarIterator != _avatarHash.end()) { auto avatar = std::dynamic_pointer_cast(avatarIterator.value()); @@ -135,10 +174,14 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } else { avatar->startUpdate(); avatar->simulate(deltaTime); + if (!avatar->getShouldSkipRendering()) { + renderableCount++; + } avatar->endUpdate(); ++avatarIterator; } } + _renderedAvatarCount = renderableCount; // simulate avatar fades simulateAvatarFades(deltaTime); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index fa0593368b..92f9958962 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -18,6 +18,7 @@ #include #include +#include #include "Avatar.h" #include "AvatarMotionState.h" @@ -43,6 +44,7 @@ public: void clearOtherAvatars(); bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; } + PIDController& getRenderDistanceController() { return _renderDistanceController; } class LocalLight { public: @@ -64,6 +66,17 @@ public: void handleCollisionEvents(const CollisionEvents& collisionEvents); void updateAvatarPhysicsShape(const QUuid& id); + Q_INVOKABLE int getNumberRendered() { return _renderedAvatarCount; } + Q_INVOKABLE float getRenderDistance() { return _renderDistance; } + Q_INVOKABLE float getFIXMEupdate() { return _fixmeUpdate; } + Q_INVOKABLE void setFIXMEupdate(float f) { _fixmeUpdate = f; } + Q_INVOKABLE void setFIXMEkp(float f) { _renderDistanceController.setKP(f); } + Q_INVOKABLE void setFIXMEki(float f) { _renderDistanceController.setKI(f); } + Q_INVOKABLE void setFIXMEkd(float f) { _renderDistanceController.setKD(f); } + Q_INVOKABLE void setFIXMEbias(float f) { _renderDistanceController.setBias(f); } + Q_INVOKABLE void setFIXMElow(float f) { _renderDistanceController.setControlledValueLowLimit(f); } + Q_INVOKABLE void setFIXMEhigh(float f) { _renderDistanceController.setControlledValueHighLimit(f); } + Q_INVOKABLE void setFIXMEfeedForward(float f) { _renderFeedForward = f; } public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } @@ -88,6 +101,12 @@ private: QVector _localLights; bool _shouldShowReceiveStats = false; + float _fixmeUpdate = 40.0f; + float _renderDistance { 40.0f }; + float _renderFeedForward { 5.0f }; + int _renderedAvatarCount {0}; + PIDController _renderDistanceController {}; + SetOfAvatarMotionStates _avatarMotionStates; SetOfMotionStates _motionStatesToAdd; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 1c0c03e16c..3f27052f60 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -115,6 +115,8 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); + STAT_UPDATE(avatarRenderedCount, avatarManager->getNumberRendered()); + STAT_UPDATE(avatarRenderDistance, (int) round(avatarManager->getRenderDistance())); // deliberately truncating STAT_UPDATE(serverCount, nodeList->size()); STAT_UPDATE(framerate, (int)qApp->getFps()); STAT_UPDATE(simrate, (int)qApp->getAverageSimsPerSecond()); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 39e3c6b24a..12caf19d18 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -36,6 +36,8 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, simrate, 0) STATS_PROPERTY(int, avatarSimrate, 0) STATS_PROPERTY(int, avatarCount, 0) + STATS_PROPERTY(int, avatarRenderedCount, 0) + STATS_PROPERTY(int, avatarRenderDistance, 0) STATS_PROPERTY(int, packetInCount, 0) STATS_PROPERTY(int, packetOutCount, 0) STATS_PROPERTY(float, mbpsIn, 0) @@ -117,6 +119,8 @@ signals: void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); + void avatarRenderedCountChanged(); + void avatarRenderDistanceChanged(); void packetInCountChanged(); void packetOutCountChanged(); void mbpsInChanged(); From 97898a1ad1a4ce6972c52fe5dbf1736a5b34acc6 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 17 Nov 2015 20:43:40 -0800 Subject: [PATCH 02/29] forgot new files --- libraries/shared/src/PIDController.cpp | 71 ++++++++++++++++++++ libraries/shared/src/PIDController.h | 91 ++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 libraries/shared/src/PIDController.cpp create mode 100644 libraries/shared/src/PIDController.h diff --git a/libraries/shared/src/PIDController.cpp b/libraries/shared/src/PIDController.cpp new file mode 100644 index 0000000000..42447e9809 --- /dev/null +++ b/libraries/shared/src/PIDController.cpp @@ -0,0 +1,71 @@ +// +// PIDController.cpp +// libraries/shared/src +// +// Created by Howard Stearns 11/13/15. +// 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 "SharedLogging.h" +#include "PIDController.h" + +float PIDController::update(float measuredValue, float dt, bool resetAccumulator, float FIXME1, float FIXME2) { + const float error = getMeasuredValueSetpoint() - measuredValue; // Sign is the direction we want measuredValue to go. Positive means go higher. + + const float p = getKP() * error; // term is Proportional to error + + const float accumulatedError = glm::clamp(error * dt + (resetAccumulator ? 0 : _lastAccumulation), // integrate error + getAccumulatedValueLowLimit(), // but clamp by anti-windup limits + getAccumulatedValueHighLimit()); + const float i = getKI() * accumulatedError; // term is Integral of error + + const float changeInError = (error - _lastError) / dt; // positive value denotes increasing deficit + const float d = getKD() * changeInError; // term is Derivative of Error + + const float computedValue = glm::clamp(p + i + d + getBias(), + getControlledValueLowLimit(), + getControlledValueHighLimit()); + + if (_history.capacity()) { // THIS SECTION ONLY FOR LOGGING + // Don't report each update(), as the I/O messes with the results a lot. + // Instead, add to history, and then dump out at once when full. + // Typically, the first few values reported in each batch should be ignored. + const int n = _history.size(); + _history.resize(n + 1); + Row& next = _history[n]; + next.measured = measuredValue; + next.FIXME1 = FIXME1; + next.FIXME2 = FIXME2; + next.dt = dt; + next.error = error; + next.accumulated = accumulatedError; + next.changed = changeInError; + next.p = p; + next.i = i; + next.d = d; + next.computed = computedValue; + if (_history.size() == _history.capacity()) { // report when buffer is full + qCDebug(shared) << _label << "measured dt FIXME || error accumulated changed || p i d controlled"; + for (int i = 0; i < _history.size(); i++) { + Row& row = _history[i]; + qCDebug(shared) << row.measured << row.dt << row.FIXME1 << row.FIXME2 << + "||" << row.error << row.accumulated << row.changed << + "||" << row.p << row.i << row.d << row.computed; + } + qCDebug(shared) << "Limits: accumulate" << getAccumulatedValueLowLimit() << getAccumulatedValueHighLimit() << + "controlled" << getControlledValueLowLimit() << getControlledValueHighLimit() << + "kp/ki/kd/bias" << getKP() << getKI() << getKD() << getBias(); + _history.resize(0); + } + } + + // update state for next time + _lastError = error; + _lastAccumulation = accumulatedError; + return computedValue; +} \ No newline at end of file diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h new file mode 100644 index 0000000000..dd0c4a8528 --- /dev/null +++ b/libraries/shared/src/PIDController.h @@ -0,0 +1,91 @@ +// +// PIDController.h +// libraries/shared/src +// +// Given a measure of system performance (such as frame rate, where bigger denotes more system work), +// compute a value that the system can take as input to control the amount of work done (such as an 1/LOD-distance, +// where bigger tends to give a higher measured system performance value). The controller's job is to compute a +// controlled value such that the measured value stays near the specified setpoint, even as system load changes. +// See http://www.wetmachine.com/inventing-the-future/mostly-reliable-performance-of-software-processes-by-dynamic-control-of-quality-parameters/ +// +// Created by Howard Stearns 11/13/15. +// 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_PIDController_h +#define hifi_PIDController_h + +#include +#include + +// Although our coding standard shuns abbreviations, the control systems literature pretty uniformly uses p, i, d, and dt rather than +// proportionalTerm, integralTerm, derivativeTerm, and deltaTime. Here we will be consistent with the literature. +class PIDController { + +public: + // These three are the main interface: + void setMeasuredValueSetpoint(float newValue) { _measuredValueSetpoint = newValue; } + float update(float measuredValue, float dt, bool resetAccumulator = false, float FIXME1 = 0.0f, float FIXME2 = 0.0f); // returns the new computedValue + void setHistorySize(QString label = QString(""), int size = 0) { _history.reserve(size); _history.resize(0); _label = label; } // non-empty does logging + + // There are several values that rarely change and might be thought of as "constants", but which do change during tuning, debugging, or other + // special-but-expected circumstances. Thus the instance vars are not const. + float getMeasuredValueSetpoint() const { return _measuredValueSetpoint; } + // In normal operation (where we can easily reach setpoint), controlledValue is typcially pinned at max. + // Defaults to [0, max float], but for 1/LODdistance, it might be, say, [0, 0.2 or 0.1] + float getControlledValueLowLimit() const { return _controlledValueLowLimit; } + float getControlledValueHighLimit() const { return _controlledValueHighLimit; } + float getAntiWindupFactor() const { return _antiWindupFactor; } // default 10 + float getKP() const { return _kp; } + float getKI() const { return _ki; } + float getKD() const { return _kd; } + float getBias() const { return _bias; } + float getAccumulatedValueHighLimit() const { return getAntiWindupFactor() * getMeasuredValueSetpoint(); } + float getAccumulatedValueLowLimit() const { return -getAntiWindupFactor() * getMeasuredValueSetpoint(); } + + void setControlledValueLowLimit(float newValue) { _controlledValueLowLimit = newValue; } + void setControlledValueHighLimit(float newValue) { _controlledValueHighLimit = newValue; } + void setAntiWindupFactor(float newValue) { _antiWindupFactor = newValue; } + void setKP(float newValue) { _kp = newValue; } + void setKI(float newValue) { _ki = newValue; } + void setKD(float newValue) { _kd = newValue; } + void setBias(float newValue) { _bias = newValue; } + + class Row { // one row of accumulated history, used only for logging (if at all) + public: + float FIXME1; + float FIXME2; + float measured; + float dt; + float error; + float accumulated; + float changed; + float p; + float i; + float d; + float bias; + float computed; + }; +protected: + float _measuredValueSetpoint { 0.0f }; + float _controlledValueLowLimit { 0.0f }; + float _controlledValueHighLimit { std::numeric_limits::max() }; + float _antiWindupFactor { 10.0f }; + float _kp { 0.0f }; + float _ki { 0.0f }; + float _kd { 0.0f }; + float _bias { 0.0f }; + + // Controller state + float _lastError{ 0.0f }; + float _lastAccumulation{ 0.0f }; + // reporting + QVector _history{}; + QString _label{ "" }; + +}; + +#endif // hifi_PIDController_h From 42a1ee353e2acddf174ba057c65a9aa299e9646b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 18 Nov 2015 14:09:33 -0800 Subject: [PATCH 03/29] checkpoint --- interface/src/Application.cpp | 25 +++++---- interface/src/avatar/AvatarManager.cpp | 53 +++++++++---------- libraries/shared/src/PIDController.cpp | 72 ++++++++++++++------------ libraries/shared/src/PIDController.h | 2 + 4 files changed, 82 insertions(+), 70 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f33cd763ca..2eaf898e72 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1095,6 +1095,21 @@ void Application::paintGL() { _inPaint = true; Finally clearFlagLambda([this] { _inPaint = false; }); + _lastInstantaneousFps = instantaneousFps; + // Time between previous paintGL call and this one, which can vary not only with vSync misses, but also with QT timing. + // This is not the same as update(deltaTime), because the latter attempts to throttle to 60hz and also clamps to 1/4 second. + // Note that _lastPaintWait (stored at end of last call) is for the same paint cycle. + const float actualPeriod = diff / (float)USECS_PER_SECOND; // same as 1/instantaneousFps but easier for compiler to optimize + const float targetPeriod = isHMDMode() ? 1.0f / 75.0f : 1.0f / 60.0f; + const float nSyncsByFrameRate = round(actualPeriod / targetPeriod); + const float accuracyAllowance = 0.0005f; // sometimes paint goes over and it isn't reflected in actualPeriod + const float nSyncsByPaintWait = floor((_lastPaintWait + accuracyAllowance) / targetPeriod); + const float nSyncs = nSyncsByFrameRate + nSyncsByPaintWait; + const float modularPeriod = ((nSyncs - 1) * targetPeriod) + actualPeriod; + const float deducedNonVSyncPeriod = modularPeriod - _lastPaintWait; + _lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod; + + auto displayPlugin = getActiveDisplayPlugin(); displayPlugin->preRender(); _offscreenContext->makeCurrent(); @@ -1342,18 +1357,8 @@ void Application::paintGL() { displayPlugin->finishFrame(); } uint64_t displayEnd = usecTimestampNow(); - // Store together, without Application::idle happening in between setting fps and period. - _lastInstantaneousFps = instantaneousFps; const float displayPeriodUsec = (float)(displayEnd - displayStart); // usecs _lastPaintWait = displayPeriodUsec / (float)USECS_PER_SECOND; - const float targetPeriod = isHMDMode() ? 1.0f/75.0f : 1.0f/60.0f; - const float actualPeriod = 1.0f / instantaneousFps; - const float nSyncsByFrameRate = round(actualPeriod / targetPeriod); - const float accuracyAllowance = 0.0005; - const float nSyncsByPaintWait = floor((_lastPaintWait + accuracyAllowance) / targetPeriod); // sometimes paint goes over and it isn't reflected in actualPeriod - const float modularPeriod = (nSyncsByFrameRate + nSyncsByPaintWait) * targetPeriod; - const float deducedNonVSyncPeriod = modularPeriod - _lastPaintWait; - _lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b2a2e27999..18e4dd40be 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -61,34 +61,12 @@ void AvatarManager::registerMetaTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType >(engine); } -#define TARGET_FPS 60.0f -#define TARGET_PERIOD_MS (1000.0f / TARGET_FPS) AvatarManager::AvatarManager(QObject* parent) : _avatarFades() { // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType >("NodeWeakPointer"); _myAvatar = std::make_shared(std::make_shared()); - _renderDistanceController.setMeasuredValueSetpoint(TARGET_FPS); //FIXME - const float TREE_SCALE = 32768.0f; // Not in shared library, alas. - const float SMALLEST_REASONABLE_HORIZON = 0.5f; // FIXME 5 - _renderDistanceController.setControlledValueHighLimit(1.0f/SMALLEST_REASONABLE_HORIZON); - _renderDistanceController.setControlledValueLowLimit(1.0f/TREE_SCALE); - - // Advice for tuning parameters: - // See PIDController.h. There's a sectionon tuning in the reference. - // Turn off HYSTERESIS_PROPORTION and extra logging by defining PID_TUNING in Avatar.cpp. - // Turn on logging with the following: - _renderDistanceController.setHistorySize("avatar render", TARGET_FPS * 4); // FIXME - // KP is usually tuned by setting the other constants to zero, finding the maximum value that doesn't oscillate, - // and taking about 0.6 of that. A typical osciallation would be with error=37fps with avatars 10m away, so - // KP*37=1/10 => KP(oscillating)=0.1/37 = 0.0027 - _renderDistanceController.setKP(0.0012f); - // alt: - // Our anti-windup limits accumulated error to 10*targetFrameRate, so the sanity check on KI is - // KI*750=controlledValueHighLimit=1 => KI=1/750. - _renderDistanceController.setKI(0.001); - _renderDistanceController.setKD(0.0001); // a touch of kd increases the speed by which we get there auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); @@ -112,6 +90,29 @@ void AvatarManager::init() { _myAvatar->addToScene(_myAvatar, scene, pendingChanges); } scene->enqueuePendingChanges(pendingChanges); + + const float target_fps = 75.0f; // qApp->isHMDMode() ? 75.0f : 60.0f; + _renderDistanceController.setMeasuredValueSetpoint(target_fps); + const float TREE_SCALE = 32768.0f; // Not in shared library, alas. + const float SMALLEST_REASONABLE_HORIZON = 0.5f; // FIXME 5 + _renderDistanceController.setControlledValueHighLimit(1.0f / SMALLEST_REASONABLE_HORIZON); + _renderDistanceController.setControlledValueLowLimit(1.0f / TREE_SCALE); + + // Advice for tuning parameters: + // See PIDController.h. There's a sectionon tuning in the reference. + // Turn off HYSTERESIS_PROPORTION and extra logging by defining PID_TUNING in Avatar.cpp. + // Turn on logging with the following: + _renderDistanceController.setHistorySize("avatar render", target_fps * 4); // FIXME + // KP is usually tuned by setting the other constants to zero, finding the maximum value that doesn't oscillate, + // and taking about 0.6 of that. A typical osciallation would be with error=37fps with avatars 10m away, so + // KP*37=1/10 => KP(oscillating)=0.1/37 = 0.0027 + _renderDistanceController.setKP(0.0015f); + // alt: + // Our anti-windup limits accumulated error to 10*targetFrameRate, so the sanity check on KI is + // KI*750=controlledValueHighLimit=1 => KI=1/750. + _renderDistanceController.setKI(0.001f); + _renderDistanceController.setKD(0.0001f); // a touch of kd increases the speed by which we get there + } void AvatarManager::updateMyAvatar(float deltaTime) { @@ -142,14 +143,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const float FEED_FORWARD_RANGE = 2; const float fps = qApp->getLastInstanteousFps(); const float paintWait = qApp->getLastPaintWait(); - //const float modularizedPeriod = floor((1000.0f / std::min(fps, TARGET_FPS)) / TARGET_PERIOD_MS) * TARGET_PERIOD_MS; - // measured value: 1) bigger => more desirable plant activity (i.e., more rendering), 2) setpoint=TARGET_PERIOD_MS=13.333 - // single vsync: no load=>1or2. high load=>12or13 - // over vsync: just over: 13. way over: 14...15...16 - //const float effective = ((1000.0f / fps) < TARGET_PERIOD_MS) ? (TARGET_PERIOD_MS - paintWait) : ((2.0f * TARGET_PERIOD_MS) - paintWait); const float deduced = qApp->getLastDeducedNonVSyncFps(); const bool isAtSetpoint = false; //FIXME fabsf(effectiveFps - _renderDistanceController.getMeasuredValueSetpoint()) < FEED_FORWARD_RANGE; - const float distance = 1.0f / _renderDistanceController.update(deduced + (isAtSetpoint ? _renderFeedForward : 0.0f), deltaTime, isAtSetpoint, fps, paintWait); + //const float distance = 1.0f / _renderDistanceController.update(deduced + (isAtSetpoint ? _renderFeedForward : 0.0f), deltaTime, isAtSetpoint, fps, paintWait); + const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime, isAtSetpoint, fps, paintWait); const float RENDER_DISTANCE_DEADBAND = 0.0f; //FIXME 0.3f; // meters if (fabsf(distance - _renderDistance) > RENDER_DISTANCE_DEADBAND) { diff --git a/libraries/shared/src/PIDController.cpp b/libraries/shared/src/PIDController.cpp index 42447e9809..2756ab615f 100644 --- a/libraries/shared/src/PIDController.cpp +++ b/libraries/shared/src/PIDController.cpp @@ -31,41 +31,49 @@ float PIDController::update(float measuredValue, float dt, bool resetAccumulator getControlledValueLowLimit(), getControlledValueHighLimit()); - if (_history.capacity()) { // THIS SECTION ONLY FOR LOGGING - // Don't report each update(), as the I/O messes with the results a lot. - // Instead, add to history, and then dump out at once when full. - // Typically, the first few values reported in each batch should be ignored. - const int n = _history.size(); - _history.resize(n + 1); - Row& next = _history[n]; - next.measured = measuredValue; - next.FIXME1 = FIXME1; - next.FIXME2 = FIXME2; - next.dt = dt; - next.error = error; - next.accumulated = accumulatedError; - next.changed = changeInError; - next.p = p; - next.i = i; - next.d = d; - next.computed = computedValue; - if (_history.size() == _history.capacity()) { // report when buffer is full - qCDebug(shared) << _label << "measured dt FIXME || error accumulated changed || p i d controlled"; - for (int i = 0; i < _history.size(); i++) { - Row& row = _history[i]; - qCDebug(shared) << row.measured << row.dt << row.FIXME1 << row.FIXME2 << - "||" << row.error << row.accumulated << row.changed << - "||" << row.p << row.i << row.d << row.computed; - } - qCDebug(shared) << "Limits: accumulate" << getAccumulatedValueLowLimit() << getAccumulatedValueHighLimit() << - "controlled" << getControlledValueLowLimit() << getControlledValueHighLimit() << - "kp/ki/kd/bias" << getKP() << getKI() << getKD() << getBias(); - _history.resize(0); - } - } + if (_history.capacity()) { // if logging/reporting + updateHistory(measuredValue, dt, error, accumulatedError, changeInError, p, i, d, computedValue, FIXME1, FIXME2); + } // update state for next time _lastError = error; _lastAccumulation = accumulatedError; return computedValue; +} + +// Just for logging/reporting. Used when picking/verifying the operational parameters. +void PIDController::updateHistory(float measuredValue, float dt, float error, float accumulatedError, float changeInError, float p, float i, float d, float computedValue, float FIXME1, float FIXME2) { + // Don't report each update(), as the I/O messes with the results a lot. + // Instead, add to history, and then dump out at once when full. + // Typically, the first few values reported in each batch should be ignored. + const int n = _history.size(); + _history.resize(n + 1); + Row& next = _history[n]; + next.measured = measuredValue; + next.FIXME1 = FIXME1; + next.FIXME2 = FIXME2; + next.dt = dt; + next.error = error; + next.accumulated = accumulatedError; + next.changed = changeInError; + next.p = p; + next.i = i; + next.d = d; + next.computed = computedValue; + if (_history.size() == _history.capacity()) { // report when buffer is full + reportHistory(); + _history.resize(0); + } +} +void PIDController::reportHistory() { + qCDebug(shared) << _label << "measured dt FIXME || error accumulated changed || p i d controlled"; + for (int i = 0; i < _history.size(); i++) { + Row& row = _history[i]; + qCDebug(shared) << row.measured << (row.dt * 1000) << row.FIXME1 << (row.FIXME2 * 1000) << + "||" << row.error << row.accumulated << row.changed << + "||" << row.p << row.i << row.d << row.computed; + } + qCDebug(shared) << "Limits: setpoint" << getMeasuredValueSetpoint() << "accumulate" << getAccumulatedValueLowLimit() << getAccumulatedValueHighLimit() << + "controlled" << getControlledValueLowLimit() << getControlledValueHighLimit() << + "kp/ki/kd/bias" << getKP() << getKI() << getKD() << getBias(); } \ No newline at end of file diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h index dd0c4a8528..a2adbf98ab 100644 --- a/libraries/shared/src/PIDController.h +++ b/libraries/shared/src/PIDController.h @@ -70,6 +70,8 @@ public: float computed; }; protected: + void reportHistory(); + void updateHistory(float measured, float dt, float error, float accumulatedError, float changeInErro, float p, float i, float d, float computedValue, float FIXME1, float FIXME2); float _measuredValueSetpoint { 0.0f }; float _controlledValueLowLimit { 0.0f }; float _controlledValueHighLimit { std::numeric_limits::max() }; From 7d30cd0159bb8692514cdfb235c0e5814451e9c0 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 19 Nov 2015 09:18:40 -0800 Subject: [PATCH 04/29] checkpoint: it works! --- interface/src/Application.cpp | 22 +++++++++++++--------- interface/src/avatar/Avatar.cpp | 2 +- interface/src/avatar/AvatarManager.cpp | 26 +++++++------------------- interface/src/avatar/AvatarManager.h | 3 +++ libraries/shared/src/PIDController.cpp | 2 +- 5 files changed, 25 insertions(+), 30 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2eaf898e72..84b14497cd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1095,20 +1095,24 @@ void Application::paintGL() { _inPaint = true; Finally clearFlagLambda([this] { _inPaint = false; }); - _lastInstantaneousFps = instantaneousFps; + // Some LOD-like controls need to know a smoothly varying "potential" frame rate that doesn't + // include time waiting for vsync, and which can report a number above target if we've got the headroom. + // For example, if we're shooting for 75fps and paintWait is 3.3333ms (= 75% * 13.33ms), our deducedNonVSyncFps + // would be 100fps. In principle, a paintWait of zero would have deducedNonVSyncFps=75. + // Here we make a guess for deducedNonVSyncFps = 1 / deducedNonVSyncPeriod. + // // Time between previous paintGL call and this one, which can vary not only with vSync misses, but also with QT timing. // This is not the same as update(deltaTime), because the latter attempts to throttle to 60hz and also clamps to 1/4 second. - // Note that _lastPaintWait (stored at end of last call) is for the same paint cycle. const float actualPeriod = diff / (float)USECS_PER_SECOND; // same as 1/instantaneousFps but easier for compiler to optimize + // Note that _lastPaintWait (stored at end of last call) is for the same paint cycle. + float deducedNonVSyncPeriod = actualPeriod - _lastPaintWait; // plus a some non-zero time for machinery we can't measure + // We don't know how much time to allow for that, but if we went over the target period, we know it's at least the portion + // of paintWait up to the next vSync. const float targetPeriod = isHMDMode() ? 1.0f / 75.0f : 1.0f / 60.0f; - const float nSyncsByFrameRate = round(actualPeriod / targetPeriod); - const float accuracyAllowance = 0.0005f; // sometimes paint goes over and it isn't reflected in actualPeriod - const float nSyncsByPaintWait = floor((_lastPaintWait + accuracyAllowance) / targetPeriod); - const float nSyncs = nSyncsByFrameRate + nSyncsByPaintWait; - const float modularPeriod = ((nSyncs - 1) * targetPeriod) + actualPeriod; - const float deducedNonVSyncPeriod = modularPeriod - _lastPaintWait; + const float minumumMachinery = glm::max(0.0f, (floorf(_lastPaintWait / targetPeriod) * targetPeriod) - _lastPaintWait); + deducedNonVSyncPeriod += minumumMachinery; _lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod; - + _lastInstantaneousFps = instantaneousFps; auto displayPlugin = getActiveDisplayPlugin(); displayPlugin->preRender(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 14824ad118..6a634bcc90 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -192,7 +192,7 @@ void Avatar::simulate(float deltaTime) { _shouldRenderBillboard = true; qCDebug(interfaceapp) << "Billboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); } -#define PID_TUNING 1 +//#define PID_TUNING 1 #ifdef PID_TUNING const float SKIP_HYSTERESIS_PROPORTION = 0.0f; #else diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 18e4dd40be..132ddb9dfa 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -102,16 +102,10 @@ void AvatarManager::init() { // See PIDController.h. There's a sectionon tuning in the reference. // Turn off HYSTERESIS_PROPORTION and extra logging by defining PID_TUNING in Avatar.cpp. // Turn on logging with the following: - _renderDistanceController.setHistorySize("avatar render", target_fps * 4); // FIXME - // KP is usually tuned by setting the other constants to zero, finding the maximum value that doesn't oscillate, - // and taking about 0.6 of that. A typical osciallation would be with error=37fps with avatars 10m away, so - // KP*37=1/10 => KP(oscillating)=0.1/37 = 0.0027 - _renderDistanceController.setKP(0.0015f); - // alt: - // Our anti-windup limits accumulated error to 10*targetFrameRate, so the sanity check on KI is - // KI*750=controlledValueHighLimit=1 => KI=1/750. - _renderDistanceController.setKI(0.001f); - _renderDistanceController.setKD(0.0001f); // a touch of kd increases the speed by which we get there + //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); // FIXME + _renderDistanceController.setKP(0.0003f); //Usually about 0.6 of largest that doesn't oscillate, with other constants 0. + _renderDistanceController.setKI(0.001f); // Big enough to bring us to target with the above KP. + _renderDistanceController.setKD(0.00001f); // a touch of kd increases the speed by which we get there } @@ -140,18 +134,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); PerformanceTimer perfTimer("otherAvatars"); - const float FEED_FORWARD_RANGE = 2; const float fps = qApp->getLastInstanteousFps(); const float paintWait = qApp->getLastPaintWait(); const float deduced = qApp->getLastDeducedNonVSyncFps(); - const bool isAtSetpoint = false; //FIXME fabsf(effectiveFps - _renderDistanceController.getMeasuredValueSetpoint()) < FEED_FORWARD_RANGE; - //const float distance = 1.0f / _renderDistanceController.update(deduced + (isAtSetpoint ? _renderFeedForward : 0.0f), deltaTime, isAtSetpoint, fps, paintWait); - const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime, isAtSetpoint, fps, paintWait); - - const float RENDER_DISTANCE_DEADBAND = 0.0f; //FIXME 0.3f; // meters - if (fabsf(distance - _renderDistance) > RENDER_DISTANCE_DEADBAND) { - _renderDistance = distance; - } + const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime, false, fps, paintWait); + _renderDistanceAverage.updateAverage(distance); + _renderDistance = _renderDistanceAverage.getAverage(); // simulate avatars AvatarHash::iterator avatarIterator = _avatarHash.begin(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 92f9958962..7a4dd2632e 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -19,6 +19,7 @@ #include #include #include +#include #include "Avatar.h" #include "AvatarMotionState.h" @@ -106,6 +107,8 @@ private: float _renderFeedForward { 5.0f }; int _renderedAvatarCount {0}; PIDController _renderDistanceController {}; + SimpleMovingAverage _renderDistanceAverage {10}; + SetOfAvatarMotionStates _avatarMotionStates; diff --git a/libraries/shared/src/PIDController.cpp b/libraries/shared/src/PIDController.cpp index 2756ab615f..d5697538d5 100644 --- a/libraries/shared/src/PIDController.cpp +++ b/libraries/shared/src/PIDController.cpp @@ -71,7 +71,7 @@ void PIDController::reportHistory() { Row& row = _history[i]; qCDebug(shared) << row.measured << (row.dt * 1000) << row.FIXME1 << (row.FIXME2 * 1000) << "||" << row.error << row.accumulated << row.changed << - "||" << row.p << row.i << row.d << row.computed; + "||" << row.p << row.i << row.d << row.computed << 1.0f/row.computed; } qCDebug(shared) << "Limits: setpoint" << getMeasuredValueSetpoint() << "accumulate" << getAccumulatedValueLowLimit() << getAccumulatedValueHighLimit() << "controlled" << getControlledValueLowLimit() << getControlledValueHighLimit() << From 0a8fd9cbad939cf82668cd6457cafd629e63ae22 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 19 Nov 2015 09:22:41 -0800 Subject: [PATCH 05/29] Spread avatars around (not up to) a larger area. --- examples/acScripts/animatedAvatarAgent.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/acScripts/animatedAvatarAgent.js b/examples/acScripts/animatedAvatarAgent.js index 4e550e9789..3e5c90ed1a 100644 --- a/examples/acScripts/animatedAvatarAgent.js +++ b/examples/acScripts/animatedAvatarAgent.js @@ -14,16 +14,17 @@ // An assignment client script that animates one avatar at random location within 'spread' meters of 'origin'. // In Domain Server Settings, go to scripts and give the url of this script. Press '+', and then 'Save and restart'. -var origin = {x: 500, y: 502, z: 500}; -var spread = 10; // meters +var origin = {x: 500, y: 500, z: 500}; +var spread = 20; // meters var animationData = {url: "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", lastFrame: 35}; Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/dd03b8e3-52fb-4ab3-9ac9-3b17e00cd85d/98baa90b3b66803c5d7bd4537fca6993.fst"; //lovejoy Avatar.displayName = "'Bot"; var millisecondsToWaitBeforeStarting = 10 * 1000; // To give the various servers a chance to start. Agent.isAvatar = true; +function coord() { return (Math.random() * spread) - (spread / 2); } // randomly distribute a coordinate zero += spread/2. Script.setTimeout(function () { - Avatar.position = Vec3.sum(origin, {x: Math.random() * spread, y: 0, z: Math.random() * spread}); + Avatar.position = Vec3.sum(origin, {x: coord(), y: 0, z: coord()}); print("Starting at", JSON.stringify(Avatar.position)); Avatar.startAnimation(animationData.url, animationData.fps || 30, 1, true, false, animationData.firstFrame || 0, animationData.lastFrame); }, millisecondsToWaitBeforeStarting); From f6286201f46daa296004c6528417241e557198ee Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 19 Nov 2015 11:55:22 -0800 Subject: [PATCH 06/29] cleanup --- interface/resources/qml/Stats.qml | 8 +++++++- interface/src/avatar/Avatar.cpp | 24 ++++++++++-------------- interface/src/avatar/AvatarManager.cpp | 12 ++++++------ interface/src/avatar/AvatarManager.h | 22 ++++++++++------------ interface/src/ui/Stats.cpp | 2 +- interface/src/ui/Stats.h | 4 ++-- libraries/shared/src/PIDController.cpp | 6 +++--- libraries/shared/src/PIDController.h | 11 ++++------- 8 files changed, 43 insertions(+), 46 deletions(-) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 336b8ff577..84381cc754 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -40,7 +40,7 @@ Item { Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Avatars: " + root.avatarCount + ", " + root.avatarRenderedCount + " w/in " + root.avatarRenderDistance + "m" + text: "Avatars: " + root.avatarCount } Text { color: root.fontColor; @@ -256,6 +256,12 @@ Item { visible: root.expanded text: "LOD: " + root.lodStatus; } + Text { + color: root.fontColor; + font.pixelSize: root.fontSize + visible: root.expanded + text: "Renderable avatars: " + root.avatarRenderableCount + " w/in " + root.avatarRenderDistance + "m"; + } } } } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 6a634bcc90..bfee330700 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -115,7 +115,7 @@ Avatar::~Avatar() { } } -/*const fixme*/ float BILLBOARD_LOD_DISTANCE = 40.0f; +const float BILLBOARD_LOD_DISTANCE = 40.0f; void Avatar::init() { getHead()->init(); @@ -182,7 +182,6 @@ void Avatar::simulate(float deltaTime) { // update the billboard render flag const float BILLBOARD_HYSTERESIS_PROPORTION = 0.1f; - BILLBOARD_LOD_DISTANCE = DependencyManager::get()->getFIXMEupdate(); if (_shouldRenderBillboard) { if (getLODDistance() < BILLBOARD_LOD_DISTANCE * (1.0f - BILLBOARD_HYSTERESIS_PROPORTION)) { _shouldRenderBillboard = false; @@ -192,26 +191,23 @@ void Avatar::simulate(float deltaTime) { _shouldRenderBillboard = true; qCDebug(interfaceapp) << "Billboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); } -//#define PID_TUNING 1 -#ifdef PID_TUNING - const float SKIP_HYSTERESIS_PROPORTION = 0.0f; -#else - const float SKIP_HYSTERESIS_PROPORTION = BILLBOARD_HYSTERESIS_PROPORTION; -#endif + + const bool isControllerLogging = DependencyManager::get()->getRenderDistanceControllerIsLogging(); float renderDistance = DependencyManager::get()->getRenderDistance(); + const float SKIP_HYSTERESIS_PROPORTION = isControllerLogging ? 0.0f : BILLBOARD_HYSTERESIS_PROPORTION; float distance = glm::distance(qApp->getCamera()->getPosition(), _position); if (_shouldSkipRender) { if (distance < renderDistance * (1.0f - SKIP_HYSTERESIS_PROPORTION)) { _shouldSkipRender = false; -#ifndef PID_TUNING - qCDebug(interfaceapp) << "Rerendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); -#endif + if (!isControllerLogging) { // Test for isMyAvatar is prophylactic. Never occurs in current code. + qCDebug(interfaceapp) << "Rerendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for distance" << renderDistance; + } } } else if (distance > renderDistance * (1.0f + SKIP_HYSTERESIS_PROPORTION)) { _shouldSkipRender = true; -#ifndef PID_TUNING - qCDebug(interfaceapp) << "Unrendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); -#endif + if (!isControllerLogging) { + qCDebug(interfaceapp) << "Unrendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for distance" << renderDistance; + } } // simple frustum check diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 132ddb9dfa..d2765e0122 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -91,18 +91,17 @@ void AvatarManager::init() { } scene->enqueuePendingChanges(pendingChanges); - const float target_fps = 75.0f; // qApp->isHMDMode() ? 75.0f : 60.0f; + const float target_fps = 75.0f; // qApp->isHMDMode() ? 75.0f : 60.0f; FIXME _renderDistanceController.setMeasuredValueSetpoint(target_fps); const float TREE_SCALE = 32768.0f; // Not in shared library, alas. - const float SMALLEST_REASONABLE_HORIZON = 0.5f; // FIXME 5 + const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters _renderDistanceController.setControlledValueHighLimit(1.0f / SMALLEST_REASONABLE_HORIZON); _renderDistanceController.setControlledValueLowLimit(1.0f / TREE_SCALE); - // Advice for tuning parameters: // See PIDController.h. There's a sectionon tuning in the reference. - // Turn off HYSTERESIS_PROPORTION and extra logging by defining PID_TUNING in Avatar.cpp. - // Turn on logging with the following: - //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); // FIXME + // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) + //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); + // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. _renderDistanceController.setKP(0.0003f); //Usually about 0.6 of largest that doesn't oscillate, with other constants 0. _renderDistanceController.setKI(0.001f); // Big enough to bring us to target with the above KP. _renderDistanceController.setKD(0.00001f); // a touch of kd increases the speed by which we get there @@ -134,6 +133,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); PerformanceTimer perfTimer("otherAvatars"); + const float fps = qApp->getLastInstanteousFps(); const float paintWait = qApp->getLastPaintWait(); const float deduced = qApp->getLastDeducedNonVSyncFps(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 7a4dd2632e..9f8b6dc646 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -67,17 +67,17 @@ public: void handleCollisionEvents(const CollisionEvents& collisionEvents); void updateAvatarPhysicsShape(const QUuid& id); - Q_INVOKABLE int getNumberRendered() { return _renderedAvatarCount; } + + // Expose results and parameter-tuning operations to other systems, such as stats and javascript. Q_INVOKABLE float getRenderDistance() { return _renderDistance; } - Q_INVOKABLE float getFIXMEupdate() { return _fixmeUpdate; } - Q_INVOKABLE void setFIXMEupdate(float f) { _fixmeUpdate = f; } - Q_INVOKABLE void setFIXMEkp(float f) { _renderDistanceController.setKP(f); } - Q_INVOKABLE void setFIXMEki(float f) { _renderDistanceController.setKI(f); } - Q_INVOKABLE void setFIXMEkd(float f) { _renderDistanceController.setKD(f); } - Q_INVOKABLE void setFIXMEbias(float f) { _renderDistanceController.setBias(f); } - Q_INVOKABLE void setFIXMElow(float f) { _renderDistanceController.setControlledValueLowLimit(f); } - Q_INVOKABLE void setFIXMEhigh(float f) { _renderDistanceController.setControlledValueHighLimit(f); } - Q_INVOKABLE void setFIXMEfeedForward(float f) { _renderFeedForward = f; } + Q_INVOKABLE int getNumberInRenderRange() { return _renderedAvatarCount; } + Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } + Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } + Q_INVOKABLE void setRenderDistanceKP(float f) { _renderDistanceController.setKP(f); } + Q_INVOKABLE void setRenderDistanceKI(float f) { _renderDistanceController.setKI(f); } + Q_INVOKABLE void setRenderDistanceKD(float f) { _renderDistanceController.setKD(f); } + Q_INVOKABLE void setRenderDistanceLowLimit(float f) { _renderDistanceController.setControlledValueLowLimit(f); } + Q_INVOKABLE void setRenderDistanceHighLimit(float f) { _renderDistanceController.setControlledValueHighLimit(f); } public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } @@ -102,9 +102,7 @@ private: QVector _localLights; bool _shouldShowReceiveStats = false; - float _fixmeUpdate = 40.0f; float _renderDistance { 40.0f }; - float _renderFeedForward { 5.0f }; int _renderedAvatarCount {0}; PIDController _renderDistanceController {}; SimpleMovingAverage _renderDistanceAverage {10}; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 3f27052f60..663cf144b1 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -115,7 +115,7 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); - STAT_UPDATE(avatarRenderedCount, avatarManager->getNumberRendered()); + STAT_UPDATE(avatarRenderableCount, avatarManager->getNumberInRenderRange()); STAT_UPDATE(avatarRenderDistance, (int) round(avatarManager->getRenderDistance())); // deliberately truncating STAT_UPDATE(serverCount, nodeList->size()); STAT_UPDATE(framerate, (int)qApp->getFps()); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 12caf19d18..d1c0dd19d7 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -36,7 +36,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, simrate, 0) STATS_PROPERTY(int, avatarSimrate, 0) STATS_PROPERTY(int, avatarCount, 0) - STATS_PROPERTY(int, avatarRenderedCount, 0) + STATS_PROPERTY(int, avatarRenderableCount, 0) STATS_PROPERTY(int, avatarRenderDistance, 0) STATS_PROPERTY(int, packetInCount, 0) STATS_PROPERTY(int, packetOutCount, 0) @@ -119,7 +119,7 @@ signals: void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); - void avatarRenderedCountChanged(); + void avatarRenderableCountChanged(); void avatarRenderDistanceChanged(); void packetInCountChanged(); void packetOutCountChanged(); diff --git a/libraries/shared/src/PIDController.cpp b/libraries/shared/src/PIDController.cpp index d5697538d5..6080b878b8 100644 --- a/libraries/shared/src/PIDController.cpp +++ b/libraries/shared/src/PIDController.cpp @@ -27,11 +27,11 @@ float PIDController::update(float measuredValue, float dt, bool resetAccumulator const float changeInError = (error - _lastError) / dt; // positive value denotes increasing deficit const float d = getKD() * changeInError; // term is Derivative of Error - const float computedValue = glm::clamp(p + i + d + getBias(), + const float computedValue = glm::clamp(p + i + d, getControlledValueLowLimit(), getControlledValueHighLimit()); - if (_history.capacity()) { // if logging/reporting + if (getIsLogging()) { // if logging/reporting updateHistory(measuredValue, dt, error, accumulatedError, changeInError, p, i, d, computedValue, FIXME1, FIXME2); } @@ -75,5 +75,5 @@ void PIDController::reportHistory() { } qCDebug(shared) << "Limits: setpoint" << getMeasuredValueSetpoint() << "accumulate" << getAccumulatedValueLowLimit() << getAccumulatedValueHighLimit() << "controlled" << getControlledValueLowLimit() << getControlledValueHighLimit() << - "kp/ki/kd/bias" << getKP() << getKI() << getKD() << getBias(); + "kp/ki/kd" << getKP() << getKI() << getKD(); } \ No newline at end of file diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h index a2adbf98ab..7c63206406 100644 --- a/libraries/shared/src/PIDController.h +++ b/libraries/shared/src/PIDController.h @@ -26,13 +26,12 @@ class PIDController { public: - // These three are the main interface: + // These four the main interface: void setMeasuredValueSetpoint(float newValue) { _measuredValueSetpoint = newValue; } float update(float measuredValue, float dt, bool resetAccumulator = false, float FIXME1 = 0.0f, float FIXME2 = 0.0f); // returns the new computedValue void setHistorySize(QString label = QString(""), int size = 0) { _history.reserve(size); _history.resize(0); _label = label; } // non-empty does logging - // There are several values that rarely change and might be thought of as "constants", but which do change during tuning, debugging, or other - // special-but-expected circumstances. Thus the instance vars are not const. + bool getIsLogging() { return _history.capacity(); } float getMeasuredValueSetpoint() const { return _measuredValueSetpoint; } // In normal operation (where we can easily reach setpoint), controlledValue is typcially pinned at max. // Defaults to [0, max float], but for 1/LODdistance, it might be, say, [0, 0.2 or 0.1] @@ -42,17 +41,17 @@ public: float getKP() const { return _kp; } float getKI() const { return _ki; } float getKD() const { return _kd; } - float getBias() const { return _bias; } float getAccumulatedValueHighLimit() const { return getAntiWindupFactor() * getMeasuredValueSetpoint(); } float getAccumulatedValueLowLimit() const { return -getAntiWindupFactor() * getMeasuredValueSetpoint(); } + // There are several values that rarely change and might be thought of as "constants", but which do change during tuning, debugging, or other + // special-but-expected circumstances. Thus the instance vars are not const. void setControlledValueLowLimit(float newValue) { _controlledValueLowLimit = newValue; } void setControlledValueHighLimit(float newValue) { _controlledValueHighLimit = newValue; } void setAntiWindupFactor(float newValue) { _antiWindupFactor = newValue; } void setKP(float newValue) { _kp = newValue; } void setKI(float newValue) { _ki = newValue; } void setKD(float newValue) { _kd = newValue; } - void setBias(float newValue) { _bias = newValue; } class Row { // one row of accumulated history, used only for logging (if at all) public: @@ -66,7 +65,6 @@ public: float p; float i; float d; - float bias; float computed; }; protected: @@ -79,7 +77,6 @@ protected: float _kp { 0.0f }; float _ki { 0.0f }; float _kd { 0.0f }; - float _bias { 0.0f }; // Controller state float _lastError{ 0.0f }; From 431a8c95848a2599743e70cf4034a0970549ae94 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 19 Nov 2015 17:12:50 -0800 Subject: [PATCH 07/29] cleanup --- interface/src/Application.cpp | 11 +++++++---- interface/src/Application.h | 1 + interface/src/avatar/AvatarManager.cpp | 8 ++++---- libraries/shared/src/PIDController.cpp | 5 +++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 84b14497cd..1e970828ec 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1102,15 +1102,18 @@ void Application::paintGL() { // Here we make a guess for deducedNonVSyncFps = 1 / deducedNonVSyncPeriod. // // Time between previous paintGL call and this one, which can vary not only with vSync misses, but also with QT timing. + // We're using this as a proxy for the time between vsync and displayEnd, below. (Not exact, but tends to be the same over time.) // This is not the same as update(deltaTime), because the latter attempts to throttle to 60hz and also clamps to 1/4 second. const float actualPeriod = diff / (float)USECS_PER_SECOND; // same as 1/instantaneousFps but easier for compiler to optimize // Note that _lastPaintWait (stored at end of last call) is for the same paint cycle. float deducedNonVSyncPeriod = actualPeriod - _lastPaintWait; // plus a some non-zero time for machinery we can't measure // We don't know how much time to allow for that, but if we went over the target period, we know it's at least the portion - // of paintWait up to the next vSync. - const float targetPeriod = isHMDMode() ? 1.0f / 75.0f : 1.0f / 60.0f; - const float minumumMachinery = glm::max(0.0f, (floorf(_lastPaintWait / targetPeriod) * targetPeriod) - _lastPaintWait); - deducedNonVSyncPeriod += minumumMachinery; + // of paintWait up to the next vSync. This gives us enough of a penalty so that when actualPeriod crosses two cycles, + // the key part (and not an exagerated part) of _lastPaintWait is accounted for. + const float targetPeriod = getTargetFramePeriod(); + if (_lastPaintWait > EPSILON && actualPeriod > targetPeriod) { + deducedNonVSyncPeriod += remainder(actualPeriod, _lastPaintWait); + } _lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod; _lastInstantaneousFps = instantaneousFps; diff --git a/interface/src/Application.h b/interface/src/Application.h index 01d0c72645..0222f585cf 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -159,6 +159,7 @@ public: bool isForeground() const { return _isForeground; } float getFps() const { return _fps; } + float getTargetFramePeriod() { return isHMDMode() ? 1.0f / 75.0f : 1.0f / 60.0f; } float getLastInstanteousFps() const { return _lastInstantaneousFps; } float getLastPaintWait() const { return _lastPaintWait; }; float getLastDeducedNonVSyncFps() const { return _lastDeducedNonVSyncFps; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d2765e0122..6c1097a42b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -91,7 +91,7 @@ void AvatarManager::init() { } scene->enqueuePendingChanges(pendingChanges); - const float target_fps = 75.0f; // qApp->isHMDMode() ? 75.0f : 60.0f; FIXME + const float target_fps = 1.0f / qApp->getTargetFramePeriod(); _renderDistanceController.setMeasuredValueSetpoint(target_fps); const float TREE_SCALE = 32768.0f; // Not in shared library, alas. const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters @@ -102,9 +102,9 @@ void AvatarManager::init() { // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. - _renderDistanceController.setKP(0.0003f); //Usually about 0.6 of largest that doesn't oscillate, with other constants 0. - _renderDistanceController.setKI(0.001f); // Big enough to bring us to target with the above KP. - _renderDistanceController.setKD(0.00001f); // a touch of kd increases the speed by which we get there + _renderDistanceController.setKP(0.0005f); // Usually about 0.6 of largest that doesn't oscillate when other constants 0. + _renderDistanceController.setKI(0.0004f); // Big enough to bring us to target with the above KP. + _renderDistanceController.setKD(0.00001f); // A touch of kd increases the speed by which we get there. } diff --git a/libraries/shared/src/PIDController.cpp b/libraries/shared/src/PIDController.cpp index 6080b878b8..fc75b91fb2 100644 --- a/libraries/shared/src/PIDController.cpp +++ b/libraries/shared/src/PIDController.cpp @@ -33,8 +33,9 @@ float PIDController::update(float measuredValue, float dt, bool resetAccumulator if (getIsLogging()) { // if logging/reporting updateHistory(measuredValue, dt, error, accumulatedError, changeInError, p, i, d, computedValue, FIXME1, FIXME2); - } - + } + Q_ASSERT(!isnan(computedValue)); + // update state for next time _lastError = error; _lastAccumulation = accumulatedError; From 0f6a37f0f7de9223419eb4d7954bb5b7b7367388 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 20 Nov 2015 09:49:14 -0800 Subject: [PATCH 08/29] remainder->fmod! --- interface/src/Application.cpp | 3 ++- interface/src/avatar/AvatarManager.cpp | 11 +++++++---- interface/src/avatar/AvatarManager.h | 2 +- libraries/shared/src/PIDController.h | 7 ++++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1e970828ec..5640827982 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1112,7 +1112,8 @@ void Application::paintGL() { // the key part (and not an exagerated part) of _lastPaintWait is accounted for. const float targetPeriod = getTargetFramePeriod(); if (_lastPaintWait > EPSILON && actualPeriod > targetPeriod) { - deducedNonVSyncPeriod += remainder(actualPeriod, _lastPaintWait); + // Don't use C++ remainder(). It's authors are mathematically insane. + deducedNonVSyncPeriod += fmod(actualPeriod, _lastPaintWait); } _lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod; _lastInstantaneousFps = instantaneousFps; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 6c1097a42b..789865679d 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -93,16 +93,15 @@ void AvatarManager::init() { const float target_fps = 1.0f / qApp->getTargetFramePeriod(); _renderDistanceController.setMeasuredValueSetpoint(target_fps); - const float TREE_SCALE = 32768.0f; // Not in shared library, alas. const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters _renderDistanceController.setControlledValueHighLimit(1.0f / SMALLEST_REASONABLE_HORIZON); - _renderDistanceController.setControlledValueLowLimit(1.0f / TREE_SCALE); + _renderDistanceController.setControlledValueLowLimit(1.0f / (float) TREE_SCALE); // Advice for tuning parameters: - // See PIDController.h. There's a sectionon tuning in the reference. + // See PIDController.h. There's a section on tuning in the reference. // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. - _renderDistanceController.setKP(0.0005f); // Usually about 0.6 of largest that doesn't oscillate when other constants 0. + _renderDistanceController.setKP(0.0005f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. _renderDistanceController.setKI(0.0004f); // Big enough to bring us to target with the above KP. _renderDistanceController.setKD(0.00001f); // A touch of kd increases the speed by which we get there. @@ -136,6 +135,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const float fps = qApp->getLastInstanteousFps(); const float paintWait = qApp->getLastPaintWait(); + // The PID controller raises the controlled value when the measured value goes up. + // The measured value is frame rate. When the controlled value (1 / render cutoff distance) + // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate + // goes up. const float deduced = qApp->getLastDeducedNonVSyncFps(); const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime, false, fps, paintWait); _renderDistanceAverage.updateAverage(distance); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 9f8b6dc646..f17a34da35 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -102,7 +102,7 @@ private: QVector _localLights; bool _shouldShowReceiveStats = false; - float _renderDistance { 40.0f }; + float _renderDistance { (float) TREE_SCALE }; int _renderedAvatarCount {0}; PIDController _renderDistanceController {}; SimpleMovingAverage _renderDistanceAverage {10}; diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h index 7c63206406..55519063bc 100644 --- a/libraries/shared/src/PIDController.h +++ b/libraries/shared/src/PIDController.h @@ -21,12 +21,12 @@ #include #include -// Although our coding standard shuns abbreviations, the control systems literature pretty uniformly uses p, i, d, and dt rather than +// Although our coding standard shuns abbreviations, the control systems literature uniformly uses p, i, d, and dt rather than // proportionalTerm, integralTerm, derivativeTerm, and deltaTime. Here we will be consistent with the literature. class PIDController { public: - // These four the main interface: + // These are the main interfaces: void setMeasuredValueSetpoint(float newValue) { _measuredValueSetpoint = newValue; } float update(float measuredValue, float dt, bool resetAccumulator = false, float FIXME1 = 0.0f, float FIXME2 = 0.0f); // returns the new computedValue void setHistorySize(QString label = QString(""), int size = 0) { _history.reserve(size); _history.resize(0); _label = label; } // non-empty does logging @@ -78,9 +78,10 @@ protected: float _ki { 0.0f }; float _kd { 0.0f }; - // Controller state + // Controller operating state float _lastError{ 0.0f }; float _lastAccumulation{ 0.0f }; + // reporting QVector _history{}; QString _label{ "" }; From b5e5adc41e9485e6e2a02c7634c2df0e095edf35 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 20 Nov 2015 09:51:57 -0800 Subject: [PATCH 09/29] Update for changed target frame period. --- interface/src/avatar/AvatarManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 789865679d..5c9796b1e4 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -135,6 +135,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const float fps = qApp->getLastInstanteousFps(); const float paintWait = qApp->getLastPaintWait(); + _renderDistanceController.setMeasuredValueSetpoint(1.0f / qApp->getTargetFramePeriod()); // No problem updating in flight. // The PID controller raises the controlled value when the measured value goes up. // The measured value is frame rate. When the controlled value (1 / render cutoff distance) // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate From a3665985986cb98103f40a8084fc8270ad0364c5 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 20 Nov 2015 11:48:44 -0800 Subject: [PATCH 10/29] snapshot. It works. --- interface/src/Application.cpp | 2 +- interface/src/Application.h | 2 ++ interface/src/avatar/AvatarManager.cpp | 8 ++++---- interface/src/avatar/AvatarManager.h | 18 ++++++++++-------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5640827982..9a3f3b071e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1106,7 +1106,7 @@ void Application::paintGL() { // This is not the same as update(deltaTime), because the latter attempts to throttle to 60hz and also clamps to 1/4 second. const float actualPeriod = diff / (float)USECS_PER_SECOND; // same as 1/instantaneousFps but easier for compiler to optimize // Note that _lastPaintWait (stored at end of last call) is for the same paint cycle. - float deducedNonVSyncPeriod = actualPeriod - _lastPaintWait; // plus a some non-zero time for machinery we can't measure + float deducedNonVSyncPeriod = actualPeriod - _lastPaintWait + 0.002f; // plus a some non-zero time for machinery we can't measure // We don't know how much time to allow for that, but if we went over the target period, we know it's at least the portion // of paintWait up to the next vSync. This gives us enough of a penalty so that when actualPeriod crosses two cycles, // the key part (and not an exagerated part) of _lastPaintWait is accounted for. diff --git a/interface/src/Application.h b/interface/src/Application.h index 0222f585cf..7d9efb34af 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -163,6 +163,7 @@ public: float getLastInstanteousFps() const { return _lastInstantaneousFps; } float getLastPaintWait() const { return _lastPaintWait; }; float getLastDeducedNonVSyncFps() const { return _lastDeducedNonVSyncFps; } + void setMarginForDeducedFramePeriod(float newValue) { _marginForDeducedFramePeriod = newValue; } float getFieldOfView() { return _fieldOfView.get(); } void setFieldOfView(float fov); @@ -437,6 +438,7 @@ private: float _lastInstantaneousFps { 0.0f }; float _lastPaintWait { 0.0f }; float _lastDeducedNonVSyncFps { 0.0f }; + float _marginForDeducedFramePeriod { 1.0f }; ShapeManager _shapeManager; PhysicalEntitySimulation _entitySimulation; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 5c9796b1e4..80e35174bd 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -101,9 +101,9 @@ void AvatarManager::init() { // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. - _renderDistanceController.setKP(0.0005f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. - _renderDistanceController.setKI(0.0004f); // Big enough to bring us to target with the above KP. - _renderDistanceController.setKD(0.00001f); // A touch of kd increases the speed by which we get there. + _renderDistanceController.setKP(0.0006f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. + _renderDistanceController.setKI(0.0005f); // Big enough to bring us to target with the above KP. + _renderDistanceController.setKD(0.000001f); // A touch of kd increases the speed by which we get there. } @@ -140,7 +140,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // The measured value is frame rate. When the controlled value (1 / render cutoff distance) // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate // goes up. - const float deduced = qApp->getLastDeducedNonVSyncFps(); + const float deduced = qApp->getLastDeducedNonVSyncFps() - _renderPresentationAllowance; const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime, false, fps, paintWait); _renderDistanceAverage.updateAverage(distance); _renderDistance = _renderDistanceAverage.getAverage(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index f17a34da35..8be7df43f9 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -73,11 +73,12 @@ public: Q_INVOKABLE int getNumberInRenderRange() { return _renderedAvatarCount; } Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } - Q_INVOKABLE void setRenderDistanceKP(float f) { _renderDistanceController.setKP(f); } - Q_INVOKABLE void setRenderDistanceKI(float f) { _renderDistanceController.setKI(f); } - Q_INVOKABLE void setRenderDistanceKD(float f) { _renderDistanceController.setKD(f); } - Q_INVOKABLE void setRenderDistanceLowLimit(float f) { _renderDistanceController.setControlledValueLowLimit(f); } - Q_INVOKABLE void setRenderDistanceHighLimit(float f) { _renderDistanceController.setControlledValueHighLimit(f); } + Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); } + Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); } + Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } + Q_INVOKABLE void setRenderDistanceLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } + Q_INVOKABLE void setRenderDistanceHighLimit(float newValue) { _renderDistanceController.setControlledValueHighLimit(newValue); } + // Q_INVOKABLE void setRenderPresentationAllowance(float newValue) { qApp->setMarginForDeducedFramePeriod(newValue); } public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } @@ -103,9 +104,10 @@ private: bool _shouldShowReceiveStats = false; float _renderDistance { (float) TREE_SCALE }; - int _renderedAvatarCount {0}; - PIDController _renderDistanceController {}; - SimpleMovingAverage _renderDistanceAverage {10}; + int _renderedAvatarCount { 0 }; + float _renderPresentationAllowance { 0.0f }; + PIDController _renderDistanceController { }; + SimpleMovingAverage _renderDistanceAverage { 10 }; From 6c784256cb9eae3269d943e8e3053d20f458219b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 20 Nov 2015 13:51:12 -0800 Subject: [PATCH 11/29] final(?) cleanup --- interface/src/Application.cpp | 2 +- interface/src/Application.h | 2 +- interface/src/avatar/AvatarManager.cpp | 10 ++++------ interface/src/avatar/AvatarManager.h | 2 -- libraries/shared/src/PIDController.cpp | 10 ++++------ libraries/shared/src/PIDController.h | 6 ++---- 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9a3f3b071e..9863248cf8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1106,7 +1106,7 @@ void Application::paintGL() { // This is not the same as update(deltaTime), because the latter attempts to throttle to 60hz and also clamps to 1/4 second. const float actualPeriod = diff / (float)USECS_PER_SECOND; // same as 1/instantaneousFps but easier for compiler to optimize // Note that _lastPaintWait (stored at end of last call) is for the same paint cycle. - float deducedNonVSyncPeriod = actualPeriod - _lastPaintWait + 0.002f; // plus a some non-zero time for machinery we can't measure + float deducedNonVSyncPeriod = actualPeriod - _lastPaintWait + _marginForDeducedFramePeriod; // plus a some non-zero time for machinery we can't measure // We don't know how much time to allow for that, but if we went over the target period, we know it's at least the portion // of paintWait up to the next vSync. This gives us enough of a penalty so that when actualPeriod crosses two cycles, // the key part (and not an exagerated part) of _lastPaintWait is accounted for. diff --git a/interface/src/Application.h b/interface/src/Application.h index 7d9efb34af..b90ad43d60 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -438,7 +438,7 @@ private: float _lastInstantaneousFps { 0.0f }; float _lastPaintWait { 0.0f }; float _lastDeducedNonVSyncFps { 0.0f }; - float _marginForDeducedFramePeriod { 1.0f }; + float _marginForDeducedFramePeriod{ 0.002f }; // 2ms, adjustable ShapeManager _shapeManager; PhysicalEntitySimulation _entitySimulation; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 80e35174bd..eea4edafc9 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -101,8 +101,8 @@ void AvatarManager::init() { // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. - _renderDistanceController.setKP(0.0006f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. - _renderDistanceController.setKI(0.0005f); // Big enough to bring us to target with the above KP. + _renderDistanceController.setKP(0.0008f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. + _renderDistanceController.setKI(0.0006f); // Big enough to bring us to target with the above KP. _renderDistanceController.setKD(0.000001f); // A touch of kd increases the speed by which we get there. } @@ -133,15 +133,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceTimer perfTimer("otherAvatars"); - const float fps = qApp->getLastInstanteousFps(); - const float paintWait = qApp->getLastPaintWait(); _renderDistanceController.setMeasuredValueSetpoint(1.0f / qApp->getTargetFramePeriod()); // No problem updating in flight. // The PID controller raises the controlled value when the measured value goes up. // The measured value is frame rate. When the controlled value (1 / render cutoff distance) // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate // goes up. - const float deduced = qApp->getLastDeducedNonVSyncFps() - _renderPresentationAllowance; - const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime, false, fps, paintWait); + const float deduced = qApp->getLastDeducedNonVSyncFps(); + const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime); _renderDistanceAverage.updateAverage(distance); _renderDistance = _renderDistanceAverage.getAverage(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 8be7df43f9..52741970aa 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -78,7 +78,6 @@ public: Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } Q_INVOKABLE void setRenderDistanceLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } Q_INVOKABLE void setRenderDistanceHighLimit(float newValue) { _renderDistanceController.setControlledValueHighLimit(newValue); } - // Q_INVOKABLE void setRenderPresentationAllowance(float newValue) { qApp->setMarginForDeducedFramePeriod(newValue); } public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } @@ -105,7 +104,6 @@ private: bool _shouldShowReceiveStats = false; float _renderDistance { (float) TREE_SCALE }; int _renderedAvatarCount { 0 }; - float _renderPresentationAllowance { 0.0f }; PIDController _renderDistanceController { }; SimpleMovingAverage _renderDistanceAverage { 10 }; diff --git a/libraries/shared/src/PIDController.cpp b/libraries/shared/src/PIDController.cpp index fc75b91fb2..a853553b89 100644 --- a/libraries/shared/src/PIDController.cpp +++ b/libraries/shared/src/PIDController.cpp @@ -14,7 +14,7 @@ #include "SharedLogging.h" #include "PIDController.h" -float PIDController::update(float measuredValue, float dt, bool resetAccumulator, float FIXME1, float FIXME2) { +float PIDController::update(float measuredValue, float dt, bool resetAccumulator) { const float error = getMeasuredValueSetpoint() - measuredValue; // Sign is the direction we want measuredValue to go. Positive means go higher. const float p = getKP() * error; // term is Proportional to error @@ -32,7 +32,7 @@ float PIDController::update(float measuredValue, float dt, bool resetAccumulator getControlledValueHighLimit()); if (getIsLogging()) { // if logging/reporting - updateHistory(measuredValue, dt, error, accumulatedError, changeInError, p, i, d, computedValue, FIXME1, FIXME2); + updateHistory(measuredValue, dt, error, accumulatedError, changeInError, p, i, d, computedValue); } Q_ASSERT(!isnan(computedValue)); @@ -43,7 +43,7 @@ float PIDController::update(float measuredValue, float dt, bool resetAccumulator } // Just for logging/reporting. Used when picking/verifying the operational parameters. -void PIDController::updateHistory(float measuredValue, float dt, float error, float accumulatedError, float changeInError, float p, float i, float d, float computedValue, float FIXME1, float FIXME2) { +void PIDController::updateHistory(float measuredValue, float dt, float error, float accumulatedError, float changeInError, float p, float i, float d, float computedValue) { // Don't report each update(), as the I/O messes with the results a lot. // Instead, add to history, and then dump out at once when full. // Typically, the first few values reported in each batch should be ignored. @@ -51,8 +51,6 @@ void PIDController::updateHistory(float measuredValue, float dt, float error, fl _history.resize(n + 1); Row& next = _history[n]; next.measured = measuredValue; - next.FIXME1 = FIXME1; - next.FIXME2 = FIXME2; next.dt = dt; next.error = error; next.accumulated = accumulatedError; @@ -70,7 +68,7 @@ void PIDController::reportHistory() { qCDebug(shared) << _label << "measured dt FIXME || error accumulated changed || p i d controlled"; for (int i = 0; i < _history.size(); i++) { Row& row = _history[i]; - qCDebug(shared) << row.measured << (row.dt * 1000) << row.FIXME1 << (row.FIXME2 * 1000) << + qCDebug(shared) << row.measured << row.dt << "||" << row.error << row.accumulated << row.changed << "||" << row.p << row.i << row.d << row.computed << 1.0f/row.computed; } diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h index 55519063bc..e89ff2a693 100644 --- a/libraries/shared/src/PIDController.h +++ b/libraries/shared/src/PIDController.h @@ -28,7 +28,7 @@ class PIDController { public: // These are the main interfaces: void setMeasuredValueSetpoint(float newValue) { _measuredValueSetpoint = newValue; } - float update(float measuredValue, float dt, bool resetAccumulator = false, float FIXME1 = 0.0f, float FIXME2 = 0.0f); // returns the new computedValue + float update(float measuredValue, float dt, bool resetAccumulator = false); // returns the new computedValue void setHistorySize(QString label = QString(""), int size = 0) { _history.reserve(size); _history.resize(0); _label = label; } // non-empty does logging bool getIsLogging() { return _history.capacity(); } @@ -55,8 +55,6 @@ public: class Row { // one row of accumulated history, used only for logging (if at all) public: - float FIXME1; - float FIXME2; float measured; float dt; float error; @@ -69,7 +67,7 @@ public: }; protected: void reportHistory(); - void updateHistory(float measured, float dt, float error, float accumulatedError, float changeInErro, float p, float i, float d, float computedValue, float FIXME1, float FIXME2); + void updateHistory(float measured, float dt, float error, float accumulatedError, float changeInErro, float p, float i, float d, float computedValue); float _measuredValueSetpoint { 0.0f }; float _controlledValueLowLimit { 0.0f }; float _controlledValueHighLimit { std::numeric_limits::max() }; From 9673133bf4d76aade057a5a0d3d50e7cf2d33af7 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 20 Nov 2015 14:26:37 -0800 Subject: [PATCH 12/29] whitespace (reduce diffs) --- interface/src/avatar/Avatar.cpp | 1 + interface/src/avatar/AvatarManager.h | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 26fb9c1b2e..d3f47703fc 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -606,6 +606,7 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { } void Avatar::fixupModelsInScene() { + // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene render::ScenePointer scene = qApp->getMain3DScene(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 9918fc9374..96383b7e60 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -65,6 +65,7 @@ public: void getObjectsToChange(VectorOfMotionStates& motionStates); void handleOutgoingChanges(const VectorOfMotionStates& motionStates); void handleCollisionEvents(const CollisionEvents& collisionEvents); + void updateAvatarPhysicsShape(Avatar* avatar); // Expose results and parameter-tuning operations to other systems, such as stats and javascript. @@ -108,8 +109,6 @@ private: PIDController _renderDistanceController { }; SimpleMovingAverage _renderDistanceAverage { 10 }; - - SetOfAvatarMotionStates _avatarMotionStates; SetOfMotionStates _motionStatesToAdd; VectorOfMotionStates _motionStatesToDelete; From 64ba5b4f1488828a4e8b6626b4560b25231c63a3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 23 Nov 2015 09:42:34 -0800 Subject: [PATCH 13/29] Cleaning up old recording files --- interface/src/avatar/Avatar.cpp | 1 - interface/src/avatar/MyAvatar.cpp | 1 - libraries/avatars/src/Player.cpp | 443 ------------------- libraries/avatars/src/Player.h | 94 ---- libraries/avatars/src/Recorder.cpp | 147 ------ libraries/avatars/src/Recorder.h | 57 --- libraries/avatars/src/Recording.cpp | 663 ---------------------------- libraries/avatars/src/Recording.h | 131 ------ 8 files changed, 1537 deletions(-) delete mode 100644 libraries/avatars/src/Player.cpp delete mode 100644 libraries/avatars/src/Player.h delete mode 100644 libraries/avatars/src/Recorder.cpp delete mode 100644 libraries/avatars/src/Recorder.h delete mode 100644 libraries/avatars/src/Recording.cpp delete mode 100644 libraries/avatars/src/Recording.h diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index bfa32b8223..f0f77cfd20 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -40,7 +40,6 @@ #include "Menu.h" #include "ModelReferential.h" #include "Physics.h" -#include "Recorder.h" #include "Util.h" #include "world.h" #include "InterfaceLogging.h" diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c446eba514..cac60b2381 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -49,7 +49,6 @@ #include "ModelReferential.h" #include "MyAvatar.h" #include "Physics.h" -#include "Recorder.h" #include "Util.h" #include "InterfaceLogging.h" #include "DebugDraw.h" diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp deleted file mode 100644 index 31efb4cd9c..0000000000 --- a/libraries/avatars/src/Player.cpp +++ /dev/null @@ -1,443 +0,0 @@ -// -// Player.cpp -// -// -// Created by Clement on 9/17/14. -// 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 -// - - -#if 0 -#include -#include -#include -#include - -#include "AvatarData.h" -#include "AvatarLogging.h" -#include "Player.h" - -static const int INVALID_FRAME = -1; - -Player::Player(AvatarData* avatar) : - _avatar(avatar), - _recording(new Recording()), - _currentFrame(INVALID_FRAME), - _frameInterpolationFactor(0.0f), - _pausedFrame(INVALID_FRAME), - _timerOffset(0), - _audioOffset(0), - _audioThread(NULL), - _playFromCurrentPosition(true), - _loop(false), - _useAttachments(true), - _useDisplayName(true), - _useHeadURL(true), - _useSkeletonURL(true) -{ - _timer.invalidate(); -} - -bool Player::isPlaying() const { - return _timer.isValid(); -} - -bool Player::isPaused() const { - return (_pausedFrame != INVALID_FRAME); -} - -qint64 Player::elapsed() const { - if (isPlaying()) { - return _timerOffset + _timer.elapsed(); - } else if (isPaused()) { - return _timerOffset; - } else { - return 0; - } -} - -void Player::startPlaying() { - if (!_recording || _recording->getFrameNumber() <= 1) { - return; - } - - if (!isPaused()) { - _currentContext.globalTimestamp = usecTimestampNow(); - _currentContext.domain = DependencyManager::get()->getDomainHandler().getHostname(); - _currentContext.position = _avatar->getPosition(); - _currentContext.orientation = _avatar->getOrientation(); - _currentContext.scale = _avatar->getTargetScale(); - _currentContext.headModel = _avatar->getFaceModelURL().toString(); - _currentContext.skeletonModel = _avatar->getSkeletonModelURL().toString(); - _currentContext.displayName = _avatar->getDisplayName(); - _currentContext.attachments = _avatar->getAttachmentData(); - - _currentContext.orientationInv = glm::inverse(_currentContext.orientation); - - RecordingContext& context = _recording->getContext(); - if (_useAttachments) { - _avatar->setAttachmentData(context.attachments); - } - if (_useDisplayName) { - _avatar->setDisplayName(context.displayName); - } - if (_useHeadURL) { - _avatar->setFaceModelURL(context.headModel); - } - if (_useSkeletonURL) { - _avatar->setSkeletonModelURL(context.skeletonModel); - } - - bool wantDebug = false; - if (wantDebug) { - qCDebug(avatars) << "Player::startPlaying(): Recording Context"; - qCDebug(avatars) << "Domain:" << _currentContext.domain; - qCDebug(avatars) << "Position:" << _currentContext.position; - qCDebug(avatars) << "Orientation:" << _currentContext.orientation; - qCDebug(avatars) << "Scale:" << _currentContext.scale; - qCDebug(avatars) << "Head URL:" << _currentContext.headModel; - qCDebug(avatars) << "Skeleton URL:" << _currentContext.skeletonModel; - qCDebug(avatars) << "Display Name:" << _currentContext.displayName; - qCDebug(avatars) << "Num Attachments:" << _currentContext.attachments.size(); - - for (int i = 0; i < _currentContext.attachments.size(); ++i) { - qCDebug(avatars) << "Model URL:" << _currentContext.attachments[i].modelURL; - qCDebug(avatars) << "Joint Name:" << _currentContext.attachments[i].jointName; - qCDebug(avatars) << "Translation:" << _currentContext.attachments[i].translation; - qCDebug(avatars) << "Rotation:" << _currentContext.attachments[i].rotation; - qCDebug(avatars) << "Scale:" << _currentContext.attachments[i].scale; - } - } - - // Fake faceshift connection - _avatar->setForceFaceTrackerConnected(true); - - qCDebug(avatars) << "Recorder::startPlaying()"; - setupAudioThread(); - _currentFrame = 0; - _timerOffset = 0; - _timer.start(); - } else { - qCDebug(avatars) << "Recorder::startPlaying(): Unpause"; - setupAudioThread(); - _timer.start(); - - setCurrentFrame(_pausedFrame); - _pausedFrame = INVALID_FRAME; - } -} - -void Player::stopPlaying() { - if (!isPlaying()) { - return; - } - _pausedFrame = INVALID_FRAME; - _timer.invalidate(); - cleanupAudioThread(); - _avatar->clearJointsData(); - - // Turn off fake face tracker connection - _avatar->setForceFaceTrackerConnected(false); - - if (_useAttachments) { - _avatar->setAttachmentData(_currentContext.attachments); - } - if (_useDisplayName) { - _avatar->setDisplayName(_currentContext.displayName); - } - if (_useHeadURL) { - _avatar->setFaceModelURL(_currentContext.headModel); - } - if (_useSkeletonURL) { - _avatar->setSkeletonModelURL(_currentContext.skeletonModel); - } - - qCDebug(avatars) << "Recorder::stopPlaying()"; -} - -void Player::pausePlayer() { - _timerOffset = elapsed(); - _timer.invalidate(); - cleanupAudioThread(); - - _pausedFrame = _currentFrame; - qCDebug(avatars) << "Recorder::pausePlayer()"; -} - -void Player::setupAudioThread() { - _audioThread = new QThread(); - _audioThread->setObjectName("Player Audio Thread"); - _options.position = _avatar->getPosition(); - _options.orientation = _avatar->getOrientation(); - _options.stereo = _recording->numberAudioChannel() == 2; - - _injector.reset(new AudioInjector(_recording->getAudioData(), _options), &QObject::deleteLater); - _injector->moveToThread(_audioThread); - _audioThread->start(); - QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection); -} - -void Player::cleanupAudioThread() { - _injector->stop(); - QObject::connect(_injector.data(), &AudioInjector::finished, - _injector.data(), &AudioInjector::deleteLater); - QObject::connect(_injector.data(), &AudioInjector::destroyed, - _audioThread, &QThread::quit); - QObject::connect(_audioThread, &QThread::finished, - _audioThread, &QThread::deleteLater); - _injector.clear(); - _audioThread = NULL; -} - -void Player::loopRecording() { - cleanupAudioThread(); - setupAudioThread(); - _currentFrame = 0; - _timerOffset = 0; - _timer.restart(); -} - -void Player::loadFromFile(const QString& file) { - if (_recording) { - _recording->clear(); - } else { - _recording = QSharedPointer(); - } - readRecordingFromFile(_recording, file); - - _pausedFrame = INVALID_FRAME; -} - -void Player::loadRecording(RecordingPointer recording) { - _recording = recording; - _pausedFrame = INVALID_FRAME; -} - -void Player::play() { - computeCurrentFrame(); - if (_currentFrame < 0 || (_currentFrame >= _recording->getFrameNumber() - 2)) { // -2 because of interpolation - if (_loop) { - loopRecording(); - } else { - stopPlaying(); - } - return; - } - - const RecordingContext* context = &_recording->getContext(); - if (_playFromCurrentPosition) { - context = &_currentContext; - } - const RecordingFrame& currentFrame = _recording->getFrame(_currentFrame); - const RecordingFrame& nextFrame = _recording->getFrame(_currentFrame + 1); - - glm::vec3 translation = glm::mix(currentFrame.getTranslation(), - nextFrame.getTranslation(), - _frameInterpolationFactor); - _avatar->setPosition(context->position + context->orientation * translation); - - glm::quat rotation = safeMix(currentFrame.getRotation(), - nextFrame.getRotation(), - _frameInterpolationFactor); - _avatar->setOrientation(context->orientation * rotation); - - float scale = glm::mix(currentFrame.getScale(), - nextFrame.getScale(), - _frameInterpolationFactor); - _avatar->setTargetScale(context->scale * scale); - - // Joint array playback - // FIXME: THis is still using a deprecated path to assign the joint orientation since setting the full RawJointData array doesn't - // work for Avatar. We need to fix this working with the animation team - const auto& prevJointArray = currentFrame.getJointArray(); - const auto& nextJointArray = currentFrame.getJointArray(); - QVector jointArray(prevJointArray.size()); - QVector jointRotations(prevJointArray.size()); // FIXME: remove once the setRawJointData is fixed - QVector jointTranslations(prevJointArray.size()); // FIXME: remove once the setRawJointData is fixed - - for (int i = 0; i < jointArray.size(); i++) { - const auto& prevJoint = prevJointArray[i]; - const auto& nextJoint = nextJointArray[i]; - auto& joint = jointArray[i]; - - // Rotation - joint.rotationSet = prevJoint.rotationSet || nextJoint.rotationSet; - if (joint.rotationSet) { - joint.rotation = safeMix(prevJoint.rotation, nextJoint.rotation, _frameInterpolationFactor); - jointRotations[i] = joint.rotation; // FIXME: remove once the setRawJointData is fixed - } - - joint.translationSet = prevJoint.translationSet || nextJoint.translationSet; - if (joint.translationSet) { - joint.translation = glm::mix(prevJoint.translation, nextJoint.translation, _frameInterpolationFactor); - jointTranslations[i] = joint.translation; // FIXME: remove once the setRawJointData is fixed - } - } - - // _avatar->setRawJointData(jointArray); // FIXME: Enable once the setRawJointData is fixed - _avatar->setJointRotations(jointRotations); // FIXME: remove once the setRawJointData is fixed - // _avatar->setJointTranslations(jointTranslations); // FIXME: remove once the setRawJointData is fixed - - HeadData* head = const_cast(_avatar->getHeadData()); - if (head) { - // Make sure fake face tracker connection doesn't get turned off - _avatar->setForceFaceTrackerConnected(true); - - QVector blendCoef(currentFrame.getBlendshapeCoefficients().size()); - for (int i = 0; i < currentFrame.getBlendshapeCoefficients().size(); ++i) { - blendCoef[i] = glm::mix(currentFrame.getBlendshapeCoefficients()[i], - nextFrame.getBlendshapeCoefficients()[i], - _frameInterpolationFactor); - } - head->setBlendshapeCoefficients(blendCoef); - - float leanSideways = glm::mix(currentFrame.getLeanSideways(), - nextFrame.getLeanSideways(), - _frameInterpolationFactor); - head->setLeanSideways(leanSideways); - - float leanForward = glm::mix(currentFrame.getLeanForward(), - nextFrame.getLeanForward(), - _frameInterpolationFactor); - head->setLeanForward(leanForward); - - glm::quat headRotation = safeMix(currentFrame.getHeadRotation(), - nextFrame.getHeadRotation(), - _frameInterpolationFactor); - glm::vec3 eulers = glm::degrees(safeEulerAngles(headRotation)); - head->setFinalPitch(eulers.x); - head->setFinalYaw(eulers.y); - head->setFinalRoll(eulers.z); - - - glm::vec3 lookAt = glm::mix(currentFrame.getLookAtPosition(), - nextFrame.getLookAtPosition(), - _frameInterpolationFactor); - head->setLookAtPosition(context->position + context->orientation * lookAt); - } else { - qCDebug(avatars) << "WARNING: Player couldn't find head data."; - } - - _options.position = _avatar->getPosition(); - _options.orientation = _avatar->getOrientation(); - _injector->setOptions(_options); -} - -void Player::setCurrentFrame(int currentFrame) { - if (_recording && currentFrame >= _recording->getFrameNumber()) { - stopPlaying(); - return; - } - - _currentFrame = currentFrame; - _timerOffset = _recording->getFrameTimestamp(_currentFrame); - - if (isPlaying()) { - _timer.start(); - setAudioInjectorPosition(); - } else { - _pausedFrame = _currentFrame; - } -} - -void Player::setCurrentTime(int currentTime) { - if (currentTime >= _recording->getLength()) { - stopPlaying(); - return; - } - - // Find correct frame - int lowestBound = 0; - int highestBound = _recording->getFrameNumber() - 1; - while (lowestBound + 1 != highestBound) { - assert(lowestBound < highestBound); - - int bestGuess = lowestBound + - (highestBound - lowestBound) * - (float)(currentTime - _recording->getFrameTimestamp(lowestBound)) / - (float)(_recording->getFrameTimestamp(highestBound) - _recording->getFrameTimestamp(lowestBound)); - - if (_recording->getFrameTimestamp(bestGuess) <= currentTime) { - if (currentTime < _recording->getFrameTimestamp(bestGuess + 1)) { - lowestBound = bestGuess; - highestBound = bestGuess + 1; - } else { - lowestBound = bestGuess + 1; - } - } else { - if (_recording->getFrameTimestamp(bestGuess - 1) <= currentTime) { - lowestBound = bestGuess - 1; - highestBound = bestGuess; - } else { - highestBound = bestGuess - 1; - } - } - } - - setCurrentFrame(lowestBound); -} - -void Player::setVolume(float volume) { - _options.volume = volume; - if (_injector) { - _injector->setOptions(_options); - } - qCDebug(avatars) << "New volume: " << volume; -} - -void Player::setAudioOffset(int audioOffset) { - _audioOffset = audioOffset; -} - -void Player::setAudioInjectorPosition() { - int MSEC_PER_SEC = 1000; - int FRAME_SIZE = sizeof(AudioConstants::AudioSample) * _recording->numberAudioChannel(); - int currentAudioFrame = elapsed() * FRAME_SIZE * (AudioConstants::SAMPLE_RATE / MSEC_PER_SEC); - _injector->setCurrentSendOffset(currentAudioFrame); -} - -void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) { - _playFromCurrentPosition = playFromCurrentLocation; -} - -bool Player::computeCurrentFrame() { - if (!isPlaying()) { - _currentFrame = INVALID_FRAME; - return false; - } - if (_currentFrame < 0) { - _currentFrame = 0; - } - - qint64 elapsed = glm::clamp(Player::elapsed() - _audioOffset, (qint64)0, (qint64)_recording->getLength()); - while (_currentFrame < _recording->getFrameNumber() && - _recording->getFrameTimestamp(_currentFrame) < elapsed) { - ++_currentFrame; - } - - while(_currentFrame > 0 && - _recording->getFrameTimestamp(_currentFrame) > elapsed) { - --_currentFrame; - } - - if (_currentFrame == _recording->getFrameNumber() - 1) { - --_currentFrame; - _frameInterpolationFactor = 1.0f; - } else { - qint64 currentTimestamps = _recording->getFrameTimestamp(_currentFrame); - qint64 nextTimestamps = _recording->getFrameTimestamp(_currentFrame + 1); - _frameInterpolationFactor = (float)(elapsed - currentTimestamps) / - (float)(nextTimestamps - currentTimestamps); - } - - if (_frameInterpolationFactor < 0.0f || _frameInterpolationFactor > 1.0f) { - _frameInterpolationFactor = 0.0f; - qCDebug(avatars) << "Invalid frame interpolation value: overriding"; - } - return true; -} - -#endif diff --git a/libraries/avatars/src/Player.h b/libraries/avatars/src/Player.h deleted file mode 100644 index 558ff309e6..0000000000 --- a/libraries/avatars/src/Player.h +++ /dev/null @@ -1,94 +0,0 @@ -// -// Player.h -// -// -// Created by Clement on 9/17/14. -// 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_Player_h -#define hifi_Player_h - -#include - -#if 0 -#include - -#include - -#include "Recording.h" - -class AvatarData; -class Player; - -typedef QSharedPointer PlayerPointer; -typedef QWeakPointer WeakPlayerPointer; - -/// Plays back a recording -class Player { -public: - Player(AvatarData* avatar); - - bool isPlaying() const; - bool isPaused() const; - qint64 elapsed() const; - - RecordingPointer getRecording() const { return _recording; } - int getCurrentFrame() const { return _currentFrame; } - -public slots: - void startPlaying(); - void stopPlaying(); - void pausePlayer(); - void loadFromFile(const QString& file); - void loadRecording(RecordingPointer recording); - void play(); - - void setCurrentFrame(int currentFrame); - void setCurrentTime(int currentTime); - - void setVolume(float volume); - void setAudioOffset(int audioOffset); - - void setPlayFromCurrentLocation(bool playFromCurrentPosition); - void setLoop(bool loop) { _loop = loop; } - void useAttachements(bool useAttachments) { _useAttachments = useAttachments; } - void useDisplayName(bool useDisplayName) { _useDisplayName = useDisplayName; } - void useHeadModel(bool useHeadURL) { _useHeadURL = useHeadURL; } - void useSkeletonModel(bool useSkeletonURL) { _useSkeletonURL = useSkeletonURL; } - -private: - void setupAudioThread(); - void cleanupAudioThread(); - void loopRecording(); - void setAudioInjectorPosition(); - bool computeCurrentFrame(); - - AvatarData* _avatar; - RecordingPointer _recording; - int _currentFrame; - float _frameInterpolationFactor; - int _pausedFrame; - - QElapsedTimer _timer; - int _timerOffset; - int _audioOffset; - - QThread* _audioThread; - QSharedPointer _injector; - AudioInjectorOptions _options; - - RecordingContext _currentContext; - bool _playFromCurrentPosition; - bool _loop; - bool _useAttachments; - bool _useDisplayName; - bool _useHeadURL; - bool _useSkeletonURL; -}; -#endif - -#endif // hifi_Player_h diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp deleted file mode 100644 index 343302d472..0000000000 --- a/libraries/avatars/src/Recorder.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// -// Recorder.cpp -// -// -// Created by Clement on 8/7/14. -// 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 -// - - -#if 0 -#include -#include -#include - -#include "AvatarData.h" -#include "AvatarLogging.h" -#include "Recorder.h" - -Recorder::Recorder(AvatarData* avatar) : - _recording(new Recording()), - _avatar(avatar) -{ - _timer.invalidate(); -} - -bool Recorder::isRecording() const { - return _timer.isValid(); -} - -qint64 Recorder::elapsed() const { - if (isRecording()) { - return _timer.elapsed(); - } else { - return 0; - } -} - -void Recorder::startRecording() { - qCDebug(avatars) << "Recorder::startRecording()"; - _recording->clear(); - - RecordingContext& context = _recording->getContext(); - context.globalTimestamp = usecTimestampNow(); - context.domain = DependencyManager::get()->getDomainHandler().getHostname(); - context.position = _avatar->getPosition(); - context.orientation = _avatar->getOrientation(); - context.scale = _avatar->getTargetScale(); - context.headModel = _avatar->getFaceModelURL().toString(); - context.skeletonModel = _avatar->getSkeletonModelURL().toString(); - context.displayName = _avatar->getDisplayName(); - context.attachments = _avatar->getAttachmentData(); - - context.orientationInv = glm::inverse(context.orientation); - - bool wantDebug = false; - if (wantDebug) { - qCDebug(avatars) << "Recorder::startRecording(): Recording Context"; - qCDebug(avatars) << "Global timestamp:" << context.globalTimestamp; - qCDebug(avatars) << "Domain:" << context.domain; - qCDebug(avatars) << "Position:" << context.position; - qCDebug(avatars) << "Orientation:" << context.orientation; - qCDebug(avatars) << "Scale:" << context.scale; - qCDebug(avatars) << "Head URL:" << context.headModel; - qCDebug(avatars) << "Skeleton URL:" << context.skeletonModel; - qCDebug(avatars) << "Display Name:" << context.displayName; - qCDebug(avatars) << "Num Attachments:" << context.attachments.size(); - - for (int i = 0; i < context.attachments.size(); ++i) { - qCDebug(avatars) << "Model URL:" << context.attachments[i].modelURL; - qCDebug(avatars) << "Joint Name:" << context.attachments[i].jointName; - qCDebug(avatars) << "Translation:" << context.attachments[i].translation; - qCDebug(avatars) << "Rotation:" << context.attachments[i].rotation; - qCDebug(avatars) << "Scale:" << context.attachments[i].scale; - } - } - - _timer.start(); - record(); -} - -void Recorder::stopRecording() { - qCDebug(avatars) << "Recorder::stopRecording()"; - _timer.invalidate(); - - qCDebug(avatars).nospace() << "Recorded " << _recording->getFrameNumber() << " during " << _recording->getLength() << " msec (" << _recording->getFrameNumber() / (_recording->getLength() / 1000.0f) << " fps)"; -} - -void Recorder::saveToFile(const QString& file) { - if (_recording->isEmpty()) { - qCDebug(avatars) << "Cannot save recording to file, recording is empty."; - } - - writeRecordingToFile(_recording, file); -} - -void Recorder::record() { - if (isRecording()) { - const RecordingContext& context = _recording->getContext(); - RecordingFrame frame; - frame.setBlendshapeCoefficients(_avatar->getHeadData()->getBlendshapeCoefficients()); - - // Capture the full skeleton joint data - auto& jointData = _avatar->getRawJointData(); - frame.setJointArray(jointData); - - frame.setTranslation(context.orientationInv * (_avatar->getPosition() - context.position)); - frame.setRotation(context.orientationInv * _avatar->getOrientation()); - frame.setScale(_avatar->getTargetScale() / context.scale); - - const HeadData* head = _avatar->getHeadData(); - if (head) { - glm::vec3 rotationDegrees = glm::vec3(head->getFinalPitch(), - head->getFinalYaw(), - head->getFinalRoll()); - frame.setHeadRotation(glm::quat(glm::radians(rotationDegrees))); - frame.setLeanForward(head->getLeanForward()); - frame.setLeanSideways(head->getLeanSideways()); - glm::vec3 relativeLookAt = context.orientationInv * - (head->getLookAtPosition() - context.position); - frame.setLookAtPosition(relativeLookAt); - } - - bool wantDebug = false; - if (wantDebug) { - qCDebug(avatars) << "Recording frame #" << _recording->getFrameNumber(); - qCDebug(avatars) << "Blendshapes:" << frame.getBlendshapeCoefficients().size(); - qCDebug(avatars) << "JointArray:" << frame.getJointArray().size(); - qCDebug(avatars) << "Translation:" << frame.getTranslation(); - qCDebug(avatars) << "Rotation:" << frame.getRotation(); - qCDebug(avatars) << "Scale:" << frame.getScale(); - qCDebug(avatars) << "Head rotation:" << frame.getHeadRotation(); - qCDebug(avatars) << "Lean Forward:" << frame.getLeanForward(); - qCDebug(avatars) << "Lean Sideways:" << frame.getLeanSideways(); - qCDebug(avatars) << "LookAtPosition:" << frame.getLookAtPosition(); - } - - _recording->addFrame(_timer.elapsed(), frame); - } -} - -void Recorder::recordAudio(const QByteArray& audioByteArray) { - _recording->addAudioPacket(audioByteArray); -} -#endif diff --git a/libraries/avatars/src/Recorder.h b/libraries/avatars/src/Recorder.h deleted file mode 100644 index 15bffcec8b..0000000000 --- a/libraries/avatars/src/Recorder.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// Recorder.h -// libraries/avatars/src -// -// Created by Clement on 8/7/14. -// 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_Recorder_h -#define hifi_Recorder_h - -#include - -#if 0 -#include "Recording.h" - -template -class QSharedPointer; - -class AttachmentData; -class AvatarData; -class Recorder; -class Recording; - -typedef QSharedPointer RecorderPointer; -typedef QWeakPointer WeakRecorderPointer; - -/// Records a recording -class Recorder : public QObject { - Q_OBJECT -public: - Recorder(AvatarData* avatar); - - bool isRecording() const; - qint64 elapsed() const; - - RecordingPointer getRecording() const { return _recording; } - -public slots: - void startRecording(); - void stopRecording(); - void saveToFile(const QString& file); - void record(); - void recordAudio(const QByteArray& audioArray); - -private: - QElapsedTimer _timer; - RecordingPointer _recording; - - AvatarData* _avatar; -}; -#endif - -#endif // hifi_Recorder_h diff --git a/libraries/avatars/src/Recording.cpp b/libraries/avatars/src/Recording.cpp deleted file mode 100644 index 884ed495be..0000000000 --- a/libraries/avatars/src/Recording.cpp +++ /dev/null @@ -1,663 +0,0 @@ -// -// Recording.cpp -// -// -// Created by Clement on 9/17/14. -// 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 -// - -#if 0 -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "AvatarData.h" -#include "AvatarLogging.h" -#include "Recording.h" - -// HFR file format magic number (Inspired by PNG) -// (decimal) 17 72 70 82 13 10 26 10 -// (hexadecimal) 11 48 46 52 0d 0a 1a 0a -// (ASCII C notation) \021 H F R \r \n \032 \n -static const int MAGIC_NUMBER_SIZE = 8; -static const char MAGIC_NUMBER[MAGIC_NUMBER_SIZE] = {17, 72, 70, 82, 13, 10, 26, 10}; -// Version (Major, Minor) -static const QPair VERSION(0, 2); - -int SCALE_RADIX = 10; -int BLENDSHAPE_RADIX = 15; -int LEAN_RADIX = 7; - -void RecordingFrame::setBlendshapeCoefficients(QVector blendshapeCoefficients) { - _blendshapeCoefficients = blendshapeCoefficients; -} - -int Recording::getLength() const { - if (_timestamps.isEmpty()) { - return 0; - } - return _timestamps.last(); -} - -qint32 Recording::getFrameTimestamp(int i) const { - if (i >= _timestamps.size()) { - return getLength(); - } - if (i < 0) { - return 0; - } - return _timestamps[i]; -} - -const RecordingFrame& Recording::getFrame(int i) const { - assert(i < _timestamps.size()); - return _frames[i]; -} - - -int Recording::numberAudioChannel() const { - // Check for stereo audio - float MSEC_PER_SEC = 1000.0f; - float channelLength = ((float)getLength() / MSEC_PER_SEC) * AudioConstants::SAMPLE_RATE * - sizeof(AudioConstants::AudioSample); - return glm::round((float)getAudioData().size() / channelLength); -} - -void Recording::addFrame(int timestamp, RecordingFrame &frame) { - _timestamps << timestamp; - _frames << frame; -} - -void Recording::clear() { - _timestamps.clear(); - _frames.clear(); - _audioData.clear(); -} - -void writeVec3(QDataStream& stream, const glm::vec3& value) { - unsigned char buffer[sizeof(value)]; - memcpy(buffer, &value, sizeof(value)); - stream.writeRawData(reinterpret_cast(buffer), sizeof(value)); -} - -bool readVec3(QDataStream& stream, glm::vec3& value) { - unsigned char buffer[sizeof(value)]; - stream.readRawData(reinterpret_cast(buffer), sizeof(value)); - memcpy(&value, buffer, sizeof(value)); - return true; -} - -void writeQuat(QDataStream& stream, const glm::quat& value) { - unsigned char buffer[256]; - int writtenToBuffer = packOrientationQuatToBytes(buffer, value); - stream.writeRawData(reinterpret_cast(buffer), writtenToBuffer); -} - -bool readQuat(QDataStream& stream, glm::quat& value) { - int quatByteSize = 4 * 2; // 4 floats * 2 bytes - unsigned char buffer[256]; - stream.readRawData(reinterpret_cast(buffer), quatByteSize); - int readFromBuffer = unpackOrientationQuatFromBytes(buffer, value); - if (readFromBuffer != quatByteSize) { - return false; - } - return true; -} - -bool readFloat(QDataStream& stream, float& value, int radix) { - int floatByteSize = 2; // 1 floats * 2 bytes - int16_t buffer[256]; - stream.readRawData(reinterpret_cast(buffer), floatByteSize); - int readFromBuffer = unpackFloatScalarFromSignedTwoByteFixed(buffer, &value, radix); - if (readFromBuffer != floatByteSize) { - return false; - } - return true; -} - -void writeRecordingToFile(RecordingPointer recording, const QString& filename) { - if (!recording || recording->getFrameNumber() < 1) { - qCDebug(avatars) << "Can't save empty recording"; - return; - } - - QElapsedTimer timer; - QFile file(filename); - if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)){ - qCDebug(avatars) << "Couldn't open " << filename; - return; - } - timer.start(); - qCDebug(avatars) << "Writing recording to " << filename << "."; - - QDataStream fileStream(&file); - - // HEADER - file.write(MAGIC_NUMBER, MAGIC_NUMBER_SIZE); // Magic number - fileStream << VERSION; // File format version - const qint64 dataOffsetPos = file.pos(); - fileStream << (quint16)0; // Save two empty bytes for the data offset - const qint64 dataLengthPos = file.pos(); - fileStream << (quint32)0; // Save four empty bytes for the data offset - const quint64 crc16Pos = file.pos(); - fileStream << (quint16)0; // Save two empty bytes for the CRC-16 - - - // METADATA - // TODO - - - - // Write data offset - quint16 dataOffset = file.pos(); - file.seek(dataOffsetPos); - fileStream << dataOffset; - file.seek(dataOffset); - - // CONTEXT - RecordingContext& context = recording->getContext(); - // Global Timestamp - fileStream << context.globalTimestamp; - // Domain - fileStream << context.domain; - // Position - writeVec3(fileStream, context.position); - // Orientation - writeQuat(fileStream, context.orientation); - // Scale - fileStream << context.scale; - // Head model - fileStream << context.headModel; - // Skeleton model - fileStream << context.skeletonModel; - // Display name - fileStream << context.displayName; - // Attachements - fileStream << (quint8)context.attachments.size(); - foreach (AttachmentData data, context.attachments) { - // Model - fileStream << data.modelURL.toString(); - // Joint name - fileStream << data.jointName; - // Position - writeVec3(fileStream, data.translation); - // Orientation - writeQuat(fileStream, data.rotation); - // Scale - fileStream << data.scale; - } - - // RECORDING - fileStream << recording->_timestamps; - - QBitArray mask; - quint32 numBlendshapes = 0; - quint32 numJoints = 0; - - for (int i = 0; i < recording->_timestamps.size(); ++i) { - mask.fill(false); - int maskIndex = 0; - QByteArray buffer; - QDataStream stream(&buffer, QIODevice::WriteOnly); - RecordingFrame& previousFrame = recording->_frames[(i != 0) ? i - 1 : i]; - RecordingFrame& frame = recording->_frames[i]; - - // Blendshape Coefficients - if (i == 0) { - numBlendshapes = frame.getBlendshapeCoefficients().size(); - stream << numBlendshapes; - mask.resize(mask.size() + numBlendshapes); - } - for (quint32 j = 0; j < numBlendshapes; ++j) { - if (i == 0 || - frame._blendshapeCoefficients[j] != previousFrame._blendshapeCoefficients[j]) { - stream << frame.getBlendshapeCoefficients()[j]; - mask.setBit(maskIndex); - } - ++maskIndex; - } - - const auto& jointArray = frame.getJointArray(); - if (i == 0) { - numJoints = jointArray.size(); - stream << numJoints; - // 2 fields per joints - mask.resize(mask.size() + numJoints * 2); - } - for (quint32 j = 0; j < numJoints; j++) { - const auto& joint = jointArray[j]; - if (true) { //(joint.rotationSet) { - writeQuat(stream, joint.rotation); - mask.setBit(maskIndex); - } - maskIndex++; - if (joint.translationSet) { - writeVec3(stream, joint.translation); - mask.setBit(maskIndex); - } - maskIndex++; - } - - // Translation - if (i == 0) { - mask.resize(mask.size() + 1); - } - if (i == 0 || frame._translation != previousFrame._translation) { - writeVec3(stream, frame._translation); - mask.setBit(maskIndex); - } - maskIndex++; - - // Rotation - if (i == 0) { - mask.resize(mask.size() + 1); - } - if (i == 0 || frame._rotation != previousFrame._rotation) { - writeQuat(stream, frame._rotation); - mask.setBit(maskIndex); - } - maskIndex++; - - // Scale - if (i == 0) { - mask.resize(mask.size() + 1); - } - if (i == 0 || frame._scale != previousFrame._scale) { - stream << frame._scale; - mask.setBit(maskIndex); - } - maskIndex++; - - // Head Rotation - if (i == 0) { - mask.resize(mask.size() + 1); - } - if (i == 0 || frame._headRotation != previousFrame._headRotation) { - writeQuat(stream, frame._headRotation); - mask.setBit(maskIndex); - } - maskIndex++; - - // Lean Sideways - if (i == 0) { - mask.resize(mask.size() + 1); - } - if (i == 0 || frame._leanSideways != previousFrame._leanSideways) { - stream << frame._leanSideways; - mask.setBit(maskIndex); - } - maskIndex++; - - // Lean Forward - if (i == 0) { - mask.resize(mask.size() + 1); - } - if (i == 0 || frame._leanForward != previousFrame._leanForward) { - stream << frame._leanForward; - mask.setBit(maskIndex); - } - maskIndex++; - - // LookAt Position - if (i == 0) { - mask.resize(mask.size() + 1); - } - if (i == 0 || frame._lookAtPosition != previousFrame._lookAtPosition) { - writeVec3(stream, frame._lookAtPosition); - mask.setBit(maskIndex); - } - maskIndex++; - - fileStream << mask; - fileStream << buffer; - } - - fileStream << recording->getAudioData(); - - qint64 writingTime = timer.restart(); - // Write data length and CRC-16 - quint32 dataLength = file.pos() - dataOffset; - file.seek(dataOffset); // Go to beginning of data for checksum - quint16 crc16 = qChecksum(file.readAll().constData(), dataLength); - - file.seek(dataLengthPos); - fileStream << dataLength; - file.seek(crc16Pos); - fileStream << crc16; - file.seek(dataOffset + dataLength); - - bool wantDebug = true; - if (wantDebug) { - qCDebug(avatars) << "[DEBUG] WRITE recording"; - qCDebug(avatars) << "Header:"; - qCDebug(avatars) << "File Format version:" << VERSION; - qCDebug(avatars) << "Data length:" << dataLength; - qCDebug(avatars) << "Data offset:" << dataOffset; - qCDebug(avatars) << "CRC-16:" << crc16; - - qCDebug(avatars) << "Context block:"; - qCDebug(avatars) << "Global timestamp:" << context.globalTimestamp; - qCDebug(avatars) << "Domain:" << context.domain; - qCDebug(avatars) << "Position:" << context.position; - qCDebug(avatars) << "Orientation:" << context.orientation; - qCDebug(avatars) << "Scale:" << context.scale; - qCDebug(avatars) << "Head Model:" << context.headModel; - qCDebug(avatars) << "Skeleton Model:" << context.skeletonModel; - qCDebug(avatars) << "Display Name:" << context.displayName; - qCDebug(avatars) << "Num Attachments:" << context.attachments.size(); - for (int i = 0; i < context.attachments.size(); ++i) { - qCDebug(avatars) << "Model URL:" << context.attachments[i].modelURL; - qCDebug(avatars) << "Joint Name:" << context.attachments[i].jointName; - qCDebug(avatars) << "Translation:" << context.attachments[i].translation; - qCDebug(avatars) << "Rotation:" << context.attachments[i].rotation; - qCDebug(avatars) << "Scale:" << context.attachments[i].scale; - } - - qCDebug(avatars) << "Recording:"; - qCDebug(avatars) << "Total frames:" << recording->getFrameNumber(); - qCDebug(avatars) << "Audio array:" << recording->getAudioData().size(); - } - - qint64 checksumTime = timer.elapsed(); - qCDebug(avatars) << "Wrote" << file.size() << "bytes in" << writingTime + checksumTime << "ms. (" << checksumTime << "ms for checksum)"; -} - -RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString& filename) { - QByteArray byteArray; - QUrl url(filename); - QElapsedTimer timer; - timer.start(); // timer used for debug informations (download/parsing time) - - // Aquire the data and place it in byteArray - // Return if data unavailable - if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "ftp") { - // Download file if necessary - qCDebug(avatars) << "Downloading recording at" << url; - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = QNetworkRequest(url); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(networkRequest); - QEventLoop loop; - QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - loop.exec(); // wait for file - if (reply->error() != QNetworkReply::NoError) { - qCDebug(avatars) << "Error while downloading recording: " << reply->error(); - reply->deleteLater(); - return recording; - } - byteArray = reply->readAll(); - reply->deleteLater(); - // print debug + restart timer - qCDebug(avatars) << "Downloaded " << byteArray.size() << " bytes in " << timer.restart() << " ms."; - } else { - // If local file, just read it. - qCDebug(avatars) << "Reading recording from " << filename << "."; - QFile file(filename); - if (!file.open(QIODevice::ReadOnly)){ - qCDebug(avatars) << "Could not open local file: " << url; - return recording; - } - byteArray = file.readAll(); - file.close(); - } - - if (!filename.endsWith(".hfr") && !filename.endsWith(".HFR")) { - qCDebug(avatars) << "File extension not recognized"; - } - - // Reset the recording passed in the arguments - if (!recording) { - recording = QSharedPointer::create(); - } - - QDataStream fileStream(byteArray); - - // HEADER - QByteArray magicNumber(MAGIC_NUMBER, MAGIC_NUMBER_SIZE); - if (!byteArray.startsWith(magicNumber)) { - qCDebug(avatars) << "ERROR: This is not a .HFR file. (Magic Number incorrect)"; - return recording; - } - fileStream.skipRawData(MAGIC_NUMBER_SIZE); - - QPair version; - fileStream >> version; // File format version - if (version != VERSION && version != QPair(0,1)) { - qCDebug(avatars) << "ERROR: This file format version is not supported."; - return recording; - } - - quint16 dataOffset = 0; - fileStream >> dataOffset; - quint32 dataLength = 0; - fileStream >> dataLength; - quint16 crc16 = 0; - fileStream >> crc16; - - - // Check checksum - quint16 computedCRC16 = qChecksum(byteArray.constData() + dataOffset, dataLength); - if (computedCRC16 != crc16) { - qCDebug(avatars) << "Checksum does not match. Bailling!"; - recording.clear(); - return recording; - } - - // METADATA - // TODO - - - - // CONTEXT - RecordingContext& context = recording->getContext(); - // Global Timestamp - fileStream >> context.globalTimestamp; - // Domain - fileStream >> context.domain; - // Position - if (!readVec3(fileStream, context.position)) { - qCDebug(avatars) << "Couldn't read file correctly. (Invalid vec3)"; - recording.clear(); - return recording; - } - // Orientation - if (!readQuat(fileStream, context.orientation)) { - qCDebug(avatars) << "Couldn't read file correctly. (Invalid quat)"; - recording.clear(); - return recording; - } - - // Scale - if (version == QPair(0,1)) { - readFloat(fileStream, context.scale, SCALE_RADIX); - } else { - fileStream >> context.scale; - } - // Head model - fileStream >> context.headModel; - // Skeleton model - fileStream >> context.skeletonModel; - // Display Name - fileStream >> context.displayName; - - // Attachements - quint8 numAttachments = 0; - fileStream >> numAttachments; - for (int i = 0; i < numAttachments; ++i) { - AttachmentData data; - // Model - QString modelURL; - fileStream >> modelURL; - data.modelURL = modelURL; - // Joint name - fileStream >> data.jointName; - // Translation - if (!readVec3(fileStream, data.translation)) { - qCDebug(avatars) << "Couldn't read attachment correctly. (Invalid vec3)"; - continue; - } - // Rotation - if (!readQuat(fileStream, data.rotation)) { - qCDebug(avatars) << "Couldn't read attachment correctly. (Invalid quat)"; - continue; - } - - // Scale - if (version == QPair(0,1)) { - readFloat(fileStream, data.scale, SCALE_RADIX); - } else { - fileStream >> data.scale; - } - context.attachments << data; - } - - quint32 numBlendshapes = 0; - quint32 numJoints = 0; - // RECORDING - fileStream >> recording->_timestamps; - - for (int i = 0; i < recording->_timestamps.size(); ++i) { - QBitArray mask; - QByteArray buffer; - QDataStream stream(&buffer, QIODevice::ReadOnly); - RecordingFrame frame; - RecordingFrame& previousFrame = (i == 0) ? frame : recording->_frames.last(); - - fileStream >> mask; - fileStream >> buffer; - int maskIndex = 0; - - // Blendshape Coefficients - if (i == 0) { - stream >> numBlendshapes; - } - frame._blendshapeCoefficients.resize(numBlendshapes); - for (quint32 j = 0; j < numBlendshapes; ++j) { - if (!mask[maskIndex++]) { - frame._blendshapeCoefficients[j] = previousFrame._blendshapeCoefficients[j]; - } else if (version == QPair(0,1)) { - readFloat(stream, frame._blendshapeCoefficients[j], BLENDSHAPE_RADIX); - } else { - stream >> frame._blendshapeCoefficients[j]; - } - } - // Joint Array - if (i == 0) { - stream >> numJoints; - } - - frame._jointArray.resize(numJoints); - for (quint32 j = 0; j < numJoints; ++j) { - auto& joint = frame._jointArray[2]; - - if (mask[maskIndex++] && readQuat(stream, joint.rotation)) { - joint.rotationSet = true; - } else { - joint.rotationSet = false; - } - - if (mask[maskIndex++] || readVec3(stream, joint.translation)) { - joint.translationSet = true; - } else { - joint.translationSet = false; - } - } - - if (!mask[maskIndex++] || !readVec3(stream, frame._translation)) { - frame._translation = previousFrame._translation; - } - - if (!mask[maskIndex++] || !readQuat(stream, frame._rotation)) { - frame._rotation = previousFrame._rotation; - } - - if (!mask[maskIndex++]) { - frame._scale = previousFrame._scale; - } else if (version == QPair(0,1)) { - readFloat(stream, frame._scale, SCALE_RADIX); - } else { - stream >> frame._scale; - } - - if (!mask[maskIndex++] || !readQuat(stream, frame._headRotation)) { - frame._headRotation = previousFrame._headRotation; - } - - if (!mask[maskIndex++]) { - frame._leanSideways = previousFrame._leanSideways; - } else if (version == QPair(0,1)) { - readFloat(stream, frame._leanSideways, LEAN_RADIX); - } else { - stream >> frame._leanSideways; - } - - if (!mask[maskIndex++]) { - frame._leanForward = previousFrame._leanForward; - } else if (version == QPair(0,1)) { - readFloat(stream, frame._leanForward, LEAN_RADIX); - } else { - stream >> frame._leanForward; - } - - if (!mask[maskIndex++] || !readVec3(stream, frame._lookAtPosition)) { - frame._lookAtPosition = previousFrame._lookAtPosition; - } - - recording->_frames << frame; - } - - QByteArray audioArray; - fileStream >> audioArray; - recording->addAudioPacket(audioArray); - - bool wantDebug = true; - if (wantDebug) { - qCDebug(avatars) << "[DEBUG] READ recording"; - qCDebug(avatars) << "Header:"; - qCDebug(avatars) << "File Format version:" << VERSION; - qCDebug(avatars) << "Data length:" << dataLength; - qCDebug(avatars) << "Data offset:" << dataOffset; - qCDebug(avatars) << "CRC-16:" << crc16; - - qCDebug(avatars) << "Context block:"; - qCDebug(avatars) << "Global timestamp:" << context.globalTimestamp; - qCDebug(avatars) << "Domain:" << context.domain; - qCDebug(avatars) << "Position:" << context.position; - qCDebug(avatars) << "Orientation:" << context.orientation; - qCDebug(avatars) << "Scale:" << context.scale; - qCDebug(avatars) << "Head Model:" << context.headModel; - qCDebug(avatars) << "Skeleton Model:" << context.skeletonModel; - qCDebug(avatars) << "Display Name:" << context.displayName; - qCDebug(avatars) << "Num Attachments:" << numAttachments; - for (int i = 0; i < numAttachments; ++i) { - qCDebug(avatars) << "Model URL:" << context.attachments[i].modelURL; - qCDebug(avatars) << "Joint Name:" << context.attachments[i].jointName; - qCDebug(avatars) << "Translation:" << context.attachments[i].translation; - qCDebug(avatars) << "Rotation:" << context.attachments[i].rotation; - qCDebug(avatars) << "Scale:" << context.attachments[i].scale; - } - - qCDebug(avatars) << "Recording:"; - qCDebug(avatars) << "Total frames:" << recording->getFrameNumber(); - qCDebug(avatars) << "Audio array:" << recording->getAudioData().size(); - - } - - qCDebug(avatars) << "Read " << byteArray.size() << " bytes in " << timer.elapsed() << " ms."; - return recording; -} - -#endif diff --git a/libraries/avatars/src/Recording.h b/libraries/avatars/src/Recording.h deleted file mode 100644 index a5829b1e2f..0000000000 --- a/libraries/avatars/src/Recording.h +++ /dev/null @@ -1,131 +0,0 @@ -// -// Recording.h -// -// -// Created by Clement on 9/17/14. -// 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_Recording_h -#define hifi_Recording_h - -#if 0 - -#include -#include - -#include -#include - -template -class QSharedPointer; - -class AttachmentData; -class Recording; -class RecordingFrame; -class Sound; -class JointData; - -typedef QSharedPointer RecordingPointer; - -/// Stores recordings static data -class RecordingContext { -public: - quint64 globalTimestamp; - QString domain; - glm::vec3 position; - glm::quat orientation; - float scale; - QString headModel; - QString skeletonModel; - QString displayName; - QVector attachments; - - // This avoids recomputation every frame while recording. - glm::quat orientationInv; -}; - -/// Stores a recording -class Recording { -public: - bool isEmpty() const { return _timestamps.isEmpty(); } - int getLength() const; // in ms - - RecordingContext& getContext() { return _context; } - int getFrameNumber() const { return _frames.size(); } - qint32 getFrameTimestamp(int i) const; - const RecordingFrame& getFrame(int i) const; - const QByteArray& getAudioData() const { return _audioData; } - int numberAudioChannel() const; - -protected: - void addFrame(int timestamp, RecordingFrame& frame); - void addAudioPacket(const QByteArray& byteArray) { _audioData.append(byteArray); } - void clear(); - -private: - RecordingContext _context; - QVector _timestamps; - QVector _frames; - - QByteArray _audioData; - - friend class Recorder; - friend class Player; - friend void writeRecordingToFile(RecordingPointer recording, const QString& file); - friend RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString& file); - friend RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QString& filename, - const QByteArray& byteArray); -}; - -/// Stores the different values associated to one recording frame -class RecordingFrame { -public: - QVector getBlendshapeCoefficients() const { return _blendshapeCoefficients; } - QVector getJointArray() const { return _jointArray; } - glm::vec3 getTranslation() const { return _translation; } - glm::quat getRotation() const { return _rotation; } - float getScale() const { return _scale; } - glm::quat getHeadRotation() const { return _headRotation; } - float getLeanSideways() const { return _leanSideways; } - float getLeanForward() const { return _leanForward; } - glm::vec3 getLookAtPosition() const { return _lookAtPosition; } - -protected: - void setBlendshapeCoefficients(QVector blendshapeCoefficients); - void setJointArray(const QVector& jointArray) { _jointArray = jointArray; } - void setTranslation(const glm::vec3& translation) { _translation = translation; } - void setRotation(const glm::quat& rotation) { _rotation = rotation; } - void setScale(float scale) { _scale = scale; } - void setHeadRotation(glm::quat headRotation) { _headRotation = headRotation; } - void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; } - void setLeanForward(float leanForward) { _leanForward = leanForward; } - void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; } - -private: - QVector _blendshapeCoefficients; - QVector _jointArray; - - glm::vec3 _translation; - glm::quat _rotation; - float _scale; - glm::quat _headRotation; - float _leanSideways; - float _leanForward; - glm::vec3 _lookAtPosition; - - friend class Recorder; - friend void writeRecordingToFile(RecordingPointer recording, const QString& file); - friend RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString& file); - friend RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QString& filename, - const QByteArray& byteArray); -}; - -void writeRecordingToFile(RecordingPointer recording, const QString& filename); -RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString& filename); -RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QString& filename, const QByteArray& byteArray); -#endif -#endif // hifi_Recording_h From f1e0742aeb6aa5e65de04c96b974bd197082e023 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 23 Nov 2015 10:20:59 -0800 Subject: [PATCH 14/29] Add getTargetFrameRate(). --- interface/src/Application.h | 5 ++++- interface/src/avatar/AvatarManager.cpp | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index 356fabce71..730158c689 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -159,7 +159,10 @@ public: bool isForeground() const { return _isForeground; } float getFps() const { return _fps; } - float getTargetFramePeriod() { return isHMDMode() ? 1.0f / 75.0f : 1.0f / 60.0f; } + float const HMD_TARGET_FRAME_RATE = 75.0f; + float const DESKTOP_TARGET_FRAME_RATE = 60.0f; + float getTargetFrameRate() { return isHMDMode() ? HMD_TARGET_FRAME_RATE : DESKTOP_TARGET_FRAME_RATE; } + float getTargetFramePeriod() { return isHMDMode() ? 1.0f / HMD_TARGET_FRAME_RATE : 1.0f / DESKTOP_TARGET_FRAME_RATE; } // same as 1/getTargetFrameRate, but w/compile-time division float getLastInstanteousFps() const { return _lastInstantaneousFps; } float getLastPaintWait() const { return _lastPaintWait; }; float getLastDeducedNonVSyncFps() const { return _lastDeducedNonVSyncFps; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e7701dcb24..6d8182dfc0 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -91,7 +91,7 @@ void AvatarManager::init() { } scene->enqueuePendingChanges(pendingChanges); - const float target_fps = 1.0f / qApp->getTargetFramePeriod(); + const float target_fps = qApp->getTargetFrameRate(); _renderDistanceController.setMeasuredValueSetpoint(target_fps); const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters _renderDistanceController.setControlledValueHighLimit(1.0f / SMALLEST_REASONABLE_HORIZON); @@ -139,7 +139,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceTimer perfTimer("otherAvatars"); - _renderDistanceController.setMeasuredValueSetpoint(1.0f / qApp->getTargetFramePeriod()); // No problem updating in flight. + _renderDistanceController.setMeasuredValueSetpoint(qApp->getTargetFrameRate()); // No problem updating in flight. // The PID controller raises the controlled value when the measured value goes up. // The measured value is frame rate. When the controlled value (1 / render cutoff distance) // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate From 591280807795d9e6e05478475a1d58beb0c95b63 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 23 Nov 2015 10:27:49 -0800 Subject: [PATCH 15/29] getShouldSkipRendering() => !getShouldRender(). --- interface/src/avatar/Avatar.h | 2 +- interface/src/avatar/AvatarManager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index cb238dc82c..64ef321f1c 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -140,7 +140,7 @@ public: Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; } Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; } - Q_INVOKABLE bool getShouldSkipRendering() const { return _shouldSkipRender; } + Q_INVOKABLE bool getShouldRender() const { return !_shouldSkipRender; } /// Scales a world space position vector relative to the avatar position and scale /// \param vector position to be scaled. Will store the result diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 6d8182dfc0..40f469961c 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -167,7 +167,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } else { avatar->startUpdate(); avatar->simulate(deltaTime); - if (!avatar->getShouldSkipRendering()) { + if (avatar->getShouldRender()) { renderableCount++; } avatar->endUpdate(); From c94a5389a0af9144182e90659c0807d61a91d22b Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 23 Nov 2015 10:31:37 -0800 Subject: [PATCH 16/29] comments --- libraries/shared/src/PIDController.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h index e89ff2a693..0b2411530a 100644 --- a/libraries/shared/src/PIDController.h +++ b/libraries/shared/src/PIDController.h @@ -38,9 +38,9 @@ public: float getControlledValueLowLimit() const { return _controlledValueLowLimit; } float getControlledValueHighLimit() const { return _controlledValueHighLimit; } float getAntiWindupFactor() const { return _antiWindupFactor; } // default 10 - float getKP() const { return _kp; } - float getKI() const { return _ki; } - float getKD() const { return _kd; } + float getKP() const { return _kp; } // proportional to error. See comment above class. + float getKI() const { return _ki; } // to time integral of error + float getKD() const { return _kd; } // to time derivative of error float getAccumulatedValueHighLimit() const { return getAntiWindupFactor() * getMeasuredValueSetpoint(); } float getAccumulatedValueLowLimit() const { return -getAntiWindupFactor() * getMeasuredValueSetpoint(); } From 6339c2c48f0d75977bdbf844c9ffcfad606dcec7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 23 Nov 2015 17:25:31 -0600 Subject: [PATCH 17/29] fix for unnecessary model URL updating --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index f1be8611e1..0aef6d0af3 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -219,7 +219,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (hasModel()) { if (_model) { - if (getModelURL() != _model->getURL().toString()) { + if (getModelURL() != _model->getURL().toEncoded()) { qDebug() << "Updating model URL: " << getModelURL(); _model->setURL(getModelURL()); } From 3738f219366066071926b7c195056911ef0271d5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 23 Nov 2015 22:34:17 -0800 Subject: [PATCH 18/29] Fix occasional deadlock in loading recordings --- .../script-engine/src/RecordingScriptingInterface.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 6f82e22b8d..25abf2e09e 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -56,10 +56,12 @@ bool RecordingScriptingInterface::loadRecording(const QString& url) { using namespace recording; auto loader = ClipCache::instance().getClipLoader(url); - QEventLoop loop; - QObject::connect(loader.data(), &Resource::loaded, &loop, &QEventLoop::quit); - QObject::connect(loader.data(), &Resource::failed, &loop, &QEventLoop::quit); - loop.exec(); + if (!loader->isLoaded()) { + QEventLoop loop; + QObject::connect(loader.data(), &Resource::loaded, &loop, &QEventLoop::quit); + QObject::connect(loader.data(), &Resource::failed, &loop, &QEventLoop::quit); + loop.exec(); + } if (!loader->isLoaded()) { qWarning() << "Clip failed to load from " << url; From bc84265e991800fc95dd31b4fb1d2a1117df22bb Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 23 Nov 2015 22:35:12 -0800 Subject: [PATCH 19/29] Additional checking when serializing transforms to json --- libraries/shared/src/Transform.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/libraries/shared/src/Transform.cpp b/libraries/shared/src/Transform.cpp index d0aef8a9be..c51b3dae4b 100644 --- a/libraries/shared/src/Transform.cpp +++ b/libraries/shared/src/Transform.cpp @@ -128,17 +128,25 @@ QJsonObject Transform::toJson(const Transform& transform) { } QJsonObject result; - auto json = toJsonValue(transform.getTranslation()); - if (!json.isNull()) { - result[JSON_TRANSLATION] = json; + if (transform.getTranslation() != vec3()) { + auto json = toJsonValue(transform.getTranslation()); + if (!json.isNull()) { + result[JSON_TRANSLATION] = json; + } } - json = toJsonValue(transform.getRotation()); - if (!json.isNull()) { - result[JSON_ROTATION] = json; + + if (transform.getRotation() != quat()) { + auto json = toJsonValue(transform.getRotation()); + if (!json.isNull()) { + result[JSON_ROTATION] = json; + } } - json = toJsonValue(transform.getScale()); - if (!json.isNull()) { - result[JSON_SCALE] = json; + + if (transform.getScale() != vec3(1.0f)) { + auto json = toJsonValue(transform.getScale()); + if (!json.isNull()) { + result[JSON_SCALE] = json; + } } return result; } From 5c2b980a4532dfe3ff95b3590858a1642fc7c7f6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 23 Nov 2015 22:36:07 -0800 Subject: [PATCH 20/29] Fixing avatar facial blendshapes and attachments in recordings --- interface/src/avatar/MyAvatar.cpp | 20 ++- libraries/avatars/src/AvatarData.cpp | 247 ++++++++++++++++----------- libraries/avatars/src/AvatarData.h | 9 +- libraries/avatars/src/HeadData.cpp | 113 ++++++++++-- libraries/avatars/src/HeadData.h | 4 + 5 files changed, 274 insertions(+), 119 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index cac60b2381..56ab066faf 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -180,10 +180,22 @@ MyAvatar::MyAvatar(RigPointer rig) : setPosition(dummyAvatar.getPosition()); setOrientation(dummyAvatar.getOrientation()); - // FIXME attachments - // FIXME joints - // FIXME head lean - // FIXME head orientation + if (!dummyAvatar.getAttachmentData().isEmpty()) { + setAttachmentData(dummyAvatar.getAttachmentData()); + } + + auto headData = dummyAvatar.getHeadData(); + if (headData && _headData) { + // blendshapes + if (!headData->getBlendshapeCoefficients().isEmpty()) { + _headData->setBlendshapeCoefficients(headData->getBlendshapeCoefficients()); + } + // head lean + _headData->setLeanForward(headData->getLeanForward()); + _headData->setLeanSideways(headData->getLeanSideways()); + // head orientation + _headData->setLookAtPosition(headData->getLookAtPosition()); + } }); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 0574f712bc..0f588b5013 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1297,8 +1297,51 @@ void AvatarData::updateJointMappings() { } } -AttachmentData::AttachmentData() : - scale(1.0f) { +static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); +static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); +static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform"); + +QJsonObject AttachmentData::toJson() const { + QJsonObject result; + if (modelURL.isValid() && !modelURL.isEmpty()) { + result[JSON_ATTACHMENT_URL] = modelURL.toString(); + } + if (!jointName.isEmpty()) { + result[JSON_ATTACHMENT_JOINT_NAME] = jointName; + } + // FIXME the transform constructor that takes rot/scale/translation + // doesn't return the correct value for isIdentity() + Transform transform; + transform.setRotation(rotation); + transform.setScale(scale); + transform.setTranslation(translation); + if (!transform.isIdentity()) { + result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform); + } + return result; +} + +void AttachmentData::fromJson(const QJsonObject& json) { + if (json.contains(JSON_ATTACHMENT_URL)) { + const QString modelURLTemp = json[JSON_ATTACHMENT_URL].toString(); + if (modelURLTemp != modelURL.toString()) { + modelURL = modelURLTemp; + } + } + + if (json.contains(JSON_ATTACHMENT_JOINT_NAME)) { + const QString jointNameTemp = json[JSON_ATTACHMENT_JOINT_NAME].toString(); + if (jointNameTemp != jointName) { + jointName = jointNameTemp; + } + } + + if (json.contains(JSON_ATTACHMENT_TRANSFORM)) { + Transform transform = Transform::fromJson(json[JSON_ATTACHMENT_TRANSFORM]); + translation = transform.getTranslation(); + rotation = transform.getRotation(); + scale = transform.getScale().x; + } } bool AttachmentData::operator==(const AttachmentData& other) const { @@ -1399,15 +1442,11 @@ static const QString JSON_AVATAR_BASIS = QStringLiteral("basisTransform"); static const QString JSON_AVATAR_RELATIVE = QStringLiteral("relativeTransform"); static const QString JSON_AVATAR_JOINT_ARRAY = QStringLiteral("jointArray"); static const QString JSON_AVATAR_HEAD = QStringLiteral("head"); -static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation"); -static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes"); -static const QString JSON_AVATAR_HEAD_LEAN_FORWARD = QStringLiteral("leanForward"); -static const QString JSON_AVATAR_HEAD_LEAN_SIDEWAYS = QStringLiteral("leanSideways"); -static const QString JSON_AVATAR_HEAD_LOOKAT = QStringLiteral("lookAt"); static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel"); static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments"); +static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); QJsonValue toJsonValue(const JointData& joint) { QJsonArray result; @@ -1428,93 +1467,84 @@ JointData jointDataFromJsonValue(const QJsonValue& json) { return result; } -// Every frame will store both a basis for the recording and a relative transform -// This allows the application to decide whether playback should be relative to an avatar's -// transform at the start of playback, or relative to the transform of the recorded -// avatar -QByteArray AvatarData::toFrame(const AvatarData& avatar) { +QJsonObject AvatarData::toJson() const { QJsonObject root; - if (!avatar.getFaceModelURL().isEmpty()) { - root[JSON_AVATAR_HEAD_MODEL] = avatar.getFaceModelURL().toString(); + if (!getFaceModelURL().isEmpty()) { + root[JSON_AVATAR_HEAD_MODEL] = getFaceModelURL().toString(); } - if (!avatar.getSkeletonModelURL().isEmpty()) { - root[JSON_AVATAR_BODY_MODEL] = avatar.getSkeletonModelURL().toString(); + if (!getSkeletonModelURL().isEmpty()) { + root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); } - if (!avatar.getDisplayName().isEmpty()) { - root[JSON_AVATAR_DISPLAY_NAME] = avatar.getDisplayName(); + if (!getDisplayName().isEmpty()) { + root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName(); } - if (!avatar.getAttachmentData().isEmpty()) { - // FIXME serialize attachment data + if (!getAttachmentData().isEmpty()) { + QJsonArray attachmentsJson; + for (auto attachment : getAttachmentData()) { + attachmentsJson.push_back(attachment.toJson()); + } + root[JSON_AVATAR_ATTACHEMENTS] = attachmentsJson; } - auto recordingBasis = avatar.getRecordingBasis(); + auto recordingBasis = getRecordingBasis(); if (recordingBasis) { root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis); // Find the relative transform - auto relativeTransform = recordingBasis->relativeTransform(avatar.getTransform()); - root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform); + auto relativeTransform = recordingBasis->relativeTransform(getTransform()); + if (!relativeTransform.isIdentity()) { + root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform); + } } else { - root[JSON_AVATAR_RELATIVE] = Transform::toJson(avatar.getTransform()); + root[JSON_AVATAR_RELATIVE] = Transform::toJson(getTransform()); + } + + auto scale = getTargetScale(); + if (scale != 1.0f) { + root[JSON_AVATAR_SCALE] = scale; } // Skeleton pose QJsonArray jointArray; - for (const auto& joint : avatar.getRawJointData()) { + for (const auto& joint : getRawJointData()) { jointArray.push_back(toJsonValue(joint)); } root[JSON_AVATAR_JOINT_ARRAY] = jointArray; - const HeadData* head = avatar.getHeadData(); + const HeadData* head = getHeadData(); if (head) { - QJsonObject headJson; - QJsonArray blendshapeCoefficients; - for (const auto& blendshapeCoefficient : head->getBlendshapeCoefficients()) { - blendshapeCoefficients.push_back(blendshapeCoefficient); + auto headJson = head->toJson(); + if (!headJson.isEmpty()) { + root[JSON_AVATAR_HEAD] = headJson; } - headJson[JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS] = blendshapeCoefficients; - headJson[JSON_AVATAR_HEAD_ROTATION] = toJsonValue(head->getRawOrientation()); - headJson[JSON_AVATAR_HEAD_LEAN_FORWARD] = QJsonValue(head->getLeanForward()); - headJson[JSON_AVATAR_HEAD_LEAN_SIDEWAYS] = QJsonValue(head->getLeanSideways()); - vec3 relativeLookAt = glm::inverse(avatar.getOrientation()) * - (head->getLookAtPosition() - avatar.getPosition()); - headJson[JSON_AVATAR_HEAD_LOOKAT] = toJsonValue(relativeLookAt); - root[JSON_AVATAR_HEAD] = headJson; } - - return QJsonDocument(root).toBinaryData(); + return root; } -void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) { - QJsonDocument doc = QJsonDocument::fromBinaryData(frameData); -#ifdef WANT_JSON_DEBUG - qDebug() << doc.toJson(QJsonDocument::JsonFormat::Indented); -#endif - QJsonObject root = doc.object(); - - if (root.contains(JSON_AVATAR_HEAD_MODEL)) { - auto faceModelURL = root[JSON_AVATAR_HEAD_MODEL].toString(); - if (faceModelURL != result.getFaceModelURL().toString()) { +void AvatarData::fromJson(const QJsonObject& json) { + if (json.contains(JSON_AVATAR_HEAD_MODEL)) { + auto faceModelURL = json[JSON_AVATAR_HEAD_MODEL].toString(); + if (faceModelURL != getFaceModelURL().toString()) { QUrl faceModel(faceModelURL); if (faceModel.isValid()) { - result.setFaceModelURL(faceModel); + setFaceModelURL(faceModel); } } } - if (root.contains(JSON_AVATAR_BODY_MODEL)) { - auto bodyModelURL = root[JSON_AVATAR_BODY_MODEL].toString(); - if (bodyModelURL != result.getSkeletonModelURL().toString()) { - result.setSkeletonModelURL(bodyModelURL); + if (json.contains(JSON_AVATAR_BODY_MODEL)) { + auto bodyModelURL = json[JSON_AVATAR_BODY_MODEL].toString(); + if (bodyModelURL != getSkeletonModelURL().toString()) { + setSkeletonModelURL(bodyModelURL); } } - if (root.contains(JSON_AVATAR_DISPLAY_NAME)) { - auto newDisplayName = root[JSON_AVATAR_DISPLAY_NAME].toString(); - if (newDisplayName != result.getDisplayName()) { - result.setDisplayName(newDisplayName); + if (json.contains(JSON_AVATAR_DISPLAY_NAME)) { + auto newDisplayName = json[JSON_AVATAR_DISPLAY_NAME].toString(); + if (newDisplayName != getDisplayName()) { + setDisplayName(newDisplayName); } - } + } - if (root.contains(JSON_AVATAR_RELATIVE)) { + if (json.contains(JSON_AVATAR_RELATIVE)) { // During playback you can either have the recording basis set to the avatar current state // meaning that all playback is relative to this avatars starting position, or // the basis can be loaded from the recording, meaning the playback is relative to the @@ -1522,70 +1552,83 @@ void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) { // The first is more useful for playing back recordings on your own avatar, while // the latter is more useful for playing back other avatars within your scene. - auto currentBasis = result.getRecordingBasis(); + auto currentBasis = getRecordingBasis(); if (!currentBasis) { - currentBasis = std::make_shared(Transform::fromJson(root[JSON_AVATAR_BASIS])); + currentBasis = std::make_shared(Transform::fromJson(json[JSON_AVATAR_BASIS])); } - auto relativeTransform = Transform::fromJson(root[JSON_AVATAR_RELATIVE]); + auto relativeTransform = Transform::fromJson(json[JSON_AVATAR_RELATIVE]); auto worldTransform = currentBasis->worldTransform(relativeTransform); - result.setPosition(worldTransform.getTranslation()); - result.setOrientation(worldTransform.getRotation()); - - // TODO: find a way to record/playback the Scale of the avatar - //result.setTargetScale(worldTransform.getScale().x); + setPosition(worldTransform.getTranslation()); + setOrientation(worldTransform.getRotation()); } + if (json.contains(JSON_AVATAR_SCALE)) { + setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble()); + } - if (root.contains(JSON_AVATAR_ATTACHEMENTS)) { - // FIXME de-serialize attachment data + if (json.contains(JSON_AVATAR_ATTACHEMENTS) && json[JSON_AVATAR_ATTACHEMENTS].isArray()) { + QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray(); + QVector attachments; + for (auto attachmentJson : attachmentsJson) { + AttachmentData attachment; + attachment.fromJson(attachmentJson.toObject()); + attachments.push_back(attachment); + } + setAttachmentData(attachments); } // Joint rotations are relative to the avatar, so they require no basis correction - if (root.contains(JSON_AVATAR_JOINT_ARRAY)) { + if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { QVector jointArray; - QJsonArray jointArrayJson = root[JSON_AVATAR_JOINT_ARRAY].toArray(); + QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray(); jointArray.reserve(jointArrayJson.size()); int i = 0; for (const auto& jointJson : jointArrayJson) { auto joint = jointDataFromJsonValue(jointJson); jointArray.push_back(joint); - result.setJointData(i, joint.rotation, joint.translation); - result._jointData[i].rotationSet = true; // Have to do that to broadcast the avatar new pose + setJointData(i, joint.rotation, joint.translation); + _jointData[i].rotationSet = true; // Have to do that to broadcast the avatar new pose i++; } - result.setRawJointData(jointArray); + setRawJointData(jointArray); } -#if 0 // Most head data is relative to the avatar, and needs no basis correction, // but the lookat vector does need correction - HeadData* head = result._headData; - if (head && root.contains(JSON_AVATAR_HEAD)) { - QJsonObject headJson = root[JSON_AVATAR_HEAD].toObject(); - if (headJson.contains(JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS)) { - QVector blendshapeCoefficients; - QJsonArray blendshapeCoefficientsJson = headJson[JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS].toArray(); - for (const auto& blendshapeCoefficient : blendshapeCoefficientsJson) { - blendshapeCoefficients.push_back((float)blendshapeCoefficient.toDouble()); - } - head->setBlendshapeCoefficients(blendshapeCoefficients); - } - if (headJson.contains(JSON_AVATAR_HEAD_ROTATION)) { - head->setOrientation(quatFromJsonValue(headJson[JSON_AVATAR_HEAD_ROTATION])); - } - if (headJson.contains(JSON_AVATAR_HEAD_LEAN_FORWARD)) { - head->setLeanForward((float)headJson[JSON_AVATAR_HEAD_LEAN_FORWARD].toDouble()); - } - if (headJson.contains(JSON_AVATAR_HEAD_LEAN_SIDEWAYS)) { - head->setLeanSideways((float)headJson[JSON_AVATAR_HEAD_LEAN_SIDEWAYS].toDouble()); - } - if (headJson.contains(JSON_AVATAR_HEAD_LOOKAT)) { - auto relativeLookAt = vec3FromJsonValue(headJson[JSON_AVATAR_HEAD_LOOKAT]); - if (glm::length2(relativeLookAt) > 0.01) { - head->setLookAtPosition((result.getOrientation() * relativeLookAt) + result.getPosition()); - } + if (json.contains(JSON_AVATAR_HEAD)) { + if (!_headData) { + _headData = new HeadData(this); } + _headData->fromJson(json[JSON_AVATAR_HEAD].toObject()); + } +} + +// Every frame will store both a basis for the recording and a relative transform +// This allows the application to decide whether playback should be relative to an avatar's +// transform at the start of playback, or relative to the transform of the recorded +// avatar +QByteArray AvatarData::toFrame(const AvatarData& avatar) { + QJsonObject root = avatar.toJson(); +#ifdef WANT_JSON_DEBUG + { + QJsonObject obj = root; + obj.remove(JSON_AVATAR_JOINT_ARRAY); + qDebug().noquote() << QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Indented); } #endif + return QJsonDocument(root).toBinaryData(); +} + + +void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) { + QJsonDocument doc = QJsonDocument::fromBinaryData(frameData); +#ifdef WANT_JSON_DEBUG + { + QJsonObject obj = doc.object(); + obj.remove(JSON_AVATAR_JOINT_ARRAY); + qDebug().noquote() << QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Indented); + } +#endif + result.fromJson(doc.object()); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 847a369185..0f04878637 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -342,6 +342,8 @@ public: void clearRecordingBasis(); TransformPointer getRecordingBasis() const; void setRecordingBasis(TransformPointer recordingBasis = TransformPointer()); + QJsonObject toJson() const; + void fromJson(const QJsonObject& json); public slots: void sendAvatarDataPacket(); @@ -449,13 +451,14 @@ public: QString jointName; glm::vec3 translation; glm::quat rotation; - float scale; - - AttachmentData(); + float scale { 1.0f }; bool isValid() const { return modelURL.isValid(); } bool operator==(const AttachmentData& other) const; + + QJsonObject toJson() const; + void fromJson(const QJsonObject& json); }; QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment); diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index e971b184c8..1d664aa3ff 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -9,13 +9,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "HeadData.h" + +#include + +#include +#include #include #include +#include #include "AvatarData.h" -#include "HeadData.h" /// The names of the blendshapes expected by Faceshift, terminated with an empty string. extern const char* FACESHIFT_BLENDSHAPES[]; @@ -58,6 +63,7 @@ glm::quat HeadData::getOrientation() const { return _owningAvatar->getOrientation() * getRawOrientation(); } + void HeadData::setOrientation(const glm::quat& orientation) { // rotate body about vertical axis glm::quat bodyOrientation = _owningAvatar->getOrientation(); @@ -72,19 +78,24 @@ void HeadData::setOrientation(const glm::quat& orientation) { _baseRoll = eulers.z; } -void HeadData::setBlendshape(QString name, float val) { - static bool hasInitializedLookupMap = false; +//Lazily construct a lookup map from the blendshapes +static const QMap& getBlendshapesLookupMap() { + static std::once_flag once; static QMap blendshapeLookupMap; - //Lazily construct a lookup map from the blendshapes - if (!hasInitializedLookupMap) { + std::call_once(once, [&] { for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { - blendshapeLookupMap[FACESHIFT_BLENDSHAPES[i]] = i; + blendshapeLookupMap[FACESHIFT_BLENDSHAPES[i]] = i; } - hasInitializedLookupMap = true; - } + }); + return blendshapeLookupMap; +} + + +void HeadData::setBlendshape(QString name, float val) { + const auto& blendshapeLookupMap = getBlendshapesLookupMap(); //Check to see if the named blendshape exists, and then set its value if it does - QMap::iterator it = blendshapeLookupMap.find(name); + auto it = blendshapeLookupMap.find(name); if (it != blendshapeLookupMap.end()) { if (_blendshapeCoefficients.size() <= it.value()) { _blendshapeCoefficients.resize(it.value() + 1); @@ -92,3 +103,85 @@ void HeadData::setBlendshape(QString name, float val) { _blendshapeCoefficients[it.value()] = val; } } + +static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation"); +static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes"); +static const QString JSON_AVATAR_HEAD_LEAN_FORWARD = QStringLiteral("leanForward"); +static const QString JSON_AVATAR_HEAD_LEAN_SIDEWAYS = QStringLiteral("leanSideways"); +static const QString JSON_AVATAR_HEAD_LOOKAT = QStringLiteral("lookAt"); + +QJsonObject HeadData::toJson() const { + QJsonObject headJson; + const auto& blendshapeLookupMap = getBlendshapesLookupMap(); + QJsonObject blendshapesJson; + for (auto name : blendshapeLookupMap.keys()) { + auto index = blendshapeLookupMap[name]; + if (index >= _blendshapeCoefficients.size()) { + continue; + } + auto value = _blendshapeCoefficients[index]; + if (value == 0.0f) { + continue; + } + blendshapesJson[name] = value; + } + if (!blendshapesJson.isEmpty()) { + headJson[JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS] = blendshapesJson; + } + if (getRawOrientation() != quat()) { + headJson[JSON_AVATAR_HEAD_ROTATION] = toJsonValue(getRawOrientation()); + } + if (getLeanForward() != 0.0f) { + headJson[JSON_AVATAR_HEAD_LEAN_FORWARD] = getLeanForward(); + } + if (getLeanSideways() != 0.0f) { + headJson[JSON_AVATAR_HEAD_LEAN_SIDEWAYS] = getLeanSideways(); + } + auto lookat = getLookAtPosition(); + if (lookat != vec3()) { + vec3 relativeLookAt = glm::inverse(_owningAvatar->getOrientation()) * + (getLookAtPosition() - _owningAvatar->getPosition()); + headJson[JSON_AVATAR_HEAD_LOOKAT] = toJsonValue(relativeLookAt); + } + return headJson; +} + +void HeadData::fromJson(const QJsonObject& json) { + if (json.contains(JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS)) { + auto jsonValue = json[JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS]; + if (jsonValue.isArray()) { + QVector blendshapeCoefficients; + QJsonArray blendshapeCoefficientsJson = jsonValue.toArray(); + for (const auto& blendshapeCoefficient : blendshapeCoefficientsJson) { + blendshapeCoefficients.push_back((float)blendshapeCoefficient.toDouble()); + setBlendshapeCoefficients(blendshapeCoefficients); + } + } else if (jsonValue.isObject()) { + QJsonObject blendshapeCoefficientsJson = jsonValue.toObject(); + for (const QString& name : blendshapeCoefficientsJson.keys()) { + float value = (float)blendshapeCoefficientsJson[name].toDouble(); + setBlendshape(name, value); + } + } else { + qWarning() << "Unable to deserialize head json: " << jsonValue; + } + } + + if (json.contains(JSON_AVATAR_HEAD_ROTATION)) { + setOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION])); + } + if (json.contains(JSON_AVATAR_HEAD_LEAN_FORWARD)) { + setLeanForward((float)json[JSON_AVATAR_HEAD_LEAN_FORWARD].toDouble()); + } + if (json.contains(JSON_AVATAR_HEAD_LEAN_SIDEWAYS)) { + setLeanSideways((float)json[JSON_AVATAR_HEAD_LEAN_SIDEWAYS].toDouble()); + } + + if (json.contains(JSON_AVATAR_HEAD_LOOKAT)) { + auto relativeLookAt = vec3FromJsonValue(json[JSON_AVATAR_HEAD_LOOKAT]); + if (glm::length2(relativeLookAt) > 0.01f) { + setLookAtPosition((_owningAvatar->getOrientation() * relativeLookAt) + _owningAvatar->getPosition()); + } + } +} + diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 38503f6e1e..dac266f4a2 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -28,6 +28,7 @@ const float MIN_HEAD_ROLL = -50.0f; const float MAX_HEAD_ROLL = 50.0f; class AvatarData; +class QJsonObject; class HeadData { public: @@ -83,6 +84,9 @@ public: friend class AvatarData; + QJsonObject toJson() const; + void fromJson(const QJsonObject& json); + protected: // degrees float _baseYaw; From 0e19e50047cdeb0ceaf327669d02e5b6ad7bd8a2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 24 Nov 2015 11:17:46 -0600 Subject: [PATCH 21/29] cleaner handling for QString/QUrl conversion/comparison --- .../src/RenderableModelEntityItem.cpp | 10 +++++++--- .../entities-renderer/src/RenderableModelEntityItem.h | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 0aef6d0af3..1d00db22d5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -219,9 +219,13 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (hasModel()) { if (_model) { - if (getModelURL() != _model->getURL().toEncoded()) { - qDebug() << "Updating model URL: " << getModelURL(); - _model->setURL(getModelURL()); + // convert the QString from getModelURL to a URL + _url = getModelURL(); + + // check if the URL has changed + if (_url != _model->getURL()) { + qDebug() << "Updating model URL: " << _url; + _model->setURL(_url); } render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index c4e36c240a..3fdad50c9e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -79,6 +79,7 @@ private: bool _originalTexturesRead = false; QVector> _points; bool _dimensionsInitialized = true; + QUrl _url; render::ItemID _myMetaItem; From 0d6b9194486d8bd50adb5f9f18bf032cee02e515 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 24 Nov 2015 11:22:30 -0600 Subject: [PATCH 22/29] cleanup printing of updated model URL --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 1d00db22d5..08de327fa7 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -224,7 +224,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { // check if the URL has changed if (_url != _model->getURL()) { - qDebug() << "Updating model URL: " << _url; + qDebug().noquote() << "Updating model URL: " << _url.toDisplayString(); _model->setURL(_url); } From 5a7b0dd1adc8b673029f93c0dbcc43891ed53d85 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 24 Nov 2015 11:46:51 -0600 Subject: [PATCH 23/29] put the parsed model URL in ModelEntityItem --- .../src/RenderableModelEntityItem.cpp | 10 ++++------ .../entities-renderer/src/RenderableModelEntityItem.h | 1 - libraries/entities/src/ModelEntityItem.h | 4 +++- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 08de327fa7..782458894d 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -219,13 +219,11 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (hasModel()) { if (_model) { - // convert the QString from getModelURL to a URL - _url = getModelURL(); - // check if the URL has changed - if (_url != _model->getURL()) { - qDebug().noquote() << "Updating model URL: " << _url.toDisplayString(); - _model->setURL(_url); + auto& currentURL = getParsedModelURL(); + if (currentURL != _model->getURL()) { + qDebug().noquote() << "Updating model URL: " << currentURL.toDisplayString(); + _model->setURL(currentURL); } render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 3fdad50c9e..c4e36c240a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -79,7 +79,6 @@ private: bool _originalTexturesRead = false; QVector> _points; bool _dimensionsInitialized = true; - QUrl _url; render::ItemID _myMetaItem; diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index e8ffcab3e7..fb41ac4b77 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -63,6 +63,7 @@ public: static const QString DEFAULT_MODEL_URL; const QString& getModelURL() const { return _modelURL; } + const QUrl& getParsedModelURL() const { return _parsedModelURL; } static const QString DEFAULT_COMPOUND_SHAPE_URL; const QString& getCompoundShapeURL() const { return _compoundShapeURL; } @@ -75,7 +76,7 @@ public: } // model related properties - void setModelURL(const QString& url) { _modelURL = url; } + void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); } virtual void setCompoundShapeURL(const QString& url); @@ -134,6 +135,7 @@ protected: rgbColor _color; QString _modelURL; + QUrl _parsedModelURL; QString _compoundShapeURL; AnimationPropertyGroup _animationProperties; From 5135c9ddedfb380ccd777bcbffe66bf17350f300 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 23 Nov 2015 18:04:32 -0800 Subject: [PATCH 24/29] whitespace cleanup --- interface/src/Application.cpp | 166 ++++++++++++------------ libraries/physics/src/PhysicsEngine.cpp | 4 +- 2 files changed, 85 insertions(+), 85 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9acd2b82c6..4dc218ecdc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -679,7 +679,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : })); userInputMapper->registerDevice(_applicationStateDevice); - + // Setup the keyboardMouseDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); @@ -749,7 +749,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _oldHandRightClick[0] = false; _oldHandLeftClick[1] = false; _oldHandRightClick[1] = false; - + auto applicationUpdater = DependencyManager::get(); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); @@ -768,7 +768,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it auto entityScriptingInterface = DependencyManager::get(); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, + connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, [this, entityScriptingInterface](const EntityItemID& entityItemID, const MouseEvent& event) { if (_keyboardFocusedItem != entityItemID) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; @@ -817,7 +817,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : }); // If the user clicks somewhere where there is NO entity at all, we will release focus - connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity, + connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity, [=](const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; if (_keyboardFocusHighlight) { @@ -826,17 +826,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : }); connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); - + qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); } void Application::aboutToQuit() { emit beforeAboutToQuit(); - + getActiveDisplayPlugin()->deactivate(); - + _aboutToQuit = true; - + cleanupBeforeQuit(); } @@ -860,16 +860,16 @@ void Application::cleanupBeforeQuit() { _keyboardFocusHighlight = nullptr; _entities.clear(); // this will allow entity scripts to properly shutdown - + auto nodeList = DependencyManager::get(); - + // send the domain a disconnect packet, force stoppage of domain-server check-ins nodeList->getDomainHandler().disconnect(); nodeList->setIsShuttingDown(true); - + // tell the packet receiver we're shutting down, so it can drop packets nodeList->getPacketReceiver().setShouldDropPackets(true); - + _entities.shutdown(); // tell the entities system we're shutting down, so it will stop running scripts ScriptEngine::stopAllScripts(this); // stop all currently running global scripts @@ -947,7 +947,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - + // cleanup the AssetClient thread QThread* assetThread = DependencyManager::get()->thread(); DependencyManager::destroy(); @@ -955,14 +955,14 @@ Application::~Application() { assetThread->wait(); QThread* nodeThread = DependencyManager::get()->thread(); - + // remove the NodeList from the DependencyManager DependencyManager::destroy(); // ask the node thread to quit and wait until it is done nodeThread->quit(); nodeThread->wait(); - + Leapmotion::destroy(); RealSense::destroy(); @@ -1058,7 +1058,7 @@ void Application::initializeUi() { resizeGL(); } }); - + // This will set up the input plugins UI _activeInputPlugins.clear(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { @@ -1100,8 +1100,8 @@ void Application::paintGL() { return; } - // Some plugins process message events, potentially leading to - // re-entering a paint event. don't allow further processing if this + // Some plugins process message events, potentially leading to + // re-entering a paint event. don't allow further processing if this // happens if (_inPaint) { return; @@ -1137,17 +1137,17 @@ void Application::paintGL() { if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { PerformanceTimer perfTimer("Mirror"); auto primaryFbo = DependencyManager::get()->getPrimaryFramebufferDepthColor(); - + renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; renderRearViewMirror(&renderArgs, _mirrorViewRect); renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; - + { float ratio = ((float)QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale()); // Flip the src and destination rect horizontally to do the mirror auto mirrorRect = glm::ivec4(0, 0, _mirrorViewRect.width() * ratio, _mirrorViewRect.height() * ratio); auto mirrorRectDest = glm::ivec4(mirrorRect.z, mirrorRect.y, mirrorRect.x, mirrorRect.w); - + auto selfieFbo = DependencyManager::get()->getSelfieFramebuffer(); gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) { batch.setFramebuffer(selfieFbo); @@ -1169,9 +1169,9 @@ void Application::paintGL() { { PerformanceTimer perfTimer("CameraUpdates"); - + auto myAvatar = getMyAvatar(); - + myAvatar->startCapture(); if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN); @@ -1208,26 +1208,26 @@ void Application::paintGL() { * (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f))); } else { _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + myAvatar->getOrientation() + + myAvatar->getOrientation() * (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f))); } } } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { if (isHMDMode()) { glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix()); - _myCamera.setRotation(myAvatar->getWorldAlignedOrientation() + _myCamera.setRotation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation); glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix()); - _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + glm::vec3(0, _raiseMirror * myAvatar->getScale(), 0) + _myCamera.setPosition(myAvatar->getDefaultEyePosition() + + glm::vec3(0, _raiseMirror * myAvatar->getScale(), 0) + (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) * - glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror + glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror + (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset); } else { - _myCamera.setRotation(myAvatar->getWorldAlignedOrientation() + _myCamera.setRotation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); - _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + glm::vec3(0, _raiseMirror * myAvatar->getScale(), 0) + _myCamera.setPosition(myAvatar->getDefaultEyePosition() + + glm::vec3(0, _raiseMirror * myAvatar->getScale(), 0) + (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); } @@ -1246,7 +1246,7 @@ void Application::paintGL() { } } } - // Update camera position + // Update camera position if (!isHMDMode()) { _myCamera.update(1.0f / _fps); } @@ -1264,12 +1264,12 @@ void Application::paintGL() { if (displayPlugin->isStereo()) { // Stereo modes will typically have a larger projection matrix overall, // so we ask for the 'mono' projection matrix, which for stereo and HMD - // plugins will imply the combined projection for both eyes. + // plugins will imply the combined projection for both eyes. // // This is properly implemented for the Oculus plugins, but for OpenVR - // and Stereo displays I'm not sure how to get / calculate it, so we're - // just relying on the left FOV in each case and hoping that the - // overall culling margin of error doesn't cause popping in the + // and Stereo displays I'm not sure how to get / calculate it, so we're + // just relying on the left FOV in each case and hoping that the + // overall culling margin of error doesn't cause popping in the // right eye. There are FIXMEs in the relevant plugins _myCamera.setProjection(displayPlugin->getProjection(Mono, _myCamera.getProjection())); renderArgs._context->enableStereo(true); @@ -1279,11 +1279,11 @@ void Application::paintGL() { auto hmdInterface = DependencyManager::get(); float IPDScale = hmdInterface->getIPDScale(); // FIXME we probably don't need to set the projection matrix every frame, - // only when the display plugin changes (or in non-HMD modes when the user + // only when the display plugin changes (or in non-HMD modes when the user // changes the FOV manually, which right now I don't think they can. for_each_eye([&](Eye eye) { - // For providing the stereo eye views, the HMD head pose has already been - // applied to the avatar, so we need to get the difference between the head + // For providing the stereo eye views, the HMD head pose has already been + // applied to the avatar, so we need to get the difference between the head // pose applied to the avatar and the per eye pose, and use THAT as // the per-eye stereo matrix adjustment. mat4 eyeToHead = displayPlugin->getEyeToHeadTransform(eye); @@ -1293,10 +1293,10 @@ void Application::paintGL() { mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * IPDScale); eyeOffsets[eye] = eyeOffsetTransform; - // Tell the plugin what pose we're using to render. In this case we're just using the - // unmodified head pose because the only plugin that cares (the Oculus plugin) uses it - // for rotational timewarp. If we move to support positonal timewarp, we need to - // ensure this contains the full pose composed with the eye offsets. + // Tell the plugin what pose we're using to render. In this case we're just using the + // unmodified head pose because the only plugin that cares (the Oculus plugin) uses it + // for rotational timewarp. If we move to support positonal timewarp, we need to + // ensure this contains the full pose composed with the eye offsets. mat4 headPose = displayPlugin->getHeadPose(); displayPlugin->setEyeRenderPose(eye, headPose); @@ -1343,7 +1343,7 @@ void Application::paintGL() { PerformanceTimer perfTimer("pluginOutput"); auto primaryFbo = framebufferCache->getPrimaryFramebuffer(); GLuint finalTexture = gpu::GLBackend::getTextureID(primaryFbo->getRenderBuffer(0)); - // Ensure the rendering context commands are completed when rendering + // Ensure the rendering context commands are completed when rendering GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); // Ensure the sync object is flushed to the driver thread before releasing the context // CRITICAL for the mac driver apparently. @@ -1394,7 +1394,7 @@ void Application::audioMuteToggled() { } void Application::faceTrackerMuteToggled() { - + QAction* muteAction = Menu::getInstance()->getActionForOption(MenuOption::MuteFaceTracking); Q_CHECK_PTR(muteAction); bool isMuted = getSelectedFaceTracker()->isMuted(); @@ -1427,7 +1427,7 @@ void Application::resizeGL() { if (nullptr == _displayPlugin) { return; } - + auto displayPlugin = getActiveDisplayPlugin(); // Set the desired FBO texture size. If it hasn't changed, this does nothing. // Otherwise, it must rebuild the FBOs @@ -1437,14 +1437,14 @@ void Application::resizeGL() { _renderResolution = renderSize; DependencyManager::get()->setFrameBufferSize(fromGlm(renderSize)); } - + // FIXME the aspect ratio for stereo displays is incorrect based on this. float aspectRatio = displayPlugin->getRecommendedAspectRatio(); _myCamera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), aspectRatio, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); // Possible change in aspect ratio loadViewFrustum(_myCamera, _viewFrustum); - + auto offscreenUi = DependencyManager::get(); auto uiSize = displayPlugin->getRecommendedUiSize(); // Bit of a hack since there's no device pixel ratio change event I can find. @@ -1613,7 +1613,7 @@ void Application::keyPressEvent(QKeyEvent* event) { if (isMeta) { auto offscreenUi = DependencyManager::get(); offscreenUi->load("Browser.qml"); - } + } break; case Qt::Key_X: @@ -2109,7 +2109,7 @@ void Application::wheelEvent(QWheelEvent* event) { if (_controllerScriptingInterface->isWheelCaptured()) { return; } - + if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->wheelEvent(event); } @@ -2227,7 +2227,7 @@ void Application::idle(uint64_t now) { _idleLoopStdev.reset(); } } - + _overlayConductor.update(secondsSinceLastUpdate); // check for any requested background downloads. @@ -2481,7 +2481,7 @@ void Application::init() { DependencyManager::get()->loadSettings(addressLookupString); qCDebug(interfaceapp) << "Loaded settings"; - + Leapmotion::init(); RealSense::init(); @@ -2539,7 +2539,7 @@ void Application::setAvatarUpdateThreading(bool isThreaded) { if (_avatarUpdate && (_avatarUpdate->isThreaded() == isThreaded)) { return; } - + auto myAvatar = getMyAvatar(); bool isRigEnabled = myAvatar->getEnableRigAnimations(); bool isGraphEnabled = myAvatar->getEnableAnimGraph(); @@ -2756,7 +2756,7 @@ void Application::updateDialogs(float deltaTime) { if(audioStatsDialog) { audioStatsDialog->update(); } - + // Update bandwidth dialog, if any BandwidthDialog* bandwidthDialog = dialogsManager->getBandwidthDialog(); if (bandwidthDialog) { @@ -2892,7 +2892,7 @@ void Application::update(float deltaTime) { _entities.getTree()->withWriteLock([&] { _physicsEngine->stepSimulation(); }); - + if (_physicsEngine->hasOutgoingChanges()) { _entities.getTree()->withWriteLock([&] { _entitySimulation.handleOutgoingChanges(_physicsEngine->getOutgoingChanges(), _physicsEngine->getSessionID()); @@ -3026,10 +3026,10 @@ int Application::sendNackPackets() { foreach(const OCTREE_PACKET_SEQUENCE& missingNumber, missingSequenceNumbers) { nackPacketList->writePrimitive(missingNumber); } - + if (nackPacketList->getNumPackets()) { packetsSent += nackPacketList->getNumPackets(); - + // send the packet list nodeList->sendPacketList(std::move(nackPacketList), *node); } @@ -3635,7 +3635,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi float fov = MIRROR_FIELD_OF_VIEW; auto myAvatar = getMyAvatar(); - + // bool eyeRelativeCamera = false; if (billboard) { fov = BILLBOARD_FIELD_OF_VIEW; // degees @@ -3843,7 +3843,7 @@ void Application::nodeKilled(SharedNodePointer node) { Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(false); } } - + void Application::trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer sendingNode, bool wasStatsPacket) { // Attempt to identify the sender from its address. @@ -3868,7 +3868,7 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN int statsMessageLength = 0; const QUuid& nodeUUID = sendingNode->getUUID(); - + // now that we know the node ID, let's add these stats to the stats for that node... _octreeServerSceneStats.withWriteLock([&] { OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID]; @@ -4069,7 +4069,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) { Qt::AutoConnection, Q_ARG(const QString&, urlString)); return true; } - + QUrl url(urlString); QHashIterator i(_acceptedExtensions); QString lowerPath = url.path().toLower(); @@ -4080,7 +4080,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) { return (this->*method)(urlString); } } - + return defaultUpload && askToUploadAsset(urlString); } @@ -4162,10 +4162,10 @@ bool Application::askToUploadAsset(const QString& filename) { QString("You don't have upload rights on that domain.\n\n")); return false; } - + QUrl url { filename }; if (auto upload = DependencyManager::get()->createUpload(url.toLocalFile())) { - + QMessageBox messageBox; messageBox.setWindowTitle("Asset upload"); messageBox.setText("You are about to upload the following file to the asset server:\n" + @@ -4173,19 +4173,19 @@ bool Application::askToUploadAsset(const QString& filename) { messageBox.setInformativeText("Do you want to continue?"); messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); messageBox.setDefaultButton(QMessageBox::Ok); - + // Option to drop model in world for models if (filename.endsWith(FBX_EXTENSION) || filename.endsWith(OBJ_EXTENSION)) { auto checkBox = new QCheckBox(&messageBox); checkBox->setText("Add to scene"); messageBox.setCheckBox(checkBox); } - + if (messageBox.exec() != QMessageBox::Ok) { upload->deleteLater(); return false; } - + // connect to the finished signal so we know when the AssetUpload is done if (messageBox.checkBox() && (messageBox.checkBox()->checkState() == Qt::Checked)) { // Custom behavior for models @@ -4195,12 +4195,12 @@ bool Application::askToUploadAsset(const QString& filename) { &AssetUploadDialogFactory::getInstance(), &AssetUploadDialogFactory::handleUploadFinished); } - + // start the upload now upload->start(); return true; } - + // display a message box with the error QMessageBox::warning(_window, "Failed Upload", QString("Failed to upload %1.\n\n").arg(filename)); return false; @@ -4208,20 +4208,20 @@ bool Application::askToUploadAsset(const QString& filename) { void Application::modelUploadFinished(AssetUpload* upload, const QString& hash) { auto filename = QFileInfo(upload->getFilename()).fileName(); - + if ((upload->getError() == AssetUpload::NoError) && (filename.endsWith(FBX_EXTENSION) || filename.endsWith(OBJ_EXTENSION))) { - + auto entities = DependencyManager::get(); - + EntityItemProperties properties; properties.setType(EntityTypes::Model); properties.setModelURL(QString("%1:%2.%3").arg(URL_SCHEME_ATP).arg(hash).arg(upload->getExtension())); properties.setPosition(_myCamera.getPosition() + _myCamera.getOrientation() * Vectors::FRONT * 2.0f); properties.setName(QUrl(upload->getFilename()).fileName()); - + entities->addEntity(properties); - + upload->deleteLater(); } else { AssetUploadDialogFactory::getInstance().handleUploadFinished(upload, hash); @@ -4510,7 +4510,7 @@ void Application::takeSnapshot() { _snapshotShareDialog = new SnapshotShareDialog(fileName, _glWidget); } _snapshotShareDialog->show(); - + } float Application::getRenderResolutionScale() const { @@ -4755,8 +4755,8 @@ void Application::updateDisplayMode() { return; } - // Some plugins *cough* Oculus *cough* process message events from inside their - // display function, and we don't want to change the display plugin underneath + // Some plugins *cough* Oculus *cough* process message events from inside their + // display function, and we don't want to change the display plugin underneath // the paintGL call, so we need to guard against that if (_inPaint) { qDebug() << "Deferring plugin switch until out of painting"; @@ -4790,14 +4790,14 @@ void Application::updateDisplayMode() { oldDisplayPlugin = _displayPlugin; _displayPlugin = newDisplayPlugin; - + // If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed // Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1 bool newPluginWantsHMDTools = newDisplayPlugin ? (newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false; - bool oldPluginWantedHMDTools = oldDisplayPlugin ? + bool oldPluginWantedHMDTools = oldDisplayPlugin ? (oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false; - + // Only show the hmd tools after the correct plugin has // been activated so that it's UI is setup correctly if (newPluginWantsHMDTools) { @@ -4807,7 +4807,7 @@ void Application::updateDisplayMode() { if (oldDisplayPlugin) { oldDisplayPlugin->deactivate(); _offscreenContext->makeCurrent(); - + // if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) { DependencyManager::get()->hmdTools(false); @@ -4940,7 +4940,7 @@ void Application::setPalmData(Hand* hand, const controller::Pose& pose, float de rawVelocity = glm::vec3(0.0f); } palm.setRawVelocity(rawVelocity); // meters/sec - + // Angular Velocity of Palm glm::quat deltaRotation = rotation * glm::inverse(palm.getRawRotation()); glm::vec3 angularVelocity(0.0f); @@ -5020,7 +5020,7 @@ void Application::emulateMouse(Hand* hand, float click, float shift, HandData::H pos.setY(canvasSize.y / 2.0f + cursorRange * yAngle); } - + //If we are off screen then we should stop processing, and if a trigger or bumper is pressed, //we should unpress them. if (pos.x() == INT_MAX) { diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 665ae9706d..10e285186c 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -244,14 +244,14 @@ void PhysicsEngine::stepSimulation() { float timeStep = btMin(dt, MAX_TIMESTEP); if (_myAvatarController) { - // ADEBUG TODO: move this stuff outside and in front of stepSimulation, because + // TODO: move this stuff outside and in front of stepSimulation, because // the updateShapeIfNecessary() call needs info from MyAvatar and should // be done on the main thread during the pre-simulation stuff if (_myAvatarController->needsRemoval()) { _myAvatarController->setDynamicsWorld(nullptr); // We must remove any existing contacts for the avatar so that any new contacts will have - // valid data. MyAvatar's RigidBody is the ONLY one in the simulation that does not yet + // valid data. MyAvatar's RigidBody is the ONLY one in the simulation that does not yet // have a MotionState so we pass nullptr to removeContacts(). removeContacts(nullptr); } From e26081e9818a7e635249ebcc8db19b3ab38d8c8f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 23 Nov 2015 18:05:18 -0800 Subject: [PATCH 25/29] always update physics properties when they change and move activation check logic to MotionState --- libraries/audio/src/AudioConstants.h | 2 +- libraries/entities/src/EntityItem.cpp | 85 +++++++-------------- libraries/entities/src/EntityItem.h | 2 + libraries/physics/src/EntityMotionState.cpp | 6 +- libraries/physics/src/EntityMotionState.h | 4 +- libraries/physics/src/ObjectMotionState.cpp | 73 ++++++++++++++---- libraries/physics/src/ObjectMotionState.h | 4 +- 7 files changed, 95 insertions(+), 81 deletions(-) diff --git a/libraries/audio/src/AudioConstants.h b/libraries/audio/src/AudioConstants.h index e252a5354d..785060d364 100644 --- a/libraries/audio/src/AudioConstants.h +++ b/libraries/audio/src/AudioConstants.h @@ -35,4 +35,4 @@ namespace AudioConstants { } -#endif // hifi_AudioConstants_h \ No newline at end of file +#endif // hifi_AudioConstants_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 75bc26a560..992b6a1bdd 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -622,7 +622,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); - + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE) { // pack SimulationOwner and terse update properties near each other @@ -799,17 +799,11 @@ void EntityItem::setDensity(float density) { _density = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); } -const float ACTIVATION_RELATIVE_DENSITY_DELTA = 0.01f; // 1 percent - void EntityItem::updateDensity(float density) { float clampedDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); if (_density != clampedDensity) { _density = clampedDensity; - - if (fabsf(_density - clampedDensity) / _density > ACTIVATION_RELATIVE_DENSITY_DELTA) { - // the density has changed enough that we should update the physics simulation - _dirtyFlags |= Simulation::DIRTY_MASS; - } + _dirtyFlags |= Simulation::DIRTY_MASS; } } @@ -822,11 +816,16 @@ void EntityItem::setMass(float mass) { // compute new density const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3 + float newDensity = 1.0f; if (volume < 1.0e-6f) { // avoid divide by zero - _density = glm::min(mass / MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY); + newDensity = glm::min(mass / MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY); } else { - _density = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); + newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); + } + if (_density != newDensity) { + _density = newDensity; + _dirtyFlags |= Simulation::DIRTY_MASS; } } @@ -884,12 +883,12 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) { #ifdef WANT_DEBUG qCDebug(entities) << "EntityItem::simulateKinematicMotion timeElapsed" << timeElapsed; #endif - + const float MIN_TIME_SKIP = 0.0f; const float MAX_TIME_SKIP = 1.0f; // in seconds - + timeElapsed = glm::clamp(timeElapsed, MIN_TIME_SKIP, MAX_TIME_SKIP); - + if (hasActions()) { return; } @@ -1312,24 +1311,16 @@ void EntityItem::updatePosition(const glm::vec3& value) { if (shouldSuppressLocationEdits()) { return; } - auto delta = glm::distance(getPosition(), value); - if (delta > IGNORE_POSITION_DELTA) { - _dirtyFlags |= Simulation::DIRTY_POSITION; + if (getPosition() != value) { setPosition(value); - if (delta > ACTIVATION_POSITION_DELTA) { - _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - } + _dirtyFlags |= Simulation::DIRTY_POSITION; } } void EntityItem::updateDimensions(const glm::vec3& value) { - auto delta = glm::distance(getDimensions(), value); - if (delta > IGNORE_DIMENSIONS_DELTA) { + if (getDimensions() != value) { setDimensions(value); - if (delta > ACTIVATION_DIMENSIONS_DELTA) { - // rebuilding the shape will always activate - _dirtyFlags |= (Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); - } + _dirtyFlags |= (Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); } } @@ -1339,14 +1330,7 @@ void EntityItem::updateRotation(const glm::quat& rotation) { } if (getRotation() != rotation) { setRotation(rotation); - - auto alignmentDot = glm::abs(glm::dot(getRotation(), rotation)); - if (alignmentDot < IGNORE_ALIGNMENT_DOT) { - _dirtyFlags |= Simulation::DIRTY_ROTATION; - } - if (alignmentDot < ACTIVATION_ALIGNMENT_DOT) { - _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - } + _dirtyFlags |= Simulation::DIRTY_ROTATION; } } @@ -1367,11 +1351,8 @@ void EntityItem::updateMass(float mass) { newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); } - float oldDensity = _density; - _density = newDensity; - - if (fabsf(_density - oldDensity) / _density > ACTIVATION_RELATIVE_DENSITY_DELTA) { - // the density has changed enough that we should update the physics simulation + if (_density != newDensity) { + _density = newDensity; _dirtyFlags |= Simulation::DIRTY_MASS; } } @@ -1380,38 +1361,29 @@ void EntityItem::updateVelocity(const glm::vec3& value) { if (shouldSuppressLocationEdits()) { return; } - auto delta = glm::distance(_velocity, value); - if (delta > IGNORE_LINEAR_VELOCITY_DELTA) { - _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; + if (_velocity != value) { const float MIN_LINEAR_SPEED = 0.001f; if (glm::length(value) < MIN_LINEAR_SPEED) { _velocity = ENTITY_ITEM_ZERO_VEC3; } else { _velocity = value; - // only activate when setting non-zero velocity - if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { - _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - } } + _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; } } void EntityItem::updateDamping(float value) { auto clampedDamping = glm::clamp(value, 0.0f, 1.0f); - if (fabsf(_damping - clampedDamping) > IGNORE_DAMPING_DELTA) { + if (_damping != clampedDamping) { _damping = clampedDamping; _dirtyFlags |= Simulation::DIRTY_MATERIAL; } } void EntityItem::updateGravity(const glm::vec3& value) { - auto delta = glm::distance(_gravity, value); - if (delta > IGNORE_GRAVITY_DELTA) { + if (_gravity != value) { _gravity = value; _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; - if (delta > ACTIVATION_GRAVITY_DELTA) { - _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - } } } @@ -1419,25 +1391,20 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) { if (shouldSuppressLocationEdits()) { return; } - auto delta = glm::distance(_angularVelocity, value); - if (delta > IGNORE_ANGULAR_VELOCITY_DELTA) { - _dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY; + if (_angularVelocity != value) { const float MIN_ANGULAR_SPEED = 0.0002f; if (glm::length(value) < MIN_ANGULAR_SPEED) { _angularVelocity = ENTITY_ITEM_ZERO_VEC3; } else { _angularVelocity = value; - // only activate when setting non-zero velocity - if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { - _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - } } + _dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY; } } void EntityItem::updateAngularDamping(float value) { auto clampedDamping = glm::clamp(value, 0.0f, 1.0f); - if (fabsf(_angularDamping - clampedDamping) > IGNORE_DAMPING_DELTA) { + if (_angularDamping != clampedDamping) { _angularDamping = clampedDamping; _dirtyFlags |= Simulation::DIRTY_MATERIAL; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 727a806b62..d80de4d427 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -48,6 +48,7 @@ namespace render { class PendingChanges; } +/* // these thesholds determine what updates will be ignored (client and server) const float IGNORE_POSITION_DELTA = 0.0001f; const float IGNORE_DIMENSIONS_DELTA = 0.0005f; @@ -64,6 +65,7 @@ const float ACTIVATION_ALIGNMENT_DOT = 0.99990f; const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f; const float ACTIVATION_GRAVITY_DELTA = 0.1f; const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; +*/ #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { }; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 181ae7060e..34439e57ce 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -95,7 +95,7 @@ void EntityMotionState::updateServerPhysicsVariables(const QUuid& sessionID) { } // virtual -bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool EntityMotionState::handleEasyChanges(uint32_t& flags, PhysicsEngine* engine) { assert(entityTreeIsLocked()); updateServerPhysicsVariables(engine->getSessionID()); ObjectMotionState::handleEasyChanges(flags, engine); @@ -120,7 +120,7 @@ bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) } if (flags & Simulation::DIRTY_SIMULATOR_OWNERSHIP) { // (DIRTY_SIMULATOR_OWNERSHIP really means "we should bid for ownership with SCRIPT priority") - // we're manipulating this object directly via script, so we artificially + // we're manipulating this object directly via script, so we artificially // manipulate the logic to trigger an immediate bid for ownership setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY); } @@ -133,7 +133,7 @@ bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) // virtual -bool EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { updateServerPhysicsVariables(engine->getSessionID()); return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 188e7096b9..c666f87221 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -29,8 +29,8 @@ public: virtual ~EntityMotionState(); void updateServerPhysicsVariables(const QUuid& sessionID); - virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine); - virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleEasyChanges(uint32_t& flags, PhysicsEngine* engine); + virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine); /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem virtual MotionType computeObjectMotionType() const; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index be0edafff5..c39f47eaf8 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -17,6 +17,14 @@ #include "PhysicsHelpers.h" #include "PhysicsLogging.h" +// these thresholds determine what updates (object-->body) will activate the physical object +const float ACTIVATION_POSITION_DELTA = 0.005f; +const float ACTIVATION_ALIGNMENT_DOT = 0.99990f; +const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f; +const float ACTIVATION_GRAVITY_DELTA = 0.1f; +const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; + + // origin of physics simulation in world-frame glm::vec3 _worldOffset(0.0f); @@ -128,28 +136,65 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { } } -bool ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool ObjectMotionState::handleEasyChanges(uint32_t& flags, PhysicsEngine* engine) { if (flags & Simulation::DIRTY_POSITION) { - btTransform worldTrans; - if (flags & Simulation::DIRTY_ROTATION) { - worldTrans.setRotation(glmToBullet(getObjectRotation())); - } else { - worldTrans = _body->getWorldTransform(); + btTransform worldTrans = _body->getWorldTransform(); + btVector3 newPosition = glmToBullet(getObjectPosition()); + float delta = (newPosition - worldTrans.getOrigin()).length(); + if (delta > ACTIVATION_POSITION_DELTA) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } + worldTrans.setOrigin(newPosition); + + if (flags & Simulation::DIRTY_ROTATION) { + btQuaternion newRotation = glmToBullet(getObjectRotation()); + float alignmentDot = fabsf(worldTrans.getRotation().dot(newRotation)); + if (alignmentDot < ACTIVATION_ALIGNMENT_DOT) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } + worldTrans.setRotation(newRotation); } - worldTrans.setOrigin(glmToBullet(getObjectPosition())); _body->setWorldTransform(worldTrans); } else if (flags & Simulation::DIRTY_ROTATION) { btTransform worldTrans = _body->getWorldTransform(); - worldTrans.setRotation(glmToBullet(getObjectRotation())); + btQuaternion newRotation = glmToBullet(getObjectRotation()); + float alignmentDot = fabsf(worldTrans.getRotation().dot(newRotation)); + if (alignmentDot < ACTIVATION_ALIGNMENT_DOT) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } + worldTrans.setRotation(newRotation); _body->setWorldTransform(worldTrans); } if (flags & Simulation::DIRTY_LINEAR_VELOCITY) { - _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); - _body->setGravity(glmToBullet(getObjectGravity())); + btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newLinearVelocity - _body->getLinearVelocity()).length(); + if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } + } + _body->setLinearVelocity(newLinearVelocity); + + btVector3 newGravity = glmToBullet(getObjectGravity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newGravity - _body->getGravity()).length(); + if (delta > ACTIVATION_GRAVITY_DELTA || + (delta > 0.0f && _body->getGravity().length2() == 0.0f)) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } + } + _body->setGravity(newGravity); } if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) { - _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); + btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity()); + if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) { + float delta = (newAngularVelocity - _body->getAngularVelocity()).length(); + if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { + flags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + } + } + _body->setAngularVelocity(newAngularVelocity); } if (flags & Simulation::DIRTY_MATERIAL) { @@ -163,7 +208,7 @@ bool ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) return true; } -bool ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { if (flags & Simulation::DIRTY_SHAPE) { // make sure the new shape is valid if (!isReadyToComputeShape()) { @@ -195,8 +240,8 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* if (flags & EASY_DIRTY_PHYSICS_FLAGS) { handleEasyChanges(flags, engine); } - // it is possible that there are no HARD flags at this point (if DIRTY_SHAPE was removed) - // so we check again befoe we reinsert: + // it is possible there are no HARD flags at this point (if DIRTY_SHAPE was removed) + // so we check again before we reinsert: if (flags & HARD_DIRTY_PHYSICS_FLAGS) { engine->reinsertObject(this); } diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 992bdd11d7..7d5c727d6d 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -80,8 +80,8 @@ public: ObjectMotionState(btCollisionShape* shape); ~ObjectMotionState(); - virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine); - virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleEasyChanges(uint32_t& flags, PhysicsEngine* engine); + virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine); void updateBodyMaterialProperties(); void updateBodyVelocities(); From 23f10659b0e84e2f254440f46e6eac8a8425a38e Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Tue, 24 Nov 2015 14:00:23 -0800 Subject: [PATCH 26/29] update to new trigger method --- examples/controllers/squeezeHands.js | 6 ++---- examples/example/games/sword.js | 4 ++-- examples/painting/closePaint.js | 7 ++++--- examples/toybox/flashlight/createFlashlight.js | 5 ++--- examples/toybox/flashlight/flashlight.js | 11 +++++++---- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/examples/controllers/squeezeHands.js b/examples/controllers/squeezeHands.js index 29965f77eb..3bf13d8646 100644 --- a/examples/controllers/squeezeHands.js +++ b/examples/controllers/squeezeHands.js @@ -25,8 +25,6 @@ var LAST_FRAME = 15.0; // What is the number of the last frame we want to us var SMOOTH_FACTOR = 0.75; var MAX_FRAMES = 30.0; -var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); -var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); var CONTROLLER_DEAD_SPOT = 0.25; @@ -45,8 +43,8 @@ function normalizeControllerValue(val) { } Script.update.connect(function(deltaTime) { - var leftTrigger = normalizeControllerValue(Controller.getActionValue(LEFT_HAND_CLICK)); - var rightTrigger = normalizeControllerValue(Controller.getActionValue(RIGHT_HAND_CLICK)); + var leftTrigger = normalizeControllerValue(Controller.getValue(Controller.Standard.LT)); + var rightTrigger = normalizeControllerValue(Controller.getValue(Controller.Standard.RT)); // Average last few trigger values together for a bit of smoothing var smoothLeftTrigger = leftTrigger * (1.0 - SMOOTH_FACTOR) + lastLeftTrigger * SMOOTH_FACTOR; diff --git a/examples/example/games/sword.js b/examples/example/games/sword.js index abd94b5319..31a9a82d87 100644 --- a/examples/example/games/sword.js +++ b/examples/example/games/sword.js @@ -361,8 +361,8 @@ function update() { } function updateControllerState() { - rightTriggerValue = Controller.getActionValue(rightHandClick); - leftTriggerValue = Controller.getActionValue(leftHandClick); + rightTriggerValue = Controller.getValue(Controller.Standard.RT); + leftTriggerValue =Controller.getValue(Controller.Standard.LT); if (rightTriggerValue > TRIGGER_THRESHOLD && !swordHeld) { grabSword("right") diff --git a/examples/painting/closePaint.js b/examples/painting/closePaint.js index 563ed1dafb..60b4ac2e25 100644 --- a/examples/painting/closePaint.js +++ b/examples/painting/closePaint.js @@ -159,7 +159,8 @@ function MyController(hand, triggerAction) { } this.updateControllerState = function() { - this.triggerValue = Controller.getActionValue(this.triggerAction); + this.triggerValue = Controller.getValue(this.triggerAction); + if (this.triggerValue > TRIGGER_ON_VALUE && this.prevTriggerValue <= TRIGGER_ON_VALUE) { this.squeeze(); } else if (this.triggerValue < TRIGGER_ON_VALUE && this.prevTriggerValue >= TRIGGER_ON_VALUE) { @@ -256,8 +257,8 @@ function MyController(hand, triggerAction) { } } -var rightController = new MyController(RIGHT_HAND, Controller.findAction("RIGHT_HAND_CLICK")); -var leftController = new MyController(LEFT_HAND, Controller.findAction("LEFT_HAND_CLICK")); +var rightController = new MyController(RIGHT_HAND, Controller.Standard.RT); +var leftController = new MyController(LEFT_HAND, Controller.Standard.LT); Controller.actionEvent.connect(function(action, state) { if (state === 0) { diff --git a/examples/toybox/flashlight/createFlashlight.js b/examples/toybox/flashlight/createFlashlight.js index 490792027a..b049d2632e 100644 --- a/examples/toybox/flashlight/createFlashlight.js +++ b/examples/toybox/flashlight/createFlashlight.js @@ -12,10 +12,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // /*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ -Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js"); +Script.include("../../libraries/utils.js"); - -var scriptURL = Script.resolvePath('flashlight.js?123123'); +var scriptURL = Script.resolvePath('flashlight.js'); var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; diff --git a/examples/toybox/flashlight/flashlight.js b/examples/toybox/flashlight/flashlight.js index 722a5c1bfc..8911c0ecd8 100644 --- a/examples/toybox/flashlight/flashlight.js +++ b/examples/toybox/flashlight/flashlight.js @@ -183,11 +183,14 @@ }, changeLightWithTriggerPressure: function(flashLightHand) { - var handClickString = flashLightHand + "_HAND_CLICK"; - var handClick = Controller.findAction(handClickString); + if (flashLightHand === 'LEFT') { + this.triggerValue = Controller.getValue(Controller.Standard.LT); + } + if (flashLightHand === 'RIGHT') { + this.triggerValue = Controller.getValue(Controller.Standard.RT); - this.triggerValue = Controller.getActionValue(handClick); + } if (this.triggerValue < DISABLE_LIGHT_THRESHOLD && this.lightOn === true) { this.turnLightOff(); @@ -266,4 +269,4 @@ // entity scripts always need to return a newly constructed object of our type return new Flashlight(); -}); +}); \ No newline at end of file From e07e1c5c9204eb15b3d9e4895817bf7b104ec951 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 24 Nov 2015 14:01:01 -0800 Subject: [PATCH 27/29] Fix for avatar eye tracking When computing a full eye to world matrix, the translations need to be the geometry coordinate frame, not scaled into meters. --- libraries/animation/src/JointState.h | 1 + libraries/animation/src/Rig.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/JointState.h b/libraries/animation/src/JointState.h index 5dec7138b6..c85a8a508b 100644 --- a/libraries/animation/src/JointState.h +++ b/libraries/animation/src/JointState.h @@ -108,6 +108,7 @@ public: const glm::quat& getPostRotation() const { return _postRotation; } const glm::quat& getDefaultRotation() const { return _defaultRotation; } glm::vec3 getDefaultTranslation() const { return _defaultTranslation * _unitsScale; } + glm::vec3 getUnscaledDefaultTranslation() const { return _defaultTranslation; } const glm::quat& getInverseDefaultRotation() const { return _inverseDefaultRotation; } const QString& getName() const { return _name; } bool getIsFree() const { return _isFree; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 90f068d1ef..046603d660 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1288,7 +1288,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm // NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual. glm::mat4 inverse = glm::inverse(glm::mat4_cast(modelRotation) * parentState.getTransform() * - glm::translate(state.getDefaultTranslationInConstrainedFrame()) * + glm::translate(state.getUnscaledDefaultTranslation()) * state.getPreTransform() * glm::mat4_cast(state.getPreRotation() * state.getDefaultRotation())); glm::vec3 front = glm::vec3(inverse * glm::vec4(worldHeadOrientation * IDENTITY_FRONT, 0.0f)); glm::vec3 lookAtDelta = lookAtSpot - modelTranslation; From cb0b5b5d02a10ff04b5bdd53fe0bb3bf129ac17c Mon Sep 17 00:00:00 2001 From: AlessandroSigna Date: Tue, 24 Nov 2015 18:30:31 -0800 Subject: [PATCH 28/29] added prompt --- .../entityScripts/recordingEntityScript.js | 6 ++-- examples/entityScripts/recordingMaster.js | 36 ++++++++++++------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/examples/entityScripts/recordingEntityScript.js b/examples/entityScripts/recordingEntityScript.js index 0694ff431e..dedfbf809e 100644 --- a/examples/entityScripts/recordingEntityScript.js +++ b/examples/entityScripts/recordingEntityScript.js @@ -84,7 +84,7 @@ overlay = null; }, - startRecording: function (entityID) { + startRecording: function () { if (!isAvatarRecording) { print("RECORDING STARTED"); Messages.sendMessage(CLIENTS_TO_MASTER_CHANNEL, PARTICIPATING_MESSAGE); //tell to master that I'm participating @@ -94,7 +94,7 @@ } }, - stopRecording: function (entityID) { + stopRecording: function () { if (isAvatarRecording) { print("RECORDING ENDED"); Recording.stopRecording(); @@ -109,7 +109,7 @@ _this.stopRecording(); Messages.unsubscribe(MASTER_TO_CLIENTS_CHANNEL); Messages.messageReceived.disconnect(receivingMessage); - if(overlay !== null){ + if (overlay !== null) { Overlays.deleteOverlay(overlay); overlay = null; } diff --git a/examples/entityScripts/recordingMaster.js b/examples/entityScripts/recordingMaster.js index 51149991c2..0aa3e6f866 100644 --- a/examples/entityScripts/recordingMaster.js +++ b/examples/entityScripts/recordingMaster.js @@ -29,11 +29,14 @@ var STOP_MESSAGE = "recordingEnded"; var PARTICIPATING_MESSAGE = "participatingToRecording"; var TIMEOUT = 20; + var toolBar = null; var recordIcon; var isRecording = false; var performanceJSON = { "avatarClips" : [] }; var responsesExpected = 0; +var readyToPrintInfo = false; +var performanceFileURL = null; var waitingForPerformanceFile = true; var totalWaitingTime = 0; var extension = "txt"; @@ -71,9 +74,9 @@ function mousePressEvent(event) { print("I'm the master. I want to start recording"); Messages.sendMessage(MASTER_TO_CLIENTS_CHANNEL, START_MESSAGE); isRecording = true; + waitingForPerformanceFile = true; } else { print("I want to stop recording"); - waitingForPerformanceFile = true; Script.update.connect(update); Messages.sendMessage(MASTER_TO_CLIENTS_CHANNEL, STOP_MESSAGE); isRecording = false; @@ -108,29 +111,38 @@ function update(deltaTime) { } //clean things after upload performance file to asset waitingForPerformanceFile = false; - responsesExpected = 0; totalWaitingTime = 0; Script.update.disconnect(update); - performanceJSON = { "avatarClips" : [] }; } + } else if (readyToPrintInfo == true){ + Window.prompt("Performance file and clips: ", getUtilityString()); + responsesExpected = 0; + performanceJSON = { "avatarClips" : [] }; + Script.update.disconnect(update); } } +function getUtilityString() { + var resultString = "JSON:\n" + performanceFileURL + "\n" + responsesExpected + " avatar clips:\n"; + var avatarClips = performanceJSON.avatarClips; + avatarClips.forEach(function(param) { + resultString += param + "\n"; + }); + return resultString; +} + function uploadFinished(url){ //need to print somehow the url here this way the master can copy the url - print("PERFORMANCE FILE URL: " + url); - Assets.downloadData(url, function (data) { - printPerformanceJSON(JSON.parse(data)); - }); -} - -function printPerformanceJSON(obj) { print("some info:"); - print("downloaded performance file from asset and examinating its content..."); - var avatarClips = obj.avatarClips; + performanceFileURL = url; + print("PERFORMANCE FILE URL: " + performanceFileURL); + print("number of clips obtained:" + responsesExpected); + var avatarClips = performanceJSON.avatarClips; avatarClips.forEach(function(param) { print("clip url obtained: " + param); }); + readyToPrintInfo = true; + Script.update.connect(update); } function cleanup() { From 386dad7aff4357abfe39cd32bfbb1c9bd52e5b04 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 24 Nov 2015 18:57:35 -0800 Subject: [PATCH 29/29] Fixes hand IK for some avatars Specifically: https://hifi-content.s3.amazonaws.com/ozan/dev/avatars/hifi_team/ryan/_test/ryan.fst https://hifi-content.s3.amazonaws.com/ozan/dev/avatars/hifi_team/brad/brad.fst https://s3.amazonaws.com/hifi-public/tony/blackmery/blackmery.fst These avatars have "Hips" joints that are NOT the root of the skeleton. This would cause the getRootAbsoluteBindPoseByChildName() to return (0,0,0). Causing the IK targets to be lower then they should have. --- libraries/animation/src/AnimSkeleton.cpp | 17 ----------------- libraries/animation/src/AnimSkeleton.h | 1 - libraries/animation/src/Rig.cpp | 10 +++++----- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 7ec8db1490..b87d649e31 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -49,23 +49,6 @@ const AnimPose& AnimSkeleton::getAbsoluteBindPose(int jointIndex) const { return _absoluteBindPoses[jointIndex]; } -AnimPose AnimSkeleton::getRootAbsoluteBindPoseByChildName(const QString& childName) const { - AnimPose pose = AnimPose::identity; - int jointIndex = nameToJointIndex(childName); - if (jointIndex >= 0) { - int numJoints = (int)(_absoluteBindPoses.size()); - if (jointIndex < numJoints) { - int parentIndex = getParentIndex(jointIndex); - while (parentIndex != -1 && parentIndex < numJoints) { - jointIndex = parentIndex; - parentIndex = getParentIndex(jointIndex); - } - pose = _absoluteBindPoses[jointIndex]; - } - } - return pose; -} - const AnimPose& AnimSkeleton::getRelativeBindPose(int jointIndex) const { return _relativeBindPoses[jointIndex]; } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 9dda313528..98e2b5882f 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -31,7 +31,6 @@ public: // absolute pose, not relative to parent const AnimPose& getAbsoluteBindPose(int jointIndex) const; - AnimPose getRootAbsoluteBindPoseByChildName(const QString& childName) const; // relative to parent pose const AnimPose& getRelativeBindPose(int jointIndex) const; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 046603d660..029fd20e44 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1306,10 +1306,10 @@ void Rig::updateFromHandParameters(const HandParameters& params, float dt) { // TODO: figure out how to obtain the yFlip from where it is actually stored glm::quat yFlipHACK = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - AnimPose rootBindPose = _animSkeleton->getRootAbsoluteBindPoseByChildName("LeftHand"); + AnimPose hipsBindPose = _animSkeleton->getAbsoluteBindPose(_animSkeleton->nameToJointIndex("Hips")); if (params.isLeftEnabled) { - _animVars.set("leftHandPosition", rootBindPose.trans + rootBindPose.rot * yFlipHACK * params.leftPosition); - _animVars.set("leftHandRotation", rootBindPose.rot * yFlipHACK * params.leftOrientation); + _animVars.set("leftHandPosition", hipsBindPose.trans + hipsBindPose.rot * yFlipHACK * params.leftPosition); + _animVars.set("leftHandRotation", hipsBindPose.rot * yFlipHACK * params.leftOrientation); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); } else { _animVars.unset("leftHandPosition"); @@ -1317,8 +1317,8 @@ void Rig::updateFromHandParameters(const HandParameters& params, float dt) { _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); } if (params.isRightEnabled) { - _animVars.set("rightHandPosition", rootBindPose.trans + rootBindPose.rot * yFlipHACK * params.rightPosition); - _animVars.set("rightHandRotation", rootBindPose.rot * yFlipHACK * params.rightOrientation); + _animVars.set("rightHandPosition", hipsBindPose.trans + hipsBindPose.rot * yFlipHACK * params.rightPosition); + _animVars.set("rightHandRotation", hipsBindPose.rot * yFlipHACK * params.rightOrientation); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); } else { _animVars.unset("rightHandPosition");