From 9267919d7fbe6e2daa3a3631b7d7f7146ba092ed Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Wed, 29 Aug 2018 16:49:27 -0700 Subject: [PATCH 001/131] Resorting Developer menu organiztion --- interface/src/Menu.cpp | 85 +++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a7d7dcf8b3..62acb08d44 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -285,7 +285,50 @@ Menu::Menu() { // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); + + // Developer > Scripting >>> + MenuWrapper* scriptingOptionsMenu = developerMenu->addMenu("Scripting"); + + // Developer > Scripting > Console... + addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, + DependencyManager::get().data(), + SLOT(toggleConsole()), + QAction::NoRole, + UNSPECIFIED_POSITION); + // Developer > Scripting > API Debugger + action = addActionToQMenuAndActionHash(scriptingOptionsMenu, "API Debugger"); + connect(action, &QAction::triggered, [] { + QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); + defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); + }); + + // Developer > Scripting > Log... + addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, + qApp, SLOT(toggleLogDialog())); + + // Developer > Scripting > Entity Script Server Log + auto essLogAction = addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::EntityScriptServerLog, 0, + qApp, SLOT(toggleEntityScriptServerLogDialog())); + { + auto nodeList = DependencyManager::get(); + QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { + auto nodeList = DependencyManager::get(); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + }); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + } + + // Developer > Scripting > Script Log (HMD friendly)... + addActionToQMenuAndActionHash(scriptingOptionsMenu, "Script Log (HMD friendly)...", Qt::NoButton, + qApp, SLOT(showScriptLogs())); + + // Developer > Scripting > Verbose Logging + addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false, + qApp, SLOT(updateVerboseLogging())); + + // Developer > UI >>> MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI"); action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0, @@ -691,10 +734,11 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraints, 0, false, qApp, SLOT(setShowBulletConstraints(bool))); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraintLimits, 0, false, qApp, SLOT(setShowBulletConstraintLimits(bool))); - // Developer > Display Crash Options - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); // Developer > Crash >>> MenuWrapper* crashMenu = developerMenu->addMenu("Crash"); + + // Developer > Crash > Display Crash Options + addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true); addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication())); addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication())); @@ -729,7 +773,7 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded); connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); }); - // Developer > Stats + // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); // Settings > Enable Speech Control API @@ -744,41 +788,6 @@ Menu::Menu() { connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - // console - addActionToQMenuAndActionHash(developerMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, - DependencyManager::get().data(), - SLOT(toggleConsole()), - QAction::NoRole, - UNSPECIFIED_POSITION); - - // Developer > API Debugger - action = addActionToQMenuAndActionHash(developerMenu, "API Debugger"); - connect(action, &QAction::triggered, [] { - QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); - DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); - }); - - // Developer > Log... - addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, - qApp, SLOT(toggleLogDialog())); - auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0, - qApp, SLOT(toggleEntityScriptServerLogDialog())); - { - auto nodeList = DependencyManager::get(); - QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { - auto nodeList = DependencyManager::get(); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - }); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - } - - addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton, - qApp, SLOT(showScriptLogs())); - - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VerboseLogging, 0, false, - qApp, SLOT(updateVerboseLogging())); - // Developer > Show Overlays addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); From 7bc860cbc9806f87c38cdfd51307df112831e9b3 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 30 Aug 2018 11:42:26 -0700 Subject: [PATCH 002/131] Move Debug defaultscripts.js to Scripting menu --- scripts/defaultScripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b275660c0f..1ac84cd29e 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -39,7 +39,7 @@ var DEFAULT_SCRIPTS_SEPARATE = [ ]; // add a menu item for debugging -var MENU_CATEGORY = "Developer"; +var MENU_CATEGORY = "Developer > Scripting"; var MENU_ITEM = "Debug defaultScripts.js"; var SETTINGS_KEY = '_debugDefaultScriptsIsChecked'; From 1233c2ec0d8ed90709f81d992921e9b107dbf3e1 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 30 Aug 2018 14:46:53 -0700 Subject: [PATCH 003/131] Moving root settings into preferred submenus --- interface/src/Menu.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 62acb08d44..e0e16f449f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -328,15 +328,32 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false, qApp, SLOT(updateVerboseLogging())); + // Developer > Scripting > Enable Speech Control API +#if defined(Q_OS_MAC) || defined(Q_OS_WIN) + auto speechRecognizer = DependencyManager::get(); + QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::ControlWithSpeech, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, + speechRecognizer->getEnabled(), + speechRecognizer.data(), + SLOT(setEnabled(bool)), + UNSPECIFIED_POSITION); + connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); +#endif // Developer > UI >>> MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI"); action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0, qApp->getDesktopTabletBecomesToolbarSetting()); + + // Developer > UI > Show Overlays + addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::Overlays, 0, true); + + // Developer > UI > Desktop Tablet Becomes Toolbar connect(action, &QAction::triggered, [action] { qApp->setDesktopTabletBecomesToolbarSetting(action->isChecked()); }); - + + // Developer > UI > HMD Tablet Becomes Toolbar action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::HMDTabletToToolbar, 0, qApp->getHmdTabletBecomesToolbarSetting()); connect(action, &QAction::triggered, [action] { @@ -776,21 +793,6 @@ Menu::Menu() { // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); - // Settings > Enable Speech Control API -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) - auto speechRecognizer = DependencyManager::get(); - QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::ControlWithSpeech, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, - speechRecognizer->getEnabled(), - speechRecognizer.data(), - SLOT(setEnabled(bool)), - UNSPECIFIED_POSITION); - connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); -#endif - - // Developer > Show Overlays - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); - #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); QAction* backAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Back, 0, addressManager.data(), SLOT(goBack())); From 90e98f1608e1a2bad82ada56738fabcfbc28c2a9 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Fri, 31 Aug 2018 09:55:46 -0700 Subject: [PATCH 004/131] Moving Log to root Developer menu --- interface/src/Menu.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e0e16f449f..915ef9e400 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -303,11 +303,7 @@ Menu::Menu() { defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); }); - - // Developer > Scripting > Log... - addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, - qApp, SLOT(toggleLogDialog())); - + // Developer > Scripting > Entity Script Server Log auto essLogAction = addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::EntityScriptServerLog, 0, qApp, SLOT(toggleEntityScriptServerLogDialog())); @@ -792,6 +788,10 @@ Menu::Menu() { // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + + // Developer > Log + addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, + qApp, SLOT(toggleLogDialog())); #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); From 6a1c76d14d7d5a189f70c7fc66ef313b8bca7054 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 14 Sep 2018 12:19:55 -0700 Subject: [PATCH 005/131] Only sort an estimated number of avatars --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 10 +++++++++- libraries/shared/src/PrioritySortUtil.h | 10 ++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 7368db0c31..e17c108775 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -244,6 +244,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the internal state for correct random number distribution distribution.reset(); + // Base number to sort on number previously sent. + const int numToSendEst = std::max(nodeData->getNumAvatarsSentLastFrame() * 2, 20); + // reset the number of sent avatars nodeData->resetNumAvatarsSentLastFrame(); @@ -399,7 +402,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); - const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); for (const auto& sortedAvatar : sortedAvatarVector) { const Node* otherNode = sortedAvatar.getNode(); auto lastEncodeForOther = sortedAvatar.getTimestamp(); @@ -524,6 +527,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) remainingAvatars--; } + if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) { + qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame() + << " / " << numToSendEst; + } + quint64 startPacketSending = usecTimestampNow(); // close the current packet so that we're always sending something diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 27f6b193ba..3a40fead71 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -66,8 +66,14 @@ namespace PrioritySortUtil { void reserve(size_t num) { _vector.reserve(num); } - const std::vector& getSortedVector() { - std::sort(_vector.begin(), _vector.end(), [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + const std::vector& getSortedVector(int numToSort = 0) { + if (numToSort == 0 || numToSort >= (int)_vector.size()) { + std::sort(_vector.begin(), _vector.end(), + [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + } else { + std::partial_sort(_vector.begin(), _vector.begin() + numToSort, _vector.end(), + [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + } return _vector; } From ca300db410eae5a8fef6ab6143ea415e0a099dae Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 14 Sep 2018 18:24:08 -0700 Subject: [PATCH 006/131] Send only avatar data that will fit in packet (WIP) --- .../src/avatars/AvatarMixerSlave.cpp | 16 +- libraries/avatars/src/AvatarData.cpp | 222 ++++++++++-------- libraries/avatars/src/AvatarData.h | 2 +- 3 files changed, 137 insertions(+), 103 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index e17c108775..20d85a3d4d 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -460,13 +460,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool distanceAdjust = true; glm::vec3 viewerPosition = myPosition; - AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray + AvatarDataPacket::HasFlags hasFlagsOut = 0; // the result of the toByteArray bool dropFaceTracking = false; auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, - &lastSentJointsForOther); + &lastSentJointsForOther, maxAvatarDataBytes); auto endSerialize = chrono::high_resolution_clock::now(); _stats.toByteArrayElapsedTime += (quint64) chrono::duration_cast(endSerialize - startSerialize).count(); @@ -477,16 +477,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) dropFaceTracking = true; // first try dropping the facial data bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther, maxAvatarDataBytes); if (bytes.size() > maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "without facial data resulted in very large buffer of" << bytes.size() << "bytes - reducing to MinimumData"; bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther, maxAvatarDataBytes); - if (bytes.size() > maxAvatarDataBytes) { + if (bytes.size() > maxAvatarDataBytes, maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "MinimumData resulted in very large buffer of" << bytes.size() << "bytes - refusing to send avatar"; @@ -604,7 +604,7 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin QVector emptyLastJointSendData { otherAvatar->getJointCount() }; QByteArray avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, false, false, glm::vec3(0), nullptr); + flagsOut, false, false, glm::vec3(0), nullptr, 0); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); @@ -630,14 +630,14 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr); + flagsOut, true, false, glm::vec3(0), nullptr, 0); if (avatarByteArray.size() > maxAvatarByteArraySize) { qCWarning(avatars) << "Replicated avatar data without facial data still too large for" << otherAvatar->getSessionUUID() << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::MinimumData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr); + flagsOut, true, false, glm::vec3(0), nullptr, 0); } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 834754e228..fdd5317bce 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -227,13 +227,13 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro _lastToByteArray = usecTimestampNow(); return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr, - &_outboundDataRate); + 0, &_outboundDataRate); } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, - glm::vec3 viewerPosition, QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const { + AvatarDataPacket::HasFlags& itemFlags, bool dropFaceTracking, bool distanceAdjust, + glm::vec3 viewerPosition, QVector* sentJointDataOut, int maxDataSize, AvatarDataRate* outboundDataRateOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); bool sendAll = (dataDetail == SendAllData); @@ -270,22 +270,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent auto parentID = getParentID(); - bool hasAvatarGlobalPosition = true; // always include global position - bool hasAvatarOrientation = false; - bool hasAvatarBoundingBox = false; - bool hasAvatarScale = false; - bool hasLookAtPosition = false; - bool hasAudioLoudness = false; - bool hasSensorToWorldMatrix = false; - bool hasAdditionalFlags = false; - - // local position, and parent info only apply to avatars that are parented. The local position - // and the parent info can change independently though, so we track their "changed since" - // separately - bool hasParentInfo = false; - bool hasAvatarLocalPosition = false; - - bool hasFaceTrackerInfo = false; bool hasJointData = false; bool hasJointDefaultPoseFlags = false; bool hasGrabJoints = false; @@ -294,80 +278,121 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::mat4 rightFarGrabMatrix; glm::mat4 mouseFarGrabMatrix; - if (sendPALMinimum) { - hasAudioLoudness = true; - } else { - hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime); - hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime); - hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime); - hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime); - hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime); - hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime); - hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime); - hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); - hasAvatarLocalPosition = hasParent() && (sendAll || - tranlationChangedSince(lastSentTime) || - parentInfoChangedSince(lastSentTime)); + // Leading flags, to indicate how much data is actually included in the packet... + AvatarDataPacket::HasFlags packetStateFlags = 0; - hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && - (sendAll || faceTrackerInfoChangedSince(lastSentTime)); - hasJointData = sendAll || !sendMinimum; - hasJointDefaultPoseFlags = hasJointData; - if (hasJointData) { - bool leftValid; - leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); - if (!leftValid) { - leftFarGrabMatrix = glm::mat4(); + if (itemFlags == 0) { + bool hasAvatarGlobalPosition = true; // always include global position + bool hasAvatarOrientation = false; + bool hasAvatarBoundingBox = false; + bool hasAvatarScale = false; + bool hasLookAtPosition = false; + bool hasAudioLoudness = false; + bool hasSensorToWorldMatrix = false; + bool hasAdditionalFlags = false; + + // local position, and parent info only apply to avatars that are parented. The local position + // and the parent info can change independently though, so we track their "changed since" + // separately + bool hasParentInfo = false; + bool hasAvatarLocalPosition = false; + + bool hasFaceTrackerInfo = false; + + if (sendPALMinimum) { + hasAudioLoudness = true; + } else { + hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime); + hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime); + hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime); + hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime); + hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime); + hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime); + hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime); + hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); + hasAvatarLocalPosition = hasParent() && (sendAll || + tranlationChangedSince(lastSentTime) || + parentInfoChangedSince(lastSentTime)); + + hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && + (sendAll || faceTrackerInfoChangedSince(lastSentTime)); + hasJointData = sendAll || !sendMinimum; + hasJointDefaultPoseFlags = hasJointData; + if (hasJointData) { + bool leftValid; + leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); + if (!leftValid) { + leftFarGrabMatrix = glm::mat4(); + } + bool rightValid; + rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); + if (!rightValid) { + rightFarGrabMatrix = glm::mat4(); + } + bool mouseValid; + mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); + if (!mouseValid) { + mouseFarGrabMatrix = glm::mat4(); + } + hasGrabJoints = (leftValid || rightValid || mouseValid); } - bool rightValid; - rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); - if (!rightValid) { - rightFarGrabMatrix = glm::mat4(); - } - bool mouseValid; - mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); - if (!mouseValid) { - mouseFarGrabMatrix = glm::mat4(); - } - hasGrabJoints = (leftValid || rightValid || mouseValid); } + + packetStateFlags = + (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) + | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) + | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) + | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) + | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) + | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) + | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) + | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) + | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) + | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) + | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) + | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) + | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) + | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); + + } else { + packetStateFlags = itemFlags; } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + - (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + - (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size(), hasGrabJoints) : 0) + - (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); + (packetStateFlags & AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + + (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA ? AvatarDataPacket::maxJointDataSize(_jointData.size(), true) : 0) + + (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); + + if (maxDataSize == 0) { + maxDataSize = (int)byteArraySize; + } QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; - // Leading flags, to indicate how much data is actually included in the packet... - AvatarDataPacket::HasFlags packetStateFlags = - (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) - | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) - | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) - | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) - | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) - | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) - | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) - | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) - | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) - | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) - | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) - | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) - | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) - | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); - - memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); + unsigned char * packetFlagsLocation = destinationBuffer; destinationBuffer += sizeof(packetStateFlags); + if (itemFlags == 0) { + itemFlags = packetStateFlags; + } + + AvatarDataPacket::HasFlags includedFlags = 0; + + const unsigned char * packetEnd = destinationBuffer + maxDataSize; + #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); - if (hasAvatarGlobalPosition) { +// If we want an item and there's sufficient space: +#define IF_AVATAR_SPACE(flag, space) \ + if ((itemFlags & AvatarDataPacket::flag) && (packetEnd - destinationBuffer) >= (space) \ + && (includedFlags |= AvatarDataPacket::flag)) + + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { auto startSection = destinationBuffer; AVATAR_MEMCPY(_globalPosition); @@ -378,7 +403,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarBoundingBox) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_BOUNDING_BOX, sizeof _globalBoundingBoxDimensions + sizeof _globalBoundingBoxOffset) { auto startSection = destinationBuffer; AVATAR_MEMCPY(_globalBoundingBoxDimensions); AVATAR_MEMCPY(_globalBoundingBoxOffset); @@ -389,7 +414,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarOrientation) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_ORIENTATION, 6) { auto startSection = destinationBuffer; auto localOrientation = getOrientationOutbound(); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); @@ -400,7 +425,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarScale) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_SCALE, sizeof(AvatarDataPacket::AvatarScale)) { auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); auto scale = getDomainLimitedScale(); @@ -413,7 +438,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasLookAtPosition) { + IF_AVATAR_SPACE(PACKET_HAS_LOOK_AT_POSITION, sizeof(_headData->getLookAtPosition()) ) { auto startSection = destinationBuffer; AVATAR_MEMCPY(_headData->getLookAtPosition()); int numBytes = destinationBuffer - startSection; @@ -422,7 +447,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAudioLoudness) { + IF_AVATAR_SPACE(PACKET_HAS_AUDIO_LOUDNESS, sizeof(AvatarDataPacket::AudioLoudness)) { auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); data->audioLoudness = packFloatGainToByte(getAudioLoudness() / AUDIO_LOUDNESS_SCALE); @@ -434,7 +459,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasSensorToWorldMatrix) { + IF_AVATAR_SPACE(PACKET_HAS_SENSOR_TO_WORLD_MATRIX, sizeof(AvatarDataPacket::SensorToWorldMatrix)) { auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); @@ -452,7 +477,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAdditionalFlags) { + IF_AVATAR_SPACE(PACKET_HAS_ADDITIONAL_FLAGS, sizeof (uint16_t)) { auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); @@ -500,7 +525,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasParentInfo) { + IF_AVATAR_SPACE(PACKET_HAS_PARENT_INFO, sizeof(AvatarDataPacket::ParentInfo)) { auto startSection = destinationBuffer; auto parentInfo = reinterpret_cast(destinationBuffer); QByteArray referentialAsBytes = parentID.toRfc4122(); @@ -514,7 +539,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarLocalPosition) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_LOCAL_POSITION, sizeof(getLocalPosition()) ) { auto startSection = destinationBuffer; const auto localPosition = getLocalPosition(); AVATAR_MEMCPY(localPosition); @@ -525,11 +550,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // If it is connected, pack up the data - if (hasFaceTrackerInfo) { + IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, blendshapeCoefficients.size() * (int)sizeof(float)) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // note: we don't use the blink and average loudness, we just use the numBlendShapes and // compute the procedural info on the client side. faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; @@ -553,26 +578,26 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent QReadLocker readLock(&_jointDataLock); jointData = _jointData; } + const int numJoints = jointData.size(); + const int jointBitVectorSize = calcBitVectorSize(numJoints); // If it is connected, pack up the data - if (hasJointData) { + if (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA) { auto startSection = destinationBuffer; // joint rotation data - int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; unsigned char validity = 0; int validityBit = 0; - int numValidityBytes = calcBitVectorSize(numJoints); #ifdef WANT_DEBUG int rotationSentCount = 0; unsigned char* beforeRotations = destinationBuffer; #endif - destinationBuffer += numValidityBytes; // Move pointer past the validity bytes + destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes // sentJointDataOut and lastSentJointData might be the same vector if (sentJointDataOut) { @@ -625,7 +650,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent unsigned char* beforeTranslations = destinationBuffer; #endif - destinationBuffer += numValidityBytes; // Move pointer past the validity bytes + destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; @@ -718,6 +743,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (outboundDataRateOut) { outboundDataRateOut->farGrabJointRate.increment(numBytes); } + + includedFlags |= AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; } #ifdef WANT_DEBUG @@ -738,13 +765,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent outboundDataRateOut->jointDataRate.increment(numBytes); } + includedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } - - if (hasJointDefaultPoseFlags) { + + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS, 1 + 2 * jointBitVectorSize) { auto startSection = destinationBuffer; // write numJoints - int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; // write rotationIsDefaultPose bits @@ -763,6 +790,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + memcpy(packetFlagsLocation, &includedFlags, sizeof(includedFlags)); + // Return dropped items. + itemFlags = packetStateFlags & ~includedFlags; + int avatarDataSize = destinationBuffer - startPosition; if (avatarDataSize > (int)byteArraySize) { @@ -771,6 +802,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } return avatarDataByteArray.left(avatarDataSize); + +#undef AVATAR_MEMCPY +#undef IF_AVATAR_SPACE } // NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 056e745370..057b351670 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -450,7 +450,7 @@ public: virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, - QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const; + QVector* sentJointDataOut, int maxDataSize = 0, AvatarDataRate* outboundDataRateOut = nullptr) const; virtual void doneEncoding(bool cullSmallChanges); From 0c7dee730c60452f262ef507aa89a3d6609d6011 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 18 Sep 2018 10:25:59 -0700 Subject: [PATCH 007/131] Pack partial avatar data in bulk packets using space available Keep a single packet in-hand and send when full rather than a packet list. --- .../src/avatars/AvatarMixerSlave.cpp | 60 +++++++++++----- libraries/avatars/src/AvatarData.cpp | 72 ++++++++++--------- 2 files changed, 79 insertions(+), 53 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 20d85a3d4d..c6d8c7f495 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -279,10 +279,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int minimumBytesPerAvatar = PALIsOpen ? AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID + sizeof(AvatarDataPacket::AvatarGlobalPosition) + sizeof(AvatarDataPacket::AudioLoudness) : 0; - // setup a PacketList for the avatarPackets - auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; - // compute node bounding box const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); @@ -401,6 +397,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); + auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); + const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); + int avatarSpaceAvailable = avatarPacketCapacity; + //avatarSpaceAvailable = 100; + int numPacketsSent = 0; const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); for (const auto& sortedAvatar : sortedAvatarVector) { @@ -460,17 +461,40 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool distanceAdjust = true; glm::vec3 viewerPosition = myPosition; - AvatarDataPacket::HasFlags hasFlagsOut = 0; // the result of the toByteArray + AvatarDataPacket::HasFlags includeFlags = 0; // the result of the toByteArray bool dropFaceTracking = false; - auto startSerialize = chrono::high_resolution_clock::now(); - QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, - &lastSentJointsForOther, maxAvatarDataBytes); - auto endSerialize = chrono::high_resolution_clock::now(); - _stats.toByteArrayElapsedTime += - (quint64) chrono::duration_cast(endSerialize - startSerialize).count(); + do { + auto startSerialize = chrono::high_resolution_clock::now(); + QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + includeFlags, dropFaceTracking, distanceAdjust, viewerPosition, + &lastSentJointsForOther, avatarSpaceAvailable); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64)chrono::duration_cast(endSerialize - startSerialize).count(); + avatarPacket->write(bytes); + avatarSpaceAvailable -= bytes.size(); + if (includeFlags != 0) { + // Weren't able to fit everything. + nodeList->sendPacket(std::move(avatarPacket), *destinationNode); + ++numPacketsSent; + avatarPacket = NLPacket::create(PacketType::BulkAvatarData); + avatarSpaceAvailable = avatarPacketCapacity; + } + } while (includeFlags != 0); + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; + + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); + } +#if 0 if (bytes.size() > maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; @@ -517,6 +541,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // TODO? this avatar is not included now, and will probably not be included next frame. // It would be nice if we could tweak its future sort priority to put it at the back of the list. } +#endif auto endAvatarDataPacking = chrono::high_resolution_clock::now(); _stats.avatarDataPackingElapsedTime += @@ -534,15 +559,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) quint64 startPacketSending = usecTimestampNow(); - // close the current packet so that we're always sending something - avatarPacketList->closeCurrentPacket(true); + if (avatarPacket->getPayloadSize() != 0) { + nodeList->sendPacket(std::move(avatarPacket), *destinationNode); + ++numPacketsSent; + } - _stats.numPacketsSent += (int)avatarPacketList->getNumPackets(); + _stats.numPacketsSent += numPacketsSent; _stats.numBytesSent += numAvatarDataBytes; - // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode); - // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index fdd5317bce..6e5dc3fc6a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -65,7 +65,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients } size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) { - const size_t validityBitsSize = (size_t)std::ceil(numJoints / (float)BITS_IN_BYTE); + const size_t validityBitsSize = (size_t) calcBitVectorSize(numJoints); size_t totalSize = sizeof(uint8_t); // numJoints @@ -270,10 +270,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent auto parentID = getParentID(); - bool hasJointData = false; - bool hasJointDefaultPoseFlags = false; - bool hasGrabJoints = false; - glm::mat4 leftFarGrabMatrix; glm::mat4 rightFarGrabMatrix; glm::mat4 mouseFarGrabMatrix; @@ -289,6 +285,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool hasLookAtPosition = false; bool hasAudioLoudness = false; bool hasSensorToWorldMatrix = false; + bool hasJointData = false; + bool hasJointDefaultPoseFlags = false; + bool hasGrabJoints = false; bool hasAdditionalFlags = false; // local position, and parent info only apply to avatars that are parented. The local position @@ -354,13 +353,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); + itemFlags = packetStateFlags; } else { packetStateFlags = itemFlags; } - - const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + - (packetStateFlags & AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + + const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID + + AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA ? AvatarDataPacket::maxJointDataSize(_jointData.size(), true) : 0) + (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); @@ -371,25 +370,27 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; + const unsigned char * packetEnd = destinationBuffer + maxDataSize; + + AvatarDataPacket::HasFlags includedFlags = 0; + + // Packets always have UUID. + memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); + destinationBuffer += NUM_BYTES_RFC4122_UUID; unsigned char * packetFlagsLocation = destinationBuffer; destinationBuffer += sizeof(packetStateFlags); if (itemFlags == 0) { - itemFlags = packetStateFlags; } - AvatarDataPacket::HasFlags includedFlags = 0; - - const unsigned char * packetEnd = destinationBuffer + maxDataSize; - #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); // If we want an item and there's sufficient space: -#define IF_AVATAR_SPACE(flag, space) \ - if ((itemFlags & AvatarDataPacket::flag) && (packetEnd - destinationBuffer) >= (space) \ +#define IF_AVATAR_SPACE(flag, space) \ + if ((itemFlags & AvatarDataPacket::flag) && (int)(packetEnd - destinationBuffer) >= (space) \ && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { @@ -414,7 +415,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - IF_AVATAR_SPACE(PACKET_HAS_AVATAR_ORIENTATION, 6) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_ORIENTATION, sizeof(AvatarDataPacket::SixByteQuat)) { auto startSection = destinationBuffer; auto localOrientation = getOrientationOutbound(); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); @@ -552,7 +553,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // If it is connected, pack up the data - IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, blendshapeCoefficients.size() * (int)sizeof(float)) { + IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + blendshapeCoefficients.size() * sizeof(float)) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); // note: we don't use the blink and average loudness, we just use the numBlendShapes and @@ -574,15 +575,16 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } QVector jointData; - if (hasJointData || hasJointDefaultPoseFlags) { + if (packetStateFlags & (AvatarDataPacket::PACKET_HAS_JOINT_DATA | AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS)) { QReadLocker readLock(&_jointDataLock); jointData = _jointData; } const int numJoints = jointData.size(); const int jointBitVectorSize = calcBitVectorSize(numJoints); - // If it is connected, pack up the data - if (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA) { + // Check against full size or minimum size: count + two bit-vectors + two controllers + const int approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(numJoints, true) : 1 + 2 * jointBitVectorSize + 2 * (6 + 2); + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { auto startSection = destinationBuffer; // joint rotation data @@ -611,8 +613,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& last = lastSentJointData[i]; if (!data.rotationIsDefaultPose) { - // The dot product for larger rotations is a lower number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation + // The dot product for larger rotations is a lower number, + // so if the dot() is less than the value, then the rotation is a larger angle of rotation if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) || (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT) ) { validity |= (1 << validityBit); @@ -705,7 +707,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); - if (hasGrabJoints) { + int numGrabJointBytes = 0; + IF_AVATAR_SPACE(PACKET_HAS_GRAB_JOINTS, sizeof (AvatarDataPacket::FarGrabJoints)) { // the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); @@ -738,13 +741,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; destinationBuffer += sizeof(data->mouseFarGrabRotation); - int numBytes = destinationBuffer - startSection; - - if (outboundDataRateOut) { - outboundDataRateOut->farGrabJointRate.increment(numBytes); - } - - includedFlags |= AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; + int numGrabJointBytes = destinationBuffer - startSection; } #ifdef WANT_DEBUG @@ -760,12 +757,17 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } #endif - int numBytes = destinationBuffer - startSection; - if (outboundDataRateOut) { - outboundDataRateOut->jointDataRate.increment(numBytes); + if (destinationBuffer >= packetEnd) { + // Joint data too large - revert + destinationBuffer = startSection; + includedFlags &= ~(AvatarDataPacket::PACKET_HAS_GRAB_JOINTS | AvatarDataPacket::PACKET_HAS_JOINT_DATA); + } else { + int numBytes = destinationBuffer - startSection; + if (outboundDataRateOut) { + outboundDataRateOut->jointDataRate.increment(numBytes); + outboundDataRateOut->farGrabJointRate.increment(numGrabJointBytes); + } } - - includedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } IF_AVATAR_SPACE(PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS, 1 + 2 * jointBitVectorSize) { From 5d91396e91320b354fc24b5e1be30cd2ade97755 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 18 Sep 2018 17:49:23 -0700 Subject: [PATCH 008/131] Fixes for client & other clean-up Handle grab-joints better; fix packet size calc; remove dead code; other improvements. --- .../src/avatars/AvatarMixerSlave.cpp | 60 ++------------ libraries/avatars/src/AvatarData.cpp | 82 +++++++++++-------- 2 files changed, 52 insertions(+), 90 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index c6d8c7f495..2aa8de6cf4 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -261,7 +261,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // max number of avatarBytes per frame int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); - // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -457,23 +456,22 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool includeThisAvatar = true; QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); - lastSentJointsForOther.resize(otherAvatar->getJointCount()); - - bool distanceAdjust = true; - glm::vec3 viewerPosition = myPosition; + const bool distanceAdjust = true; + const bool dropFaceTracking = false; AvatarDataPacket::HasFlags includeFlags = 0; // the result of the toByteArray - bool dropFaceTracking = false; do { auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - includeFlags, dropFaceTracking, distanceAdjust, viewerPosition, + includeFlags, dropFaceTracking, distanceAdjust, myPosition, &lastSentJointsForOther, avatarSpaceAvailable); auto endSerialize = chrono::high_resolution_clock::now(); _stats.toByteArrayElapsedTime += (quint64)chrono::duration_cast(endSerialize - startSerialize).count(); + avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); + numAvatarDataBytes += bytes.size(); if (includeFlags != 0) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); @@ -494,54 +492,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) otherNodeData->getLastReceivedSequenceNumber()); nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); } -#if 0 - if (bytes.size() > maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; - - dropFaceTracking = true; // first try dropping the facial data - bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther, maxAvatarDataBytes); - - if (bytes.size() > maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "without facial data resulted in very large buffer of" << bytes.size() - << "bytes - reducing to MinimumData"; - bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther, maxAvatarDataBytes); - - if (bytes.size() > maxAvatarDataBytes, maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "MinimumData resulted in very large buffer of" << bytes.size() - << "bytes - refusing to send avatar"; - includeThisAvatar = false; - } - } - } - - if (includeThisAvatar) { - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); - numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); - numAvatarDataBytes += avatarPacketList->write(bytes); - avatarPacketList->endSegment(); - - if (detail != AvatarData::NoData) { - _stats.numOthersIncluded++; - - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); - - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); - nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); - } - } else { - // TODO? this avatar is not included now, and will probably not be included next frame. - // It would be nice if we could tweak its future sort priority to put it at the back of the list. - } -#endif auto endAvatarDataPacking = chrono::high_resolution_clock::now(); _stats.avatarDataPackingElapsedTime += diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6e5dc3fc6a..2b46eb9cdd 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -65,7 +65,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients } size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) { - const size_t validityBitsSize = (size_t) calcBitVectorSize(numJoints); + const size_t validityBitsSize = calcBitVectorSize((int)numJoints); size_t totalSize = sizeof(uint8_t); // numJoints @@ -74,9 +74,9 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) totalSize += validityBitsSize; // Translations mask totalSize += numJoints * sizeof(SixByteTrans); // Translations - size_t NUM_FAUX_JOINT = 2; - size_t num_grab_joints = (hasGrabJoints ? 2 : 0); - totalSize += (NUM_FAUX_JOINT + num_grab_joints) * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints + static const size_t NUM_FAUX_JOINT = 2; + totalSize += NUM_FAUX_JOINT * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints + totalSize += hasGrabJoints ? sizeof(FarGrabJoints) : 0; return totalSize; } @@ -222,12 +222,14 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio // we want to track outbound data in this case... QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { - AvatarDataPacket::HasFlags hasFlagsOut; + AvatarDataPacket::HasFlags hasFlagsOut = 0; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); - return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), + auto avatarByteArray = AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr, 0, &_outboundDataRate); + // Strip UUID + return avatarByteArray.right(avatarByteArray.size() - NUM_BYTES_RFC4122_UUID); } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, @@ -245,7 +247,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { AvatarDataPacket::HasFlags packetStateFlags = 0; - QByteArray avatarDataByteArray(reinterpret_cast(&packetStateFlags), sizeof(packetStateFlags)); + + QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof(packetStateFlags)); + avatarDataByteArray.append((char*) &packetStateFlags, sizeof packetStateFlags); return avatarDataByteArray; } @@ -287,7 +291,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool hasSensorToWorldMatrix = false; bool hasJointData = false; bool hasJointDefaultPoseFlags = false; - bool hasGrabJoints = false; bool hasAdditionalFlags = false; // local position, and parent info only apply to avatars that are parented. The local position @@ -315,26 +318,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); - hasJointData = sendAll || !sendMinimum; + hasJointData = !sendMinimum; hasJointDefaultPoseFlags = hasJointData; - if (hasJointData) { - bool leftValid; - leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); - if (!leftValid) { - leftFarGrabMatrix = glm::mat4(); - } - bool rightValid; - rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); - if (!rightValid) { - rightFarGrabMatrix = glm::mat4(); - } - bool mouseValid; - mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); - if (!mouseValid) { - mouseFarGrabMatrix = glm::mat4(); - } - hasGrabJoints = (leftValid || rightValid || mouseValid); - } } packetStateFlags = @@ -351,17 +336,45 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) - | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); + | (hasJointData ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); itemFlags = packetStateFlags; } else { packetStateFlags = itemFlags; + if (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA) { + // Force all joints upon continuation, as deltas aren't valid. + sendAll = true; + } + if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + packetStateFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; + } + } + + if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + bool leftValid; + leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); + if (!leftValid) { + leftFarGrabMatrix = glm::mat4(); + } + bool rightValid; + rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); + if (!rightValid) { + rightFarGrabMatrix = glm::mat4(); + } + bool mouseValid; + mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); + if (!mouseValid) { + mouseFarGrabMatrix = glm::mat4(); + } + if (!(leftValid || rightValid || mouseValid)) { + packetStateFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; + } } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID + AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + - (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA ? AvatarDataPacket::maxJointDataSize(_jointData.size(), true) : 0) + - (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); + AvatarDataPacket::maxJointDataSize(_jointData.size(), true) + + AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()); if (maxDataSize == 0) { maxDataSize = (int)byteArraySize; @@ -381,16 +394,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent unsigned char * packetFlagsLocation = destinationBuffer; destinationBuffer += sizeof(packetStateFlags); - if (itemFlags == 0) { - } - #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); // If we want an item and there's sufficient space: #define IF_AVATAR_SPACE(flag, space) \ - if ((itemFlags & AvatarDataPacket::flag) && (int)(packetEnd - destinationBuffer) >= (space) \ + if ((packetStateFlags & AvatarDataPacket::flag) && (int)(packetEnd - destinationBuffer) >= (space) \ && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { @@ -583,7 +593,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const int jointBitVectorSize = calcBitVectorSize(numJoints); // Check against full size or minimum size: count + two bit-vectors + two controllers - const int approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(numJoints, true) : 1 + 2 * jointBitVectorSize + 2 * (6 + 2); + const int approxJointSpace = sendAll ? (int)AvatarDataPacket::maxJointDataSize(numJoints, true) : + 1 + 2 * jointBitVectorSize + 2 * (sizeof(AvatarDataPacket::SixByteQuat) + sizeof(AvatarDataPacket::SixByteTrans)); + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { auto startSection = destinationBuffer; From c88a713ef86e4dacf4d46f5a845fef0acc769cad Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 19 Sep 2018 12:05:06 -0700 Subject: [PATCH 009/131] Use Local ID for last time & sequence # Also try to fix size_t warnings on gcc --- assignment-client/src/avatars/AvatarMixer.cpp | 6 +++--- .../src/avatars/AvatarMixerClientData.cpp | 14 +++++++------- .../src/avatars/AvatarMixerClientData.h | 18 +++++++++--------- .../src/avatars/AvatarMixerSlave.cpp | 17 ++++++++--------- libraries/avatars/src/AvatarData.cpp | 8 ++++---- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 561afee296..ebd86b749b 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -541,7 +541,7 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointersetLastBroadcastTime(node->getUUID(), 0); + nodeData->setLastBroadcastTime(node->getLocalID(), 0); nodeData->resetSentTraitData(node->getLocalID()); } ); @@ -637,7 +637,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer // Reset the lastBroadcastTime for the ignored avatar to 0 // so the AvatarMixer knows it'll have to send identity data about the ignored avatar // to the ignorer if the ignorer unignores. - nodeData->setLastBroadcastTime(ignoredUUID, 0); + nodeData->setLastBroadcastTime(ignoredNode->getLocalID(), 0); nodeData->resetSentTraitData(ignoredNode->getLocalID()); } @@ -647,7 +647,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer // to the ignored if the ignorer unignores. AvatarMixerClientData* ignoredNodeData = reinterpret_cast(ignoredNode->getLinkedData()); if (ignoredNodeData) { - ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0); + ignoredNodeData->setLastBroadcastTime(senderNode->getLocalID(), 0); ignoredNodeData->resetSentTraitData(senderNode->getLocalID()); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 6c01e6e02b..2226a4c7a6 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -200,7 +200,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa } } -uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const { +uint64_t AvatarMixerClientData::getLastBroadcastTime(NLPacket::LocalID nodeUUID) const { // return the matching PacketSequenceNumber, or the default if we don't have it auto nodeMatch = _lastBroadcastTimes.find(nodeUUID); if (nodeMatch != _lastBroadcastTimes.end()) { @@ -209,9 +209,9 @@ uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) cons return 0; } -uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { +uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const { // return the matching PacketSequenceNumber, or the default if we don't have it - auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID); + auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeID); if (nodeMatch != _lastBroadcastSequenceNumbers.end()) { return nodeMatch->second; } @@ -232,7 +232,7 @@ void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { } else { killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble); } - setLastBroadcastTime(other->getUUID(), 0); + setLastBroadcastTime(other->getLocalID(), 0); resetSentTraitData(other->getLocalID()); @@ -311,9 +311,9 @@ AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherA } } -void AvatarMixerClientData::cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID) { - removeLastBroadcastSequenceNumber(nodeUUID); - removeLastBroadcastTime(nodeUUID); +void AvatarMixerClientData::cleanupKilledNode(const QUuid&, Node::LocalID nodeLocalID) { + removeLastBroadcastSequenceNumber(nodeLocalID); + removeLastBroadcastTime(nodeLocalID); _lastSentTraitsTimestamps.erase(nodeLocalID); _sentTraitVersions.erase(nodeLocalID); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index d38a90ef1f..ccb0f4001d 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -49,14 +49,14 @@ public: const AvatarData* getConstAvatarData() const { return _avatar.get(); } AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } - uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const; - void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber) - { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; } - Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); } + uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const; + void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber) + { _lastBroadcastSequenceNumbers[nodeID] = sequenceNumber; } + Q_INVOKABLE void removeLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) { _lastBroadcastSequenceNumbers.erase(nodeID); } - uint64_t getLastBroadcastTime(const QUuid& nodeUUID) const; - void setLastBroadcastTime(const QUuid& nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; } - Q_INVOKABLE void removeLastBroadcastTime(const QUuid& nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); } + uint64_t getLastBroadcastTime(NLPacket::LocalID nodeUUID) const; + void setLastBroadcastTime(NLPacket::LocalID nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; } + Q_INVOKABLE void removeLastBroadcastTime(NLPacket::LocalID nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); } Q_INVOKABLE void cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID); @@ -147,8 +147,8 @@ private: AvatarSharedPointer _avatar { new AvatarData() }; uint16_t _lastReceivedSequenceNumber { 0 }; - std::unordered_map _lastBroadcastSequenceNumbers; - std::unordered_map _lastBroadcastTimes; + std::unordered_map _lastBroadcastSequenceNumbers; + std::unordered_map _lastBroadcastTimes; // this is a map of the last time we encoded an "other" avatar for // sending to "this" node diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 2aa8de6cf4..ef10cb730a 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -359,7 +359,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } if (!shouldIgnore) { - AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); + AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID()); AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); // FIXME - This code does appear to be working. But it seems brittle. @@ -435,11 +435,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. if (otherAvatar->hasProcessedFirstIdentity() - && nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { + && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { identityBytesSent += sendIdentityPacket(otherNodeData, node); // remember the last time we sent identity details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); + nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); } // Typically all out-of-view avatars but such avatars' priorities will rise with time: @@ -453,7 +453,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->incrementAvatarInView(); } - bool includeThisAvatar = true; QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); const bool distanceAdjust = true; @@ -484,11 +483,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; - // increment the number of avatars sent to this reciever + // increment the number of avatars sent to this receiver nodeData->incrementNumAvatarsSentLastFrame(); // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), otherNodeData->getLastReceivedSequenceNumber()); nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); } @@ -582,11 +581,11 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); - auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getUUID()); + auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getLocalID()); if (lastBroadcastTime <= agentNodeData->getIdentityChangeTimestamp() || (start - lastBroadcastTime) >= REBROADCAST_IDENTITY_TO_DOWNSTREAM_EVERY_US) { sendReplicatedIdentityPacket(*agentNode, agentNodeData, *node); - nodeData->setLastBroadcastTime(agentNode->getUUID(), start); + nodeData->setLastBroadcastTime(agentNode->getLocalID(), start); } // figure out how large our avatar byte array can be to fit in the packet list @@ -620,7 +619,7 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin nodeData->incrementNumAvatarsSentLastFrame(); // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(agentNode->getUUID(), + nodeData->setLastBroadcastSequenceNumber(agentNode->getLocalID(), agentNodeData->getLastReceivedSequenceNumber()); // increment the number of avatars sent to this reciever diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2b46eb9cdd..5c50805e3b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -400,7 +400,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // If we want an item and there's sufficient space: #define IF_AVATAR_SPACE(flag, space) \ - if ((packetStateFlags & AvatarDataPacket::flag) && (int)(packetEnd - destinationBuffer) >= (space) \ + if ((packetStateFlags & AvatarDataPacket::flag) && (size_t)(packetEnd - destinationBuffer) >= (size_t)(space) \ && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { @@ -563,7 +563,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // If it is connected, pack up the data - IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + blendshapeCoefficients.size() * sizeof(float)) { + IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + (size_t)blendshapeCoefficients.size() * sizeof(float)) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); // note: we don't use the blink and average loudness, we just use the numBlendShapes and @@ -593,7 +593,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const int jointBitVectorSize = calcBitVectorSize(numJoints); // Check against full size or minimum size: count + two bit-vectors + two controllers - const int approxJointSpace = sendAll ? (int)AvatarDataPacket::maxJointDataSize(numJoints, true) : + const size_t approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(numJoints, true) : 1 + 2 * jointBitVectorSize + 2 * (sizeof(AvatarDataPacket::SixByteQuat) + sizeof(AvatarDataPacket::SixByteTrans)); IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { @@ -753,7 +753,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; destinationBuffer += sizeof(data->mouseFarGrabRotation); - int numGrabJointBytes = destinationBuffer - startSection; + numGrabJointBytes = destinationBuffer - startSection; } #ifdef WANT_DEBUG From 04c47943ba61cf73dd34b2f4a266bc384d2ed7ef Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 19 Sep 2018 13:45:08 -0700 Subject: [PATCH 010/131] Handle small packet space correctly --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 2 ++ libraries/avatars/src/AvatarData.h | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index ef10cb730a..ee7dfd85f3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -471,7 +471,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); numAvatarDataBytes += bytes.size(); - if (includeFlags != 0) { + if (includeFlags != 0 || avatarSpaceAvailable < AvatarDataPacket::MIN_BULK_PACKET_SIZE) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); ++numPacketsSent; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 5c50805e3b..20456012a3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -243,10 +243,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool sendPALMinimum = (dataDetail == PALMinimum); lazyInitHeadData(); + ASSERT(maxDataSize == 0 || maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { AvatarDataPacket::HasFlags packetStateFlags = 0; + itemFlags = packetStateFlags; QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof(packetStateFlags)); avatarDataByteArray.append((char*) &packetStateFlags, sizeof packetStateFlags); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 057b351670..df7129b7a5 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -296,6 +296,8 @@ namespace AvatarDataPacket { } PACKED_END; const size_t FAR_GRAB_JOINTS_SIZE = 84; static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); + + const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation From f1b7097edb69ce6f0801538470f020919a481d89 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 19 Sep 2018 15:32:56 -0700 Subject: [PATCH 011/131] Priority experiment --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 2 +- libraries/shared/src/PrioritySortUtil.h | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index ee7dfd85f3..15330ab5c3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -471,7 +471,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); numAvatarDataBytes += bytes.size(); - if (includeFlags != 0 || avatarSpaceAvailable < AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + if (includeFlags != 0 || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); ++numPacketsSent; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 20456012a3..4007856a73 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -243,7 +243,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool sendPALMinimum = (dataDetail == PALMinimum); lazyInitHeadData(); - ASSERT(maxDataSize == 0 || maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); + ASSERT(maxDataSize == 0 || (size_t)maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 3a40fead71..c3d0f352ce 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -23,7 +23,7 @@ const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; namespace PrioritySortUtil { - constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; + constexpr float DEFAULT_ANGULAR_COEF { 2.5f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; constexpr float DEFAULT_AGE_COEF { 0.25f }; @@ -103,6 +103,9 @@ namespace PrioritySortUtil { float radius = glm::max(thing.getRadius(), MIN_RADIUS); // Other item's angle from view centre: float cosineAngle = glm::dot(offset, view.getDirection()) / distance; + if (cosineAngle > 0.0f) { + cosineAngle = std::sqrt(cosineAngle); + } float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND); // the "age" term accumulates at the sum of all weights From 7bba4d4badaea550a67b3ffe4fac9416203a5e0c Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 19 Sep 2018 18:38:20 -0700 Subject: [PATCH 012/131] Change priorities in the correct place --- libraries/avatars/src/AvatarData.cpp | 2 +- libraries/shared/src/PrioritySortUtil.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4007856a73..73525f69a7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2879,7 +2879,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra value.extraInfo = object.property("extraInfo").toVariant().toMap(); } -float AvatarData::_avatarSortCoefficientSize { 8.0f }; +float AvatarData::_avatarSortCoefficientSize { 20.0f }; float AvatarData::_avatarSortCoefficientCenter { 4.0f }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index c3d0f352ce..8020bde09d 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -23,7 +23,7 @@ const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; namespace PrioritySortUtil { - constexpr float DEFAULT_ANGULAR_COEF { 2.5f }; + constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; constexpr float DEFAULT_AGE_COEF { 0.25f }; From 328ed8d9766b2c2e1a418f00df8d0ceaca49a72c Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 20 Sep 2018 09:31:01 -0700 Subject: [PATCH 013/131] Remove unused variable --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 15330ab5c3..c8cc4d0c8a 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -250,9 +250,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the number of sent avatars nodeData->resetNumAvatarsSentLastFrame(); - // keep a counter of the number of considered avatars - int numOtherAvatars = 0; - // keep track of outbound data rate specifically for avatar data int numAvatarDataBytes = 0; int identityBytesSent = 0; @@ -427,8 +424,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto startAvatarDataPacking = chrono::high_resolution_clock::now(); - ++numOtherAvatars; - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); From f2e69d5c818363e58566ceeca5a3aa75be209bfd Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 20 Sep 2018 17:37:39 -0700 Subject: [PATCH 014/131] Make default bubble-box AvatarData property for efficiency Also a use of getClientGlobalPosition(), etc --- .../src/avatars/AvatarMixerClientData.h | 2 +- .../src/avatars/AvatarMixerSlave.cpp | 6 ++--- libraries/avatars/src/AvatarData.cpp | 24 +++++++++++++++++-- libraries/avatars/src/AvatarData.h | 6 +++++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index ccb0f4001d..492d21a807 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -90,7 +90,7 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; - glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); } + glm::vec3 getPosition() const { return _avatar ? _avatar->getClientGlobalPosition() : glm::vec3(0); } bool isRadiusIgnoring(const QUuid& other) const; void addToRadiusIgnoringSet(const QUuid& other); void removeFromRadiusIgnoringSet(const QUuid& other); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index c8cc4d0c8a..f83bb2d3cb 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -244,7 +244,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the internal state for correct random number distribution distribution.reset(); - // Base number to sort on number previously sent. + // Estimate number to sort on number sent last frame. const int numToSendEst = std::max(nodeData->getNumAvatarsSentLastFrame() * 2, 20); // reset the number of sent avatars @@ -342,8 +342,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { // Perform the collision check between the two bounding boxes - const float OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR = 2.4f; // magic number determined empirically - AABox otherNodeBox = computeBubbleBox(avatarClientNodeData->getAvatar(), OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR); + AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox(); if (nodeBox.touches(otherNodeBox)) { nodeData->ignoreOther(destinationNode, avatarNode); shouldIgnore = !getsAnyIgnored; @@ -396,7 +395,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); int avatarSpaceAvailable = avatarPacketCapacity; - //avatarSpaceAvailable = 100; int numPacketsSent = 0; const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 73525f69a7..782483a6c1 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -384,8 +384,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); - unsigned char* startPosition = destinationBuffer; - const unsigned char * packetEnd = destinationBuffer + maxDataSize; + const unsigned char* const startPosition = destinationBuffer; + const unsigned char* const packetEnd = destinationBuffer + maxDataSize; AvatarDataPacket::HasFlags includedFlags = 0; @@ -971,6 +971,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _avatarBoundingBoxChanged = now; } + _defaultBubbleBox = computeBubbleBox(); + sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); int numBytesRead = sourceBuffer - startSection; _avatarBoundingBoxRate.increment(numBytesRead); @@ -2916,3 +2918,21 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value[EntityID] = binaryEntityProperties; } } + +const float AvatarData::DEFAULT_BUBBLE_SCALE = 2.4f; // magic number determined empirically + +AABox AvatarData::computeBubbleBox(float bubbleScale) const { + AABox box = AABox(_globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); + glm::vec3 size = box.getScale(); + size *= bubbleScale; + const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3); + size= glm::max(size, MIN_BUBBLE_SCALE); + box.setScaleStayCentered(size); + return box; +} + +AABox AvatarData::getDefaultBubbleBox() const { + AABox bubbleBox(_defaultBubbleBox); + bubbleBox.translate(_globalPosition); + return bubbleBox; +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index df7129b7a5..3ea7631e0c 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1100,6 +1100,7 @@ public: glm::vec3 getClientGlobalPosition() const { return _globalPosition; } AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } + AABox getDefaultBubbleBox() const; /**jsdoc * @function MyAvatar.getAvatarEntityData @@ -1192,6 +1193,9 @@ public: void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; } int getReplicaIndex() { return _replicaIndex; } + static const float DEFAULT_BUBBLE_SCALE; /* = 2.4 */ + AABox computeBubbleBox(float bubbleScale = DEFAULT_BUBBLE_SCALE) const; + signals: /**jsdoc @@ -1425,6 +1429,8 @@ protected: glm::vec3 _globalBoundingBoxDimensions; glm::vec3 _globalBoundingBoxOffset; + AABox _defaultBubbleBox; + mutable ReadWriteLockable _avatarEntitiesLock; AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording From 82b81c907670a0eb26053648bc551045219c6841 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 24 Sep 2018 11:48:48 -0700 Subject: [PATCH 015/131] Limit no. of joints sent per avatar to prevent lock-up With this the avatar mixer will sent the full bit-vector but only the first 111 actual joints. --- libraries/avatars/src/AvatarData.cpp | 10 +++++++--- libraries/avatars/src/AvatarData.h | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 782483a6c1..31687a7f27 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -592,10 +592,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent jointData = _jointData; } const int numJoints = jointData.size(); + assert(numJoints <= 255); const int jointBitVectorSize = calcBitVectorSize(numJoints); + const int cappedNumJoints = std::min(numJoints, AvatarDataPacket::MAX_NUM_JOINTS); // Check against full size or minimum size: count + two bit-vectors + two controllers - const size_t approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(numJoints, true) : + const size_t approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(cappedNumJoints, false) : 1 + 2 * jointBitVectorSize + 2 * (sizeof(AvatarDataPacket::SixByteQuat) + sizeof(AvatarDataPacket::SixByteTrans)); IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { @@ -605,6 +607,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; + memset(validityPosition, 0, jointBitVectorSize); unsigned char validity = 0; int validityBit = 0; @@ -622,7 +625,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < jointData.size(); i++) { + for (int i = 0; i < cappedNumJoints; i++) { const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; @@ -666,12 +669,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent unsigned char* beforeTranslations = destinationBuffer; #endif + memset(destinationBuffer, 0, jointBitVectorSize); destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; - for (int i = 0; i < jointData.size(); i++) { + for (int i = 0; i < cappedNumJoints; i++) { const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3ea7631e0c..5b5d49feea 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -297,7 +297,8 @@ namespace AvatarDataPacket { const size_t FAR_GRAB_JOINTS_SIZE = 84; static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); - const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; + static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; + static const int MAX_NUM_JOINTS = 111; } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation From 07bdaeede794271a98b56275e9e687b930d0022a Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 24 Sep 2018 18:23:49 -0700 Subject: [PATCH 016/131] Split avatar joint-data across multiple packets if necessary --- .../src/avatars/AvatarMixerSlave.cpp | 16 +-- libraries/avatars/src/AvatarData.cpp | 136 ++++++++---------- libraries/avatars/src/AvatarData.h | 11 +- 3 files changed, 80 insertions(+), 83 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index f83bb2d3cb..3810456f71 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -450,12 +450,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const bool distanceAdjust = true; const bool dropFaceTracking = false; - AvatarDataPacket::HasFlags includeFlags = 0; // the result of the toByteArray + AvatarDataPacket::SendStatus sendStatus; do { auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - includeFlags, dropFaceTracking, distanceAdjust, myPosition, + sendStatus, dropFaceTracking, distanceAdjust, myPosition, &lastSentJointsForOther, avatarSpaceAvailable); auto endSerialize = chrono::high_resolution_clock::now(); _stats.toByteArrayElapsedTime += @@ -464,14 +464,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); numAvatarDataBytes += bytes.size(); - if (includeFlags != 0 || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + if (sendStatus.itemFlags != 0 || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); ++numPacketsSent; avatarPacket = NLPacket::create(PacketType::BulkAvatarData); avatarSpaceAvailable = avatarPacketCapacity; } - } while (includeFlags != 0); + } while (!sendStatus); if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; @@ -565,12 +565,12 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin // so we always send a full update for this avatar quint64 start = usecTimestampNow(); - AvatarDataPacket::HasFlags flagsOut; + AvatarDataPacket::SendStatus sendStatus; QVector emptyLastJointSendData { otherAvatar->getJointCount() }; QByteArray avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, false, false, glm::vec3(0), nullptr, 0); + sendStatus, false, false, glm::vec3(0), nullptr, 0); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); @@ -596,14 +596,14 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr, 0); + sendStatus, true, false, glm::vec3(0), nullptr, 0); if (avatarByteArray.size() > maxAvatarByteArraySize) { qCWarning(avatars) << "Replicated avatar data without facial data still too large for" << otherAvatar->getSessionUUID() << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::MinimumData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr, 0); + sendStatus, true, false, glm::vec3(0), nullptr, 0); } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 31687a7f27..f393ca9ba8 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -225,16 +225,16 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro AvatarDataPacket::HasFlags hasFlagsOut = 0; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); + AvatarDataPacket::SendStatus sendStatus; auto avatarByteArray = AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), - hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr, - 0, &_outboundDataRate); + sendStatus, dropFaceTracking, false, glm::vec3(0), nullptr, 0, &_outboundDataRate); // Strip UUID return avatarByteArray.right(avatarByteArray.size() - NUM_BYTES_RFC4122_UUID); } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& itemFlags, bool dropFaceTracking, bool distanceAdjust, + AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, int maxDataSize, AvatarDataRate* outboundDataRateOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); @@ -248,7 +248,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { AvatarDataPacket::HasFlags packetStateFlags = 0; - itemFlags = packetStateFlags; + sendStatus.itemFlags = packetStateFlags; QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof(packetStateFlags)); avatarDataByteArray.append((char*) &packetStateFlags, sizeof packetStateFlags); @@ -274,7 +274,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // 3 translations * 6 bytes = 6.48kbps // - auto parentID = getParentID(); + QUuid parentID; glm::mat4 leftFarGrabMatrix; glm::mat4 rightFarGrabMatrix; @@ -283,7 +283,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // Leading flags, to indicate how much data is actually included in the packet... AvatarDataPacket::HasFlags packetStateFlags = 0; - if (itemFlags == 0) { + if (sendStatus.itemFlags == 0) { bool hasAvatarGlobalPosition = true; // always include global position bool hasAvatarOrientation = false; bool hasAvatarBoundingBox = false; @@ -340,13 +340,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) | (hasJointData ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); - itemFlags = packetStateFlags; + sendStatus.itemFlags = packetStateFlags; + sendStatus.rotationsSent = 0; + sendStatus.translationsSent = 0; } else { - packetStateFlags = itemFlags; - if (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA) { - // Force all joints upon continuation, as deltas aren't valid. - sendAll = true; - } + packetStateFlags = sendStatus.itemFlags; if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { packetStateFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } @@ -372,6 +370,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent packetStateFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; } } + if (packetStateFlags & (AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS | AvatarDataPacket::PACKET_HAS_PARENT_INFO)) { + parentID = getParentID(); + } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID + AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + @@ -388,6 +389,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const unsigned char* const packetEnd = destinationBuffer + maxDataSize; AvatarDataPacket::HasFlags includedFlags = 0; + AvatarDataPacket::HasFlags extraReturnedFlags = 0; // Packets always have UUID. memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); @@ -594,13 +596,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const int numJoints = jointData.size(); assert(numJoints <= 255); const int jointBitVectorSize = calcBitVectorSize(numJoints); - const int cappedNumJoints = std::min(numJoints, AvatarDataPacket::MAX_NUM_JOINTS); - // Check against full size or minimum size: count + two bit-vectors + two controllers - const size_t approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(cappedNumJoints, false) : - 1 + 2 * jointBitVectorSize + 2 * (sizeof(AvatarDataPacket::SixByteQuat) + sizeof(AvatarDataPacket::SixByteTrans)); - - IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINT_SIZE) { + // Allow for faux joints + translation bit-vector: + static const ptrdiff_t MIN_SIZE_FOR_JOINT = sizeof(AvatarDataPacket::SixByteQuat) + + jointBitVectorSize + AvatarDataPacket::FAUX_JOINT_SIZE; auto startSection = destinationBuffer; // joint rotation data @@ -608,8 +608,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent unsigned char* validityPosition = destinationBuffer; memset(validityPosition, 0, jointBitVectorSize); - unsigned char validity = 0; - int validityBit = 0; #ifdef WANT_DEBUG int rotationSentCount = 0; @@ -625,44 +623,41 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < cappedNumJoints; i++) { + int i = sendStatus.rotationsSent; + for (; i < numJoints; ++i) { const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; - if (!data.rotationIsDefaultPose) { - // The dot product for larger rotations is a lower number, - // so if the dot() is less than the value, then the rotation is a larger angle of rotation - if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) - || (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT) ) { - validity |= (1 << validityBit); + if ((packetEnd - destinationBuffer) >= MIN_SIZE_FOR_JOINT) { + if (!data.rotationIsDefaultPose) { + // The dot product for larger rotations is a lower number, + // so if the dot() is less than the value, then the rotation is a larger angle of rotation + if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) + || (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT)) { + validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE); #ifdef WANT_DEBUG - rotationSentCount++; + rotationSentCount++; #endif - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - (*sentJointDataOut)[i].rotation = data.rotation; + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotation = data.rotation; + } } } + } else { + break; } if (sentJointDataOut) { (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; } - if (++validityBit == BITS_IN_BYTE) { - *validityPosition++ = validity; - validityBit = validity = 0; - } - } - if (validityBit != 0) { - *validityPosition++ = validity; } + sendStatus.rotationsSent = i; // joint translation data validityPosition = destinationBuffer; - validity = 0; - validityBit = 0; #ifdef WANT_DEBUG int translationSentCount = 0; @@ -675,44 +670,41 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; - for (int i = 0; i < cappedNumJoints; i++) { + i = sendStatus.translationsSent; + for (; i < numJoints; ++i) { const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; - if (!data.translationIsDefaultPose) { - if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) - || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { - - validity |= (1 << validityBit); + if (packetEnd - destinationBuffer > MIN_SIZE_FOR_JOINT) { + if (!data.translationIsDefaultPose) { + if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) + || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { + validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE); #ifdef WANT_DEBUG - translationSentCount++; + translationSentCount++; #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - if (sentJointDataOut) { - (*sentJointDataOut)[i].translation = data.translation; + if (sentJointDataOut) { + (*sentJointDataOut)[i].translation = data.translation; + } } } + } else { + break; } if (sentJointDataOut) { (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; } - if (++validityBit == BITS_IN_BYTE) { - *validityPosition++ = validity; - validityBit = validity = 0; - } - } - - if (validityBit != 0) { - *validityPosition++ = validity; } + sendStatus.translationsSent = i; // faux joints Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); @@ -775,16 +767,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } #endif - if (destinationBuffer >= packetEnd) { - // Joint data too large - revert - destinationBuffer = startSection; - includedFlags &= ~(AvatarDataPacket::PACKET_HAS_GRAB_JOINTS | AvatarDataPacket::PACKET_HAS_JOINT_DATA); - } else { - int numBytes = destinationBuffer - startSection; - if (outboundDataRateOut) { - outboundDataRateOut->jointDataRate.increment(numBytes); - outboundDataRateOut->farGrabJointRate.increment(numGrabJointBytes); - } + if (sendStatus.rotationsSent != numJoints || sendStatus.translationsSent != numJoints) { + extraReturnedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; + } + + int numBytes = destinationBuffer - startSection; + if (outboundDataRateOut) { + outboundDataRateOut->jointDataRate.increment(numBytes); + outboundDataRateOut->farGrabJointRate.increment(numGrabJointBytes); } } @@ -812,7 +802,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memcpy(packetFlagsLocation, &includedFlags, sizeof(includedFlags)); // Return dropped items. - itemFlags = packetStateFlags & ~includedFlags; + sendStatus.itemFlags = (packetStateFlags & ~includedFlags) | extraReturnedFlags; int avatarDataSize = destinationBuffer - startPosition; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5b5d49feea..3ed90c059a 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -298,7 +298,14 @@ namespace AvatarDataPacket { static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; - static const int MAX_NUM_JOINTS = 111; + static const size_t FAUX_JOINT_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); + + struct SendStatus { + HasFlags itemFlags { 0 }; + int rotationsSent { 0 }; + int translationsSent { 0 }; + operator bool() { return itemFlags == 0; } + }; } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation @@ -452,7 +459,7 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false); virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, + AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, int maxDataSize = 0, AvatarDataRate* outboundDataRateOut = nullptr) const; virtual void doneEncoding(bool cullSmallChanges); From f95ab1b040aca9562fe222b08321a4b5e05cc56a Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 25 Sep 2018 12:07:59 -0700 Subject: [PATCH 017/131] Clean-up & other small changes --- .../src/avatars/AvatarMixerSlave.cpp | 4 +- libraries/avatars/src/AvatarData.cpp | 51 ++++++++++--------- libraries/avatars/src/AvatarData.h | 2 +- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 3810456f71..6a1241756c 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -244,7 +244,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the internal state for correct random number distribution distribution.reset(); - // Estimate number to sort on number sent last frame. + // Estimate number to sort on number sent last frame (with min. of 20). const int numToSendEst = std::max(nodeData->getNumAvatarsSentLastFrame() * 2, 20); // reset the number of sent avatars @@ -464,7 +464,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); numAvatarDataBytes += bytes.size(); - if (sendStatus.itemFlags != 0 || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); ++numPacketsSent; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f393ca9ba8..4295eb66a7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -245,13 +245,17 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent lazyInitHeadData(); ASSERT(maxDataSize == 0 || (size_t)maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); + // Leading flags, to indicate how much data is actually included in the packet... + AvatarDataPacket::HasFlags wantedFlags = 0; + AvatarDataPacket::HasFlags includedFlags = 0; + AvatarDataPacket::HasFlags extraReturnedFlags = 0; // For partial joint data. + // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { - AvatarDataPacket::HasFlags packetStateFlags = 0; - sendStatus.itemFlags = packetStateFlags; + sendStatus.itemFlags = wantedFlags; - QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof(packetStateFlags)); - avatarDataByteArray.append((char*) &packetStateFlags, sizeof packetStateFlags); + QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof wantedFlags); + avatarDataByteArray.append((char*) &wantedFlags, sizeof wantedFlags); return avatarDataByteArray; } @@ -280,10 +284,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::mat4 rightFarGrabMatrix; glm::mat4 mouseFarGrabMatrix; - // Leading flags, to indicate how much data is actually included in the packet... - AvatarDataPacket::HasFlags packetStateFlags = 0; - if (sendStatus.itemFlags == 0) { + // New avatar ... bool hasAvatarGlobalPosition = true; // always include global position bool hasAvatarOrientation = false; bool hasAvatarBoundingBox = false; @@ -324,7 +326,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent hasJointDefaultPoseFlags = hasJointData; } - packetStateFlags = + wantedFlags = (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) @@ -340,17 +342,18 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) | (hasJointData ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); - sendStatus.itemFlags = packetStateFlags; + sendStatus.itemFlags = wantedFlags; sendStatus.rotationsSent = 0; sendStatus.translationsSent = 0; - } else { - packetStateFlags = sendStatus.itemFlags; - if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { - packetStateFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; + } else { // Continuing avatar ... + wantedFlags = sendStatus.itemFlags; + if (wantedFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + // Must send joints for grab joints - + wantedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } } - if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + if (wantedFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { bool leftValid; leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); if (!leftValid) { @@ -367,10 +370,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent mouseFarGrabMatrix = glm::mat4(); } if (!(leftValid || rightValid || mouseValid)) { - packetStateFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; + wantedFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; } } - if (packetStateFlags & (AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS | AvatarDataPacket::PACKET_HAS_PARENT_INFO)) { + if (wantedFlags & (AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS | AvatarDataPacket::PACKET_HAS_PARENT_INFO)) { parentID = getParentID(); } @@ -388,24 +391,22 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const unsigned char* const startPosition = destinationBuffer; const unsigned char* const packetEnd = destinationBuffer + maxDataSize; - AvatarDataPacket::HasFlags includedFlags = 0; - AvatarDataPacket::HasFlags extraReturnedFlags = 0; - // Packets always have UUID. memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); destinationBuffer += NUM_BYTES_RFC4122_UUID; unsigned char * packetFlagsLocation = destinationBuffer; - destinationBuffer += sizeof(packetStateFlags); + destinationBuffer += sizeof(wantedFlags); #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); // If we want an item and there's sufficient space: -#define IF_AVATAR_SPACE(flag, space) \ - if ((packetStateFlags & AvatarDataPacket::flag) && (size_t)(packetEnd - destinationBuffer) >= (size_t)(space) \ - && (includedFlags |= AvatarDataPacket::flag)) +#define IF_AVATAR_SPACE(flag, space) \ + if ((wantedFlags & AvatarDataPacket::flag) \ + && (size_t)(packetEnd - destinationBuffer) >= (size_t)(space) \ + && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { auto startSection = destinationBuffer; @@ -589,7 +590,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } QVector jointData; - if (packetStateFlags & (AvatarDataPacket::PACKET_HAS_JOINT_DATA | AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS)) { + if (wantedFlags & (AvatarDataPacket::PACKET_HAS_JOINT_DATA | AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS)) { QReadLocker readLock(&_jointDataLock); jointData = _jointData; } @@ -802,7 +803,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memcpy(packetFlagsLocation, &includedFlags, sizeof(includedFlags)); // Return dropped items. - sendStatus.itemFlags = (packetStateFlags & ~includedFlags) | extraReturnedFlags; + sendStatus.itemFlags = (wantedFlags & ~includedFlags) | extraReturnedFlags; int avatarDataSize = destinationBuffer - startPosition; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3ed90c059a..40334f9b86 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -302,7 +302,7 @@ namespace AvatarDataPacket { struct SendStatus { HasFlags itemFlags { 0 }; - int rotationsSent { 0 }; + int rotationsSent { 0 }; // ie: index of next unsent joint int translationsSent { 0 }; operator bool() { return itemFlags == 0; } }; From 13890f1d16bf2c33945c9ce2aef0c668b276ae09 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 26 Sep 2018 09:47:26 -0700 Subject: [PATCH 018/131] Use local ID in place of UUID in a couple more cases --- .../src/avatars/AvatarMixerClientData.cpp | 10 +++++----- assignment-client/src/avatars/AvatarMixerClientData.h | 10 +++++----- assignment-client/src/avatars/AvatarMixerSlave.cpp | 6 +++--- libraries/avatars/src/AvatarData.cpp | 1 - 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 2226a4c7a6..801ef61abf 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -26,20 +26,20 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID _avatar->setID(nodeID); } -uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const { - std::unordered_map::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); +uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar) const { + const auto itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { return itr->second; } return 0; } -void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) { - std::unordered_map::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); +void AvatarMixerClientData::setLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar, uint64_t time) { + auto itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { itr->second = time; } else { - _lastOtherAvatarEncodeTime.emplace(std::pair(otherAvatar, time)); + _lastOtherAvatarEncodeTime.emplace(std::pair(otherAvatar, time)); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 492d21a807..634c202611 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -111,10 +111,10 @@ public: const ConicalViewFrustums& getViewFrustums() const { return _currentViewFrustums; } - uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; - void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); + uint64_t getLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar) const; + void setLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar, uint64_t time); - QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } + QVector& getLastOtherAvatarSentJoints(NLPacket::LocalID otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } void queuePacket(QSharedPointer message, SharedNodePointer node); int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed @@ -152,8 +152,8 @@ private: // this is a map of the last time we encoded an "other" avatar for // sending to "this" node - std::unordered_map _lastOtherAvatarEncodeTime; - std::unordered_map> _lastOtherAvatarSentJoints; + std::unordered_map _lastOtherAvatarEncodeTime; + std::unordered_map> _lastOtherAvatarSentJoints; uint64_t _identityChangeTimestamp; bool _avatarSessionDisplayNameMustChange{ true }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 6a1241756c..5e1e1019e4 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -382,7 +382,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); - auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID()); + auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } @@ -446,7 +446,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->incrementAvatarInView(); } - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); + QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); const bool distanceAdjust = true; const bool dropFaceTracking = false; @@ -482,7 +482,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // set the last sent sequence number for this sender on the receiver nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), otherNodeData->getLastReceivedSequenceNumber()); - nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); } auto endAvatarDataPacking = chrono::high_resolution_clock::now(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2282745905..91e7939d9c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -222,7 +222,6 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio // we want to track outbound data in this case... QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { - AvatarDataPacket::HasFlags hasFlagsOut = 0; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); AvatarDataPacket::SendStatus sendStatus; From 1e79329e834bd8f4e50a4e71f67a40694f6f9c84 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 26 Sep 2018 14:23:52 -0700 Subject: [PATCH 019/131] Access joint data via C pointer instead of operator[] QVector::detach() was showing up in the linux profiles - it's related to the copy-on-write capability. Const QVectors don't use it though. Also a bug fix for the minimum joint space needed. --- libraries/avatars/src/AvatarData.cpp | 39 +++++++++++++++------------- libraries/avatars/src/AvatarData.h | 2 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 91e7939d9c..7fb0f786ba 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -402,9 +402,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(src); // If we want an item and there's sufficient space: -#define IF_AVATAR_SPACE(flag, space) \ - if ((wantedFlags & AvatarDataPacket::flag) \ - && (size_t)(packetEnd - destinationBuffer) >= (size_t)(space) \ +#define IF_AVATAR_SPACE(flag, space) \ + if ((wantedFlags & AvatarDataPacket::flag) \ + && (packetEnd - destinationBuffer) >= (ptrdiff_t)(space) \ && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { @@ -597,10 +597,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent assert(numJoints <= 255); const int jointBitVectorSize = calcBitVectorSize(numJoints); - IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINT_SIZE) { + // Start joints if room for at least the faux joints. + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE) { // Allow for faux joints + translation bit-vector: - static const ptrdiff_t MIN_SIZE_FOR_JOINT = sizeof(AvatarDataPacket::SixByteQuat) - + jointBitVectorSize + AvatarDataPacket::FAUX_JOINT_SIZE; + const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE; auto startSection = destinationBuffer; // joint rotation data @@ -620,15 +621,17 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (sentJointDataOut) { sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it } + const JointData *const joints = jointData.data(); + JointData *const sentJoints = sentJointDataOut ? sentJointDataOut->data() : nullptr; float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; int i = sendStatus.rotationsSent; for (; i < numJoints; ++i) { - const JointData& data = jointData[i]; + const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; - if ((packetEnd - destinationBuffer) >= MIN_SIZE_FOR_JOINT) { + if (packetEnd - destinationBuffer >= minSizeForJoint) { if (!data.rotationIsDefaultPose) { // The dot product for larger rotations is a lower number, // so if the dot() is less than the value, then the rotation is a larger angle of rotation @@ -640,8 +643,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent #endif destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - (*sentJointDataOut)[i].rotation = data.rotation; + if (sentJoints) { + sentJoints[i].rotation = data.rotation; } } } @@ -649,8 +652,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent break; } - if (sentJointDataOut) { - (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + if (sentJoints) { + sentJoints[i].rotationIsDefaultPose = data.rotationIsDefaultPose; } } @@ -672,10 +675,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float maxTranslationDimension = 0.0; i = sendStatus.translationsSent; for (; i < numJoints; ++i) { - const JointData& data = jointData[i]; + const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; - if (packetEnd - destinationBuffer > MIN_SIZE_FOR_JOINT) { + if (packetEnd - destinationBuffer >= minSizeForJoint) { if (!data.translationIsDefaultPose) { if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { @@ -690,8 +693,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - if (sentJointDataOut) { - (*sentJointDataOut)[i].translation = data.translation; + if (sentJoints) { + sentJoints[i].translation = data.translation; } } } @@ -699,8 +702,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent break; } - if (sentJointDataOut) { - (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + if (sentJoints) { + sentJoints[i].translationIsDefaultPose = data.translationIsDefaultPose; } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a4dfaa594e..3ca8bd4775 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -298,7 +298,7 @@ namespace AvatarDataPacket { static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; - static const size_t FAUX_JOINT_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); + static const size_t FAUX_JOINTS_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); struct SendStatus { HasFlags itemFlags { 0 }; From 7a0043c01002e359e291b931eaaa166de87eb846 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 28 Sep 2018 10:26:52 -0700 Subject: [PATCH 020/131] Send AvatarIdentity in NLPacketList; be more selective --- .../src/avatars/AvatarMixerSlave.cpp | 37 +++++++++++-------- .../src/avatars/AvatarMixerSlave.h | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 5e1e1019e4..3834516b80 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -68,13 +68,13 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { _stats.processIncomingPacketsElapsedTime += (end - start); } -int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { - if (destinationNode->getType() == NodeType::Agent && !destinationNode->isUpstream()) { +int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarMixerClientData* nodeData, const Node& destinationNode) { + if (destinationNode.getType() == NodeType::Agent && !destinationNode.isUpstream()) { QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious - auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); - identityPackets->write(individualData); - DependencyManager::get()->sendPacketList(std::move(identityPackets), *destinationNode); + packetList.startSegment(); + packetList.write(individualData); + packetList.endSegment(); _stats.numIdentityPackets++; return individualData.size(); } else { @@ -396,6 +396,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); int avatarSpaceAvailable = avatarPacketCapacity; int numPacketsSent = 0; + auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); for (const auto& sortedAvatar : sortedAvatarVector) { @@ -425,16 +426,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); - // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (otherAvatar->hasProcessedFirstIdentity() - && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { - identityBytesSent += sendIdentityPacket(otherNodeData, node); - - // remember the last time we sent identity details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); - } - // Typically all out-of-view avatars but such avatars' priorities will rise with time: bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; @@ -444,6 +435,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } else if (!overBudget) { detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; nodeData->incrementAvatarInView(); + + // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO + // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. + if (otherAvatar->hasProcessedFirstIdentity() + && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { + identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); + + // remember the last time we sent identity details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); + } } QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); @@ -520,6 +521,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } + // Send any AvatarIdentity packets: + identityPacketList->closeCurrentPacket(); + if (identityBytesSent > 0) { + nodeList->sendPacketList(std::move(identityPacketList), *destinationNode); + } + // record the number of avatars held back this frame nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index bcb70f8743..2ef90af38e 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -101,7 +101,7 @@ public: void harvestStats(AvatarMixerSlaveStats& stats); private: - int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); + int sendIdentityPacket(NLPacketList& packet, const AvatarMixerClientData* nodeData, const Node& destinationNode); int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode); qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, From 18c1371321cea741e2c9b9ec6f49eef0759ca3b7 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 28 Sep 2018 17:53:06 -0700 Subject: [PATCH 021/131] Process multiple avatars in an AvatarIdentity message --- assignment-client/src/avatars/AvatarMixer.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 21 +++--- libraries/avatars/src/AvatarData.h | 2 +- libraries/avatars/src/AvatarHashMap.cpp | 70 ++++++++++--------- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index ebd86b749b..168c95cb77 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -565,7 +565,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes // parse the identity packet and update the change timestamp if appropriate bool identityChanged = false; bool displayNameChanged = false; - avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); + avatar.processAvatarIdentity(QDataStream(message->getMessage()), identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 7fb0f786ba..07751d03e3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1767,11 +1767,9 @@ glm::quat AvatarData::getOrientationOutbound() const { return (getLocalOrientation()); } -void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, +void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged) { - QDataStream packetStream(identityData); - QUuid avatarSessionID; // peek the sequence number, this will tell us if we should be processing this identity packet at all @@ -1786,17 +1784,18 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide << (udt::SequenceNumber::Type) incomingSequenceNumber; } - if (incomingSequenceNumber > _identitySequenceNumber) { - Identity identity; + Identity identity; - packetStream - >> identity.attachmentData - >> identity.displayName - >> identity.sessionDisplayName - >> identity.isReplicated - >> identity.lookAtSnappingEnabled + packetStream + >> identity.attachmentData + >> identity.displayName + >> identity.sessionDisplayName + >> identity.isReplicated + >> identity.lookAtSnappingEnabled ; + if (incomingSequenceNumber > _identitySequenceNumber) { + // set the store identity sequence number to match the incoming identity _identitySequenceNumber = incomingSequenceNumber; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3ca8bd4775..76b699a3d2 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -967,7 +967,7 @@ public: // identityChanged returns true if identity has changed, false otherwise. // identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange. - void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); + void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged); qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index d205a915f8..79ba5d06f9 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -70,7 +70,7 @@ void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArr if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { - avatar->processAvatarIdentity(identityData, identityChanged, displayNameChanged); + avatar->processAvatarIdentity(QDataStream(identityData), identityChanged, displayNameChanged); } } } @@ -266,41 +266,47 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode) { + QDataStream avatarIdentityStream(message->getMessage()); - // peek the avatar UUID from the incoming packet - QUuid identityUUID = QUuid::fromRfc4122(message->peek(NUM_BYTES_RFC4122_UUID)); + while (!avatarIdentityStream.atEnd()) { + // peek the avatar UUID from the incoming packet + avatarIdentityStream.startTransaction(); + QUuid identityUUID; + avatarIdentityStream >> identityUUID; + avatarIdentityStream.rollbackTransaction(); - if (identityUUID.isNull()) { - qCDebug(avatars) << "Refusing to process identity packet for null avatar ID"; - return; - } - - // make sure this isn't for an ignored avatar - auto nodeList = DependencyManager::get(); - static auto EMPTY = QUuid(); - - { - QReadLocker locker(&_hashLock); - _pendingAvatars.remove(identityUUID); - auto me = _avatarHash.find(EMPTY); - if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { - // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an - // identity packet for ourself (such as when we are assigned a sessionDisplayName by the mixer upon joining), - // we make things match here. - identityUUID = EMPTY; + if (identityUUID.isNull()) { + qCDebug(avatars) << "Refusing to process identity packet for null avatar ID"; + return; } - } - - if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { - // mesh URL for a UUID, find avatar in our list - bool isNewAvatar; - auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); - bool identityChanged = false; - bool displayNameChanged = false; - // In this case, the "sendingNode" is the Avatar Mixer. - avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); - _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); + // make sure this isn't for an ignored avatar + auto nodeList = DependencyManager::get(); + static auto EMPTY = QUuid(); + + { + QReadLocker locker(&_hashLock); + _pendingAvatars.remove(identityUUID); + auto me = _avatarHash.find(EMPTY); + if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { + // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an + // identity packet for ourself (such as when we are assigned a sessionDisplayName by the mixer upon joining), + // we make things match here. + identityUUID = EMPTY; + } + } + + if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { + // mesh URL for a UUID, find avatar in our list + bool isNewAvatar; + auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); + bool identityChanged = false; + bool displayNameChanged = false; + // In this case, the "sendingNode" is the Avatar Mixer. + avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged); + _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); + + } } } From 4534b87ea07c934e927b70a6fd23c8d7f3d3dc84 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 28 Sep 2018 18:24:59 -0700 Subject: [PATCH 022/131] Fix error passing rvalue ref as lvalue --- libraries/avatars/src/AvatarHashMap.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 79ba5d06f9..e453f5d298 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -69,8 +69,9 @@ void AvatarReplicas::removeReplicas(const QUuid& parentID) { void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; + QDataStream identityDataStream(identityData); for (auto avatar : replicas) { - avatar->processAvatarIdentity(QDataStream(identityData), identityChanged, displayNameChanged); + avatar->processAvatarIdentity(identityDataStream, identityChanged, displayNameChanged); } } } From 756d1a6fc4fd8fab92e060cce70bc201165a20f2 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Sat, 29 Sep 2018 21:15:10 -0700 Subject: [PATCH 023/131] Fix another bind-to-temporary --- assignment-client/src/avatars/AvatarMixer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 168c95cb77..48f3edb94f 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -565,7 +565,8 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes // parse the identity packet and update the change timestamp if appropriate bool identityChanged = false; bool displayNameChanged = false; - avatar.processAvatarIdentity(QDataStream(message->getMessage()), identityChanged, displayNameChanged); + QDataStream avatarIdentityStream(message->getMessage()); + avatar.processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); From 0ca112f7b91c379e0599fc113b388dfcea9cc170 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 4 Oct 2018 16:40:44 -0700 Subject: [PATCH 024/131] Restore missing abs() to quaternion dot-product See https://github.com/highfidelity/hifi/pull/14138 --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 07751d03e3..57d36e1f0f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -636,7 +636,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // The dot product for larger rotations is a lower number, // so if the dot() is less than the value, then the rotation is a larger angle of rotation if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) - || (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT)) { + || (cullSmallChanges && fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT)) { validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE); #ifdef WANT_DEBUG rotationSentCount++; From 27d9e8bd23a8cf00aa5b591e26f50bbfdfa73a39 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 9 Oct 2018 10:11:30 -0700 Subject: [PATCH 025/131] initial pass refactored re-ordered data-driver entity properties --- scripts/system/html/css/edit-style.css | 495 +-- scripts/system/html/entityProperties.html | 870 ----- scripts/system/html/js/entityProperties.js | 3781 +++++++++++--------- 3 files changed, 2043 insertions(+), 3103 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 6c1931932a..b7aac02c7c 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -448,10 +448,6 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } -.shape-section, .light-section, .model-section, .web-section, .image-section, .hyperlink-section, .text-section, .zone-section, .material-section { - display: table; -} - #properties-list fieldset { position: relative; /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ @@ -560,21 +556,6 @@ hr { padding-top: 2px; } -.text-group[collapsed="true"] ~ .text-group, -.zone-group[collapsed="true"] ~ .zone-group, -.image-group[collapsed="true"] ~ .image-group, -.web-group[collapsed="true"] ~ .web-group, -.hyperlink-group[collapsed="true"] ~ .hyperlink-group, -.spatial-group[collapsed="true"] ~ .spatial-group, -.physical-group[collapsed="true"] ~ .physical-group, -.behavior-group[collapsed="true"] ~ .behavior-group, -.model-group[collapsed="true"] ~ .model-group, -.material-group[collapsed="true"] ~ .material-group, -.light-group[collapsed="true"] ~ .light-group { - display: none !important; -} - - .property { display: table; width: 100%; @@ -633,10 +614,6 @@ hr { margin-top: 0; } -.checkbox-sub-props { - margin-top: 18px; -} - .property .number { float: left; } @@ -800,15 +777,6 @@ div.refresh input[type="button"] { display: none !important; } -#property-color-control1 { - display: table-cell; - float: none; -} - -#property-color-control1 + label { - border-left: 20px transparent solid; -} - .rgb label { float: left; margin-top: 10px; @@ -873,10 +841,10 @@ div.refresh input[type="button"] { font-family: FiraSans-SemiBold; font-size: 12px; } -.tuple .red + label, .tuple .x + label, .tuple .pitch + label { +.tuple .red + label, .tuple .x + label, .tuple .pitch + label, .tuple .width + label { color: #e2334d; } -.tuple .green + label, .tuple .y + label, .tuple .yaw + label { +.tuple .green + label, .tuple .y + label, .tuple .yaw + label, .tuple .height + label { color: #1ac567; } .tuple .blue + label, .tuple .z + label, .tuple .roll + label { @@ -950,14 +918,6 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { outline: none; } -fieldset .checkbox-sub-props { - margin-top: 0; - } - -fieldset .checkbox-sub-props .property:first-child { - margin-top: 0; -} - .column { vertical-align: top; } @@ -1298,51 +1258,48 @@ th#entity-hasScript { color: #afafaf; } - -#properties-list #properties-header { - display: table-row; - height: 28px; - border-top: none; - box-shadow: none; -} - -#properties-header .property { - display: table-cell; - vertical-align: middle; -} -#properties-header .checkbox { - position: relative; - top: -1px; -} - -#properties-header #type-icon { +#base #property-type-icon { font-family: hifi-glyphs; font-size: 31px; color: #00b4ef; margin: -4px 12px -4px -2px; width: auto; display: none; - vertical-align: middle; } -#properties-header #property-type { +#base #property-type { padding: 5px 24px 5px 0; - border-right: 1px solid #808080; - height: 100%; + border-right: 1px solid #808080; width: auto; display: inline-block; - vertical-align: middle; } -#properties-header .checkbox:last-child { +#base #div-locked { + position: absolute; + top: 0px; + right: 140px; +} + +#base #div-visible { + position: absolute; + top: 20px; + right: 20px; +} + +#base .checkbox { + position: relative; + top: -1px; +} + +#base .checkbox:last-child { padding-left: 24px; } -#properties-header .checkbox label { +#base .checkbox label { background-position-y: 1px; } -#properties-header .checkbox label span { +#base .checkbox label span { font-family: HiFi-Glyphs; font-size: 20px; padding-right: 6px; @@ -1351,11 +1308,11 @@ th#entity-hasScript { top: -4px; } -#properties-header input[type=checkbox]:checked + label span { +#base input[type=checkbox]:checked + label span { color: #ffffff; } -#properties-header + hr { +#base + hr { margin-top: 12px; } @@ -1371,30 +1328,21 @@ th#entity-hasScript { background-color: #00b4ef; } -input#property-parent-id { - width: 340px; -} - -input#dimension-rescale-button { +input#property-scale-button-rescale { min-width: 50px; margin-left: 6px; } -input#reset-to-natural-dimensions { +input#property-scale-button-reset { margin-right: 0; } -#animation-fps { - margin-top: 48px; -} - -#userdata-clear, -#materialdata-clear { +#property-userData-button-clear, +#property-materialData-button-clear { margin-bottom: 10px; } - -#static-userdata, -#static-materialData { +#property-userData-static, +#property-materialData-static { display: none; z-index: 99; position: absolute; @@ -1405,13 +1353,19 @@ input#reset-to-natural-dimensions { background-color: #2e2e2e; } -#userdata-saved, -#materialData-saved { +#property-userData-saved, +#property-materialData-saved { margin-top:5px; font-size:16px; display:none; } +#property-serverScripts-status { + position: relative; + top: -3px; + right: -20px; +} + #properties-list #collision-info > fieldset:first-of-type { border-top: none !important; box-shadow: none; @@ -1421,371 +1375,4 @@ input#reset-to-natural-dimensions { #properties-list { display: flex; flex-direction: column; -} - -/* ----- Order of Menu items for Primitive ----- */ -/* Entity Menu classes are specified by selected entity - within entityProperties.js -*/ -#properties-list.ShapeMenu #general, -#properties-list.BoxMenu #general, -#properties-list.SphereMenu #general { - order: 1; -} - -#properties-list.ShapeMenu #collision-info, -#properties-list.BoxMenu #collision-info, -#properties-list.SphereMenu #collision-info { - order: 2; -} - -#properties-list.ShapeMenu #physical, -#properties-list.BoxMenu #physical, -#properties-list.SphereMenu #physical { - order: 3; -} - -#properties-list.ShapeMenu #spatial, -#properties-list.BoxMenu #spatial, -#properties-list.SphereMenu #spatial { - order: 4; -} - -#properties-list.ShapeMenu #behavior, -#properties-list.BoxMenu #behavior, -#properties-list.SphereMenu #behavior { - order: 5; -} - -#properties-list.ShapeMenu #hyperlink, -#properties-list.BoxMenu #hyperlink, -#properties-list.SphereMenu #hyperlink { - order: 6; -} - -#properties-list.ShapeMenu #material, -#properties-list.BoxMenu #material, -#properties-list.SphereMenu #material, -#properties-list.ShapeMenu #light, -#properties-list.BoxMenu #light, -#properties-list.SphereMenu #light, -#properties-list.ShapeMenu #model, -#properties-list.BoxMenu #model, -#properties-list.SphereMenu #model, -#properties-list.ShapeMenu #zone, -#properties-list.BoxMenu #zone, -#properties-list.SphereMenu #zone, -#properties-list.ShapeMenu #text, -#properties-list.BoxMenu #text, -#properties-list.SphereMenu #text, -#properties-list.ShapeMenu #image, -#properties-list.BoxMenu #image, -#properties-list.SphereMenu #image, -#properties-list.ShapeMenu #web, -#properties-list.BoxMenu #web, -#properties-list.SphereMenu #web { - display: none; -} - -/* ----- ParticleEffectMenu ----- */ -#properties-list.ParticleEffectMenu #general { - order: 1; -} -#properties-list.ParticleEffectMenu #collision-info { - order: 2; -} -#properties-list.ParticleEffectMenu #physical { - order: 3; -} -#properties-list.ParticleEffectMenu #spatial { - order: 4; -} -#properties-list.ParticleEffectMenu #behavior { - order: 5; -} - -/* items to hide */ -#properties-list.ParticleEffectMenu #material, -#properties-list.ParticleEffectMenu #base-color-section, -#properties-list.ParticleEffectMenu #hyperlink, -#properties-list.ParticleEffectMenu #light, -#properties-list.ParticleEffectMenu #model, -#properties-list.ParticleEffectMenu #shape-list, -#properties-list.ParticleEffectMenu #text, -#properties-list.ParticleEffectMenu #web, -#properties-list.ParticleEffectMenu #image, -#properties-list.ParticleEffectMenu #zone { - display: none; -} - -/* ----- Order of Menu items for Light ----- */ -#properties-list.LightMenu #general { - order: 1; -} -#properties-list.LightMenu #light { - order: 2; -} -#properties-list.LightMenu #physical { - order: 3; -} -#properties-list.LightMenu #spatial { - order: 4; -} -#properties-list.LightMenu #behavior { - order: 5; -} -#properties-list.LightMenu #collision-info { - order: 6; -} -#properties-list.LightMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.LightMenu #material, -#properties-list.LightMenu #model, -#properties-list.LightMenu #zone, -#properties-list.LightMenu #text, -#properties-list.LightMenu #image, -#properties-list.LightMenu #web { - display: none; -} -/* items to hide */ -#properties-list.LightMenu #shape-list, -#properties-list.LightMenu #base-color-section { - display: none -} - - -/* ----- Order of Menu items for Model ----- */ -#properties-list.ModelMenu #general { - order: 1; -} -#properties-list.ModelMenu #model { - order: 2; -} -#properties-list.ModelMenu #collision-info { - order: 3; -} -#properties-list.ModelMenu #physical { - order: 4; -} -#properties-list.ModelMenu #spatial { - order: 5; -} -#properties-list.ModelMenu #behavior { - order: 6; -} -#properties-list.ModelMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ModelMenu #material, -#properties-list.ModelMenu #light, -#properties-list.ModelMenu #zone, -#properties-list.ModelMenu #text, -#properties-list.ModelMenu #image, -#properties-list.ModelMenu #web { - display: none; -} -/* items to hide */ -#properties-list.ModelMenu #shape-list, -#properties-list.ModelMenu #base-color-section { - display: none -} - - -/* ----- Order of Menu items for Zone ----- */ -#properties-list.ZoneMenu #general { - order: 1; -} -#properties-list.ZoneMenu #zone { - order: 2; -} -#properties-list.ZoneMenu #physical { - order: 3; -} -#properties-list.ZoneMenu #spatial { - order: 4; -} -#properties-list.ZoneMenu #behavior { - order: 5; -} -#properties-list.ZoneMenu #collision-info { - order: 6; -} -#properties-list.ZoneMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ZoneMenu #material, -#properties-list.ZoneMenu #light, -#properties-list.ZoneMenu #model, -#properties-list.ZoneMenu #text, -#properties-list.ZoneMenu #image, -#properties-list.ZoneMenu #web { - display: none; -} -/* items to hide */ -#properties-list.ZoneMenu #shape-list, -#properties-list.ZoneMenu #base-color-section { - display: none -} - - -/* ----- Order of Menu items for Image ----- */ -#properties-list.ImageMenu #general { - order: 1; -} -#properties-list.ImageMenu #image { - order: 2; -} -#properties-list.ImageMenu #collision-info { - order: 3; -} -#properties-list.ImageMenu #physical { - order: 4; -} -#properties-list.ImageMenu #spatial { - order: 5; -} -#properties-list.ImageMenu #behavior { - order: 6; -} -#properties-list.ImageMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ImageMenu #material, -#properties-list.ImageMenu #light, -#properties-list.ImageMenu #model, -#properties-list.ImageMenu #zone, -#properties-list.ImageMenu #web, -#properties-list.ImageMenu #text { - display: none; -} -/* items to hide */ -#properties-list.ImageMenu #shape-list, -#properties-list.ImageMenu #base-color-section { - display: none; -} - - -/* ----- Order of Menu items for Web ----- */ -#properties-list.WebMenu #general { - order: 1; -} -#properties-list.WebMenu #web { - order: 2; -} -#properties-list.WebMenu #collision-info { - order: 3; -} -#properties-list.WebMenu #physical { - order: 4; -} -#properties-list.WebMenu #spatial { - order: 5; -} -#properties-list.WebMenu #behavior { - order: 6; -} -#properties-list.WebMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.WebMenu #material, -#properties-list.WebMenu #light, -#properties-list.WebMenu #model, -#properties-list.WebMenu #zone, -#properties-list.WebMenu #image, -#properties-list.WebMenu #text { - display: none; -} -/* items to hide */ -#properties-list.WebMenu #shape-list, -#properties-list.WebMenu #base-color-section { - display: none; -} - - - -/* ----- Order of Menu items for Text ----- */ -#properties-list.TextMenu #general { - order: 1; -} -#properties-list.TextMenu #text { - order: 2; -} -#properties-list.TextMenu #collision-info { - order: 3; -} -#properties-list.TextMenu #physical { - order: 4; -} -#properties-list.TextMenu #spatial { - order: 5; -} -#properties-list.TextMenu #behavior { - order: 6; -} -#properties-list.TextMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.TextMenu #material, -#properties-list.TextMenu #light, -#properties-list.TextMenu #model, -#properties-list.TextMenu #zone, -#properties-list.TextMenu #image, -#properties-list.TextMenu #web { - display: none; -} -/* items to hide */ -#properties-list.TextMenu #shape-list, -#properties-list.TextMenu #base-color-section { - display: none -} - -/* ----- Order of Menu items for Material ----- */ -#properties-list.MaterialMenu #general { - order: 1; -} -#properties-list.MaterialMenu #material { - order: 2; -} -#properties-list.MaterialMenu #spatial { - order: 3; -} -#properties-list.MaterialMenu #hyperlink { - order: 4; -} -#properties-list.MaterialMenu #behavior { - order: 5; -} - -/* sections to hide */ -#properties-list.MaterialMenu #physical, -#properties-list.MaterialMenu #collision-info, -#properties-list.MaterialMenu #model, -#properties-list.MaterialMenu #light, -#properties-list.MaterialMenu #zone, -#properties-list.MaterialMenu #text, -#properties-list.MaterialMenu #web, -#properties-list.MaterialMenu #image { - display: none; -} -/* items to hide */ -#properties-list.MaterialMenu #shape-list, -#properties-list.MaterialMenu #base-color-section { - display: none -} - - -/* Currently always hidden */ -#properties-list #polyvox { - display: none; -} - -.skybox-section { - display: none; } \ No newline at end of file diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 744150253d..93f80e19b3 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -26,876 +26,6 @@
- -
-
- -
-
- - -
-
- - -
-
- -
- -
- - -
-
-
- -
-
-
-
-
-
-
- -
- CollisionM -
-
- - -
-
- - -
-
-
-
-
- - Collides With - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- - Grabbing - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
- - -
- - Physical M - -
-
- Linear velocity m/s -
-
-
-
-
-
-
- - -
-
-
-
- Angular velocity deg/s -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
- Gravity m/s2 -
-
-
-
-
-
-
- Acceleration m/s2 -
-
-
-
-
-
-
-
- - -
- - SpatialM - -
- Position m -
-
-
-
-
-
-
- Rotation deg -
-
-
-
-
-
-
- Dimensions m -
-
-
-
-
-
-
- Registration (pivot offset as ratio of dimension) -
-
-
-
-
-
-
- Scale % -
- - - -
-
-
-
- - -
-
- - -
-
-
-
- -
-
-
- - -
-
-
-
- -
- - BehaviorM - -
- -
-
- - - - Saved! -
-
-
- -
-
- - -
-
- - -
- -
-
- - -
-
- - -
-
-
-
- - - - -
-
-
-
- - - -
-
- - -
-
- -
-
-
- - -
-
- - - - -
- - LightM - -
-
- Light color -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- -
- - ModelM - -
-
- - -
- -
- - -
-
-
-
- - -
- -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
-
-
- - -
-
- - -
-
-
- -
- - ZoneM - -
-
- - -
-
- - -
-
-
-
- - -
-
- -
-
- Inherit - Off - On -
-
-
- -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- - -
-
- - Skybox - -
- Inherit - Off - On -
-
-
- Skybox color -
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
- -
-
-
- -
-
- Inherit - Off - On -
-
- - -
-
- - -
-
-
- - Haze - -
- Inherit - Off - On -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
-
-
-
- Haze Color -
-
-
-
-
-
-
-
-
-
-
-
-
- - - - -
-
-
-
-
- - -
-
-
- Glare Color -
-
-
-
-
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
- -
-
- - Bloom - -
- Inherit - Off - On -
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
- - TextM - -
- - -
-
- - -
-
- - -
-
-
- -
-
-
-
-
-
-
-
- Background color -
-
-
-
-
-
-
- -
- - ImageM - -
- - -
-
- -
- - WebM - -
- - -
-
- - -
-
- -
- - Voxel volume size m - -
-
-
-
-
- -
- - -
-
- - -
-
- - -
-
- -
- - MaterialM - -
-
- - -
- -
- -

-
- - - - Saved! -
-
-
- -
-
-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
-
-
-
- -
- -
-
-
-
- -
- - -
-
- -
-
-
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index de9027586e..03dad80be6 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1,4 +1,5 @@ // entityProperties.js +// entityProperties.js // // Created by Ryan Huffman on 13 Nov 2014 // Copyright 2014 High Fidelity, Inc. @@ -8,11 +9,8 @@ /* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, HifiEntityUI, JSONEditor, openEventBridge, setTimeout, window, _ $ */ - -var PI = 3.14159265358979; -var DEGREES_TO_RADIANS = PI / 180.0; -var RADIANS_TO_DEGREES = 180.0 / PI; -var ICON_FOR_TYPE = { + +const ICON_FOR_TYPE = { Box: "V", Sphere: "n", Shape: "n", @@ -27,17 +25,901 @@ var ICON_FOR_TYPE = { Multiple: "", PolyLine: "", Material: "" +}; + +const PI = 3.14159265358979; +const DEGREES_TO_RADIANS = PI / 180.0; +const RADIANS_TO_DEGREES = 180.0 / PI; + +const NO_SELECTION = "No selection"; + +const GROUPS = [ + { + id: "base", + properties: [ + { + label: NO_SELECTION, + type: "icon", + icons: ICON_FOR_TYPE, + propertyName: "type", + }, + { + label: "Name", + type: "string", + propertyName: "name", + }, + { + label: "ID", + type: "string", + propertyName: "id", + readOnly: true, + }, + { + label: "Parent", + type: "string", + propertyName: "parentID", + }, + { + label: "Locked", + glyph: "", + type: "bool", + propertyName: "locked", + }, + { + label: "Visible", + glyph: "", + type: "bool", + propertyName: "visible", + }, + ] + }, + { + id: "shape", + addToGroup: "base", + properties: [ + { + label: "Shape", + type: "dropdown", + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, + propertyName: "shape", + }, + { + label: "Color", + type: "color", + propertyName: "color", + }, + /* + { + label: "Material", + type: "string", + propertyName: "", + }, + */ + ] + }, + { + id: "text", + addToGroup: "base", + properties: [ + { + label: "Text", + type: "string", + propertyName: "text", + }, + { + label: "Text Color", + type: "color", + propertyName: "textColor", + }, + { + label: "Background Color", + type: "color", + propertyName: "backgroundColor", + }, + /* + { + label: "Transparent Background", + type: "bool", + propertyName: "" + }, + */ + { + label: "Line Height", + type: "number", + min: 0, + step: 0.005, + fixedDecimals: 4, + unit: "m", + propertyName: "lineHeight" + }, + { + label: "Face Camera", + type: "bool", + propertyName: "faceCamera" + }, + ] + }, + { + id: "zone", + addToGroup: "base", + properties: [ + { + label: "Flying Allowed", + type: "bool", + propertyName: "flyingAllowed", + }, + { + label: "Ghosting Allowed", + type: "bool", + propertyName: "ghostingAllowed", + }, + { + label: "Filter", + type: "string", + propertyName: "filterURL", + }, + { + label: "Key Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "keyLightMode", + + }, + { + label: "Key Light Color", + type: "color", + propertyName: "keyLight.color", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Intensity", + type: "number", + min: 0, + max: 10, + step: 0.1, + fixedDecimals: 2, + propertyName: "keyLight.intensity", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Altitude", + type: "number", + fixedDecimals: 2, + unit: "deg", + propertyName: "keyLight.direction.y", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Azimuth", + type: "number", + fixedDecimals: 2, + unit: "deg", + propertyName: "keyLight.direction.x", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Cast Shadows", + type: "bool", + propertyName: "keyLight.castShadows", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Skybox", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "skyboxMode", + }, + { + label: "Skybox Color", + type: "color", + propertyName: "skybox.color", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Skybox URL", + type: "string", + propertyName: "skybox.url", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + type: "buttons", + buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], + propertyName: "copyURLToAmbient", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Ambient Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "ambientLightMode", + }, + { + label: "Ambient Intensity", + type: "number", + min: 0, + max: 10, + step: 0.1, + fixedDecimals: 2, + propertyName: "ambientLight.ambientIntensity", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Ambient URL", + type: "string", + propertyName: "ambientLight.ambientURL", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Haze", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "hazeMode", + }, + { + label: "Range", + type: "number", + min: 5, + max: 10000, + step: 5, + fixedDecimals: 0, + unit: "m", + propertyName: "haze.hazeRange", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Use Altitude", + type: "bool", + propertyName: "haze.hazeAltitudeEffect", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Base", + type: "number", + min: -1000, + max: 1000, + step: 10, + fixedDecimals: 0, + unit: "m", + propertyName: "haze.hazeBaseRef", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Ceiling", + type: "number", + min: -1000, + max: 5000, + step: 10, + fixedDecimals: 0, + unit: "m", + propertyName: "haze.hazeCeiling", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Haze Color", + type: "color", + propertyName: "haze.hazeColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Background Blend", + type: "number", + min: 0.0, + max: 1.0, + step: 0.01, + fixedDecimals: 2, + propertyName: "haze.hazeBackgroundBlend", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Enable Glare", + type: "bool", + propertyName: "haze.hazeEnableGlare", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Color", + type: "color", + propertyName: "haze.hazeGlareColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Angle", + type: "number", + min: 0, + max: 180, + step: 1, + fixedDecimals: 0, + propertyName: "haze.hazeGlareAngle", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Bloom", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "bloomMode", + }, + { + label: "Bloom Intensity", + type: "number", + min: 0, + max: 1, + step: 0.01, + fixedDecimals: 2, + propertyName: "bloom.bloomIntensity", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Threshold", + type: "number", + min: 0, + min: 1, + step: 0.01, + fixedDecimals: 2, + propertyName: "bloom.bloomThreshold", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Size", + type: "number", + min: 0, + min: 2, + step: 0.01, + fixedDecimals: 2, + propertyName: "bloom.bloomSize", + showPropertyRule: { "bloomMode": "enabled" }, + }, + ] + }, + { + id: "model", + addToGroup: "base", + properties: [ + { + label: "Model", + type: "string", + propertyName: "modelURL", + }, + { + label: "Collision Shape", + type: "dropdown", + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, + propertyName: "shapeType", + }, + { + label: "Compound Shape", + type: "string", + propertyName: "compoundShapeURL", + }, + { + label: "Animation", + type: "string", + propertyName: "animation.url", + }, + { + label: "Play Automatically", + type: "bool", + propertyName: "animation.running", + }, + { + label: "Allow Transition", + type: "bool", + propertyName: "animation.allowTranslation", + }, + { + label: "Loop", + type: "bool", + propertyName: "animation.loop", + }, + { + label: "Hold", + type: "bool", + propertyName: "animation.hold", + }, + { + label: "Animation Frame", + type: "number", + propertyName: "animation.currentFrame", + }, + { + label: "First Frame", + type: "number", + propertyName: "animation.firstFrame", + }, + { + label: "Last Frame", + type: "number", + propertyName: "animation.lastFrame", + }, + { + label: "Animation FPS", + type: "number", + propertyName: "animation.fps", + }, + { + label: "Texture", + type: "textarea", + propertyName: "textures", + }, + { + label: "Original Texture", + type: "textarea", + propertyName: "originalTextures", + readOnly: true, + }, + ] + }, + { + id: "image", + addToGroup: "base", + properties: [ + { + label: "Image", + type: "string", + propertyName: "image", + }, + ] + }, + { + id: "web", + addToGroup: "base", + properties: [ + { + label: "Source", + type: "string", + propertyName: "sourceUrl", + }, + { + label: "Source Resolution", + type: "number", + propertyName: "dpi", + }, + ] + }, + { + id: "light", + addToGroup: "base", + properties: [ + { + label: "Light Color", + type: "color", + propertyName: "lightColor", // this actually shares "color" property with shape Color + // but separating naming here to separate property fields + }, + { + label: "Intensity", + type: "number", + min: 0, + step: 0.1, + fixedDecimals: 1, + propertyName: "intensity", + }, + { + label: "Fall-Off Radius", + type: "number", + min: 0, + step: 0.1, + fixedDecimals: 1, + unit: "m", + propertyName: "falloffRadius", + }, + { + label: "Spotlight", + type: "bool", + propertyName: "isSpotlight", + }, + { + label: "Spotlight Exponent", + type: "number", + step: 0.01, + fixedDecimals: 2, + propertyName: "exponent", + }, + { + label: "Spotlight Cut-Off", + type: "number", + step: 0.01, + fixedDecimals: 2, + propertyName: "cutoff", + }, + ] + }, + { + id: "material", + addToGroup: "base", + properties: [ + { + label: "Material URL", + type: "string", + propertyName: "materialURL", + }, + { + label: "Material Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + propertyName: "materialData", + }, + { + label: "Submesh to Replace", + type: "number", + min: 0, + step: 1, + propertyName: "submeshToReplace", + }, + { + label: "Material Name to Replace", + type: "string", + propertyName: "materialNameToReplace", + }, + { + label: "Select Submesh", + type: "bool", + propertyName: "selectSubmesh", + }, + { + label: "Priority", + type: "number", + min: 0, + propertyName: "priority", + }, + { + label: "Material Position", + type: "vec2", + min: 0, + min: 1, + step: 0.1, + vec2Type: "xy", + subLabels: [ "x", "y" ], + propertyName: "materialMappingPos", + }, + { + label: "Material Scale", + type: "vec2", + min: 0, + step: 0.1, + vec2Type: "wh", + subLabels: [ "width", "height" ], + propertyName: "materialMappingScale", + }, + { + label: "Material Rotation", + type: "number", + step: 0.1, + fixedDecimals: 2, + unit: "deg", + propertyName: "materialMappingRot", + }, + ] + }, + { + id: "spatial", + label: "SPATIAL", + properties: [ + { + label: "Position", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyName: "position", + }, + { + label: "Rotation", + type: "vec3", + step: 0.1, + vec3Type: "pyr", + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg", + propertyName: "rotation", + }, + { + label: "Dimension", + type: "vec3", + step: 0.1, + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyName: "dimensions", + }, + { + label: "Scale", + type: "number", + defaultValue: 100, + unit: "%", + buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + propertyName: "scale", + }, + { + label: "Pivot", + type: "vec3", + step: 0.1, + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "(ratio of dimension)", + propertyName: "registrationPoint", + }, + { + label: "Align", + type: "buttons", + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + propertyName: "alignToGrid", + }, + ] + }, + { + id: "collision", + label: "COLLISION", + twoColumn: true, + properties: [ + { + label: "Collides", + type: "bool", + propertyName: "collisionless", + inverse: true, + column: -1, // before two columns div + }, + { + label: "Dynamic", + type: "bool", + propertyName: "dynamic", + column: -1, // before two columns div + }, + { + label: "Collides With", + type: "sub-header", + propertyName: "collidesWithHeader", + showPropertyRule: { "collisionless": "false" }, + column: 1, + }, + { + label: "", + type: "sub-header", + propertyName: "collidesWithHeaderHelper", + showPropertyRule: { "collisionless": "false" }, + column: 2, + }, + { + label: "Static Entities", + type: "bool", + propertyName: "static", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, + }, + { + label: "Dynamic Entities", + type: "bool", + propertyName: "dynamic", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 2, + }, + { + label: "Kinematic Entities", + type: "bool", + propertyName: "kinematic", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, + }, + { + label: "My Avatar", + type: "bool", + propertyName: "myAvatar", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 2, + }, + { + label: "Other Avatars", + type: "bool", + propertyName: "otherAvatar", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, + }, + { + label: "Collision sound URL", + type: "string", + propertyName: "collisionSoundURL", + showPropertyRule: { "collisionless": "false" }, + }, + ] + }, + { + id: "behavior", + label: "BEHAVIOR", + twoColumn: true, + properties: [ + { + label: "Grabbable", + type: "bool", + propertyName: "grabbable", + column: 1, + }, + { + label: "Triggerable", + type: "bool", + propertyName: "triggerable", + column: 2, + }, + { + label: "Cloneable", + type: "bool", + propertyName: "cloneable", + column: 1, + }, + { + label: "Ignore inverse kinematics", + type: "bool", + propertyName: "ignoreIK", + column: 2, + }, + { + label: "Clone Lifetime", + type: "number", + unit: "s", + propertyName: "cloneLifetime", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Limit", + type: "number", + propertyName: "cloneLimit", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Dynamic", + type: "bool", + propertyName: "cloneDynamic", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Avatar Entity", + type: "bool", + propertyName: "cloneAvatarEntity", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Can cast shadow", + type: "bool", + propertyName: "castShadow", + }, + { + label: "Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + propertyName: "script", + }, + { + label: "Server Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + propertyName: "serverScripts", + }, + { + label: "Lifetime", + type: "number", + unit: "s", + propertyName: "lifetime", + }, + { + label: "User Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + propertyName: "userData", + }, + ] + }, + { + id: "physics", + label: "PHYSICS", + properties: [ + { + label: "Linear Velocity", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s", + propertyName: "velocity", + }, + { + label: "Linear Damping", + type: "number", + fixedDecimals: 2, + propertyName: "damping", + }, + { + label: "Angular Velocity", + type: "vec3", + multiplier: DEGREES_TO_RADIANS, + vec3Type: "pyr", + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg/s", + propertyName: "angularVelocity", + }, + { + label: "Angular Damping", + type: "number", + fixedDecimals: 4, + propertyName: "angularDamping", + }, + { + label: "Bounciness", + type: "number", + fixedDecimals: 4, + propertyName: "restitution", + }, + { + label: "Friction", + type: "number", + fixedDecimals: 4, + propertyName: "friction", + }, + { + label: "Density", + type: "number", + fixedDecimals: 4, + propertyName: "density", + }, + { + label: "Gravity", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s2", + propertyName: "gravity", + }, + { + label: "Acceleration", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s2", + propertyName: "acceleration", + }, + ] + }, +]; + +const GROUPS_PER_TYPE = { + None: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], + Shape: [ 'base', 'shape', 'spatial', 'collision', 'behavior', 'physics' ], + Text: [ 'base', 'text', 'spatial', 'collision', 'behavior', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'collision', 'behavior', 'physics' ], + Model: [ 'base', 'model', 'spatial', 'collision', 'behavior', 'physics' ], + Image: [ 'base', 'image', 'spatial', 'collision', 'behavior', 'physics' ], + Web: [ 'base', 'web', 'spatial', 'collision', 'behavior', 'physics' ], + Light: [ 'base', 'light', 'spatial', 'collision', 'behavior', 'physics' ], + Material: [ 'base', 'material', 'spatial', 'behavior' ], + ParticleEffect: [ 'base', 'spatial', 'behavior', 'physics' ], + Multiple: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], }; -var EDITOR_TIMEOUT_DURATION = 1500; -var KEY_P = 80; // Key code for letter p used for Parenting hotkey. +const EDITOR_TIMEOUT_DURATION = 1500; +const KEY_P = 80; // Key code for letter p used for Parenting hotkey. + +const MATERIAL_PREFIX_STRING = "mat::"; + +const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; + +var elGroups = {}; +var elPropertyElements = {}; +var showPropertyRules = {}; +var subProperties = []; +var icons = {}; var colorPickers = {}; var lastEntityID = null; -var MATERIAL_PREFIX_STRING = "mat::"; - -var PENDING_SCRIPT_STATUS = "[ Fetching status ]"; - function debugPrint(message) { EventBridge.emitWebEvent( JSON.stringify({ @@ -64,7 +946,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = document.getElementById("property-locked"); + var elLocked = elPropertyElements["locked"]; if (elLocked.checked === false) { removeStaticUserData(); @@ -72,20 +954,19 @@ function enableProperties() { } } - function disableProperties() { disableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); disableChildren(document, ".colpick"); for (var pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = document.getElementById("property-locked"); + var elLocked = elPropertyElements["locked"]; if (elLocked.checked === true) { - if ($('#userdata-editor').css('display') === "block") { + if ($('#property-userData-editor').css('display') === "block") { showStaticUserData(); } - if ($('#materialdata-editor').css('display') === "block") { + if ($('#property-materialData-editor').css('display') === "block") { showStaticMaterialData(); } } @@ -99,7 +980,21 @@ function showElements(els, show) { function updateProperty(propertyName, propertyValue) { var properties = {}; - properties[propertyName] = propertyValue; + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + properties[propertyGroupName] = {}; + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + properties[propertyGroupName][subPropertyName] = {}; + properties[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; + } else { + properties[propertyGroupName][subPropertyName] = propertyValue; + } + } else { + properties[propertyName] = propertyValue; + } updateProperties(properties); } @@ -111,18 +1006,9 @@ function updateProperties(properties) { })); } -function createEmitCheckedPropertyUpdateFunction(propertyName) { +function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { return function() { - updateProperty(propertyName, this.checked); - }; -} - -function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.checked; - updateProperties(properties); + updateProperty(propertyName, inverse ? !this.checked : this.checked); }; } @@ -134,15 +1020,6 @@ function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { }; } -function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.value; - updateProperties(properties); - }; -} - function createImageURLUpdateFunction(propertyName) { return function () { var newTextures = JSON.stringify({ "tex.picture": this.value }); @@ -156,33 +1033,6 @@ function createEmitTextPropertyUpdateFunction(propertyName) { }; } -function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit, - zoneComponentModeDisabled, zoneComponentModeEnabled) { - - return function() { - var zoneComponentMode; - - if (zoneComponentModeInherit.checked) { - zoneComponentMode = 'inherit'; - } else if (zoneComponentModeDisabled.checked) { - zoneComponentMode = 'disabled'; - } else if (zoneComponentModeEnabled.checked) { - zoneComponentMode = 'enabled'; - } - - updateProperty(zoneComponent, zoneComponentMode); - }; -} - -function createEmitGroupTextPropertyUpdateFunction(group, propertyName) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.value; - updateProperties(properties); - }; -} - function createEmitVec2PropertyUpdateFunction(property, elX, elY) { return function () { var properties = {}; @@ -194,23 +1044,21 @@ function createEmitVec2PropertyUpdateFunction(property, elX, elY) { }; } -function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { - return function() { +function createEmitVec2PropertyUpdateFunctionWithMultiplier(property, elX, elY, multiplier) { + return function () { var properties = {}; properties[property] = { - x: elX.value, - y: elY.value, - z: elZ.value + x: elX.value * multiplier, + y: elY.value * multiplier }; updateProperties(properties); }; } -function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) { +function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { return function() { var properties = {}; - properties[group] = {}; - properties[group][property] = { + properties[property] = { x: elX.value, y: elY.value, z: elZ ? elZ.value : 0 @@ -256,20 +1104,6 @@ function emitColorPropertyUpdate(property, red, green, blue, group) { updateProperties(properties); } - -function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][property] = { - red: elRed.value, - green: elGreen.value, - blue: elBlue.value - }; - updateProperties(properties); - }; -} - function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { if (subPropertyElement.checked) { if (propertyValue.indexOf(subPropertyString)) { @@ -282,6 +1116,30 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen updateProperty(propertyName, propertyValue); } +function clearUserData() { + let elUserData = elPropertyElements["userData"]; + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + updateProperty('userData', elUserData.value); +} + +function newJSONEditor() { + deleteJSONEditor(); + createJSONEditor(); + var data = {}; + setEditorJSON(data); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + showSaveUserDataButton(); +} + +function saveUserData() { + saveJSONUserData(true); +} + function setUserDataFromEditor(noUpdate) { var json = null; try { @@ -315,7 +1173,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r var parsedData = {}; var keysToBeRemoved = removeKeys ? removeKeys : []; try { - if ($('#userdata-editor').css('height') !== "0px") { + if ($('#property-userData-editor').css('height') !== "0px") { // if there is an expanded, we want to use its json. parsedData = getEditorJSON(); } else { @@ -371,6 +1229,135 @@ function userDataChanger(groupName, keyName, values, userDataElement, defaultVal multiDataUpdater(groupName, val, userDataElement, def, removeKeys); } +var editor = null; + +function createJSONEditor() { + var container = document.getElementById("property-userData-editor"); + var options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'userData', + onModeChange: function() { + $('.jsoneditor-poweredBy').remove(); + }, + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + var currentJSONString = editor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#property-userData-button-save').attr('disabled', false); + + + } + }; + editor = new JSONEditor(container, options); +} + +function showSaveUserDataButton() { + $('#property-userData-button-save').show(); +} + +function hideSaveUserDataButton() { + $('#property-userData-button-save').hide(); +} + +function disableSaveUserDataButton() { + $('#property-userData-button-save').attr('disabled', true); +} + +function showNewJSONEditorButton() { + $('#property-userData-button-edit').show(); +} + +function hideNewJSONEditorButton() { + $('#property-userData-button-edit').hide(); +} + +function showUserDataTextArea() { + $('#property-userData').show(); +} + +function hideUserDataTextArea() { + $('#property-userData').hide(); +} + +function hideUserDataSaved() { + $('#property-userData-saved').hide(); +} + +function showStaticUserData() { + if (editor !== null) { + $('#property-userData-static').show(); + $('#property-userData-static').css('height', $('#property-userData-editor').height()); + $('#property-userData-static').text(editor.getText()); + } +} + +function removeStaticUserData() { + $('#property-userData-static').hide(); +} + +function setEditorJSON(json) { + editor.set(json); + if (editor.hasOwnProperty('expandAll')) { + editor.expandAll(); + } +} + +function getEditorJSON() { + return editor.get(); +} + +function deleteJSONEditor() { + if (editor !== null) { + editor.destroy(); + editor = null; + } +} + +var savedJSONTimer = null; + +function saveJSONUserData(noUpdate) { + setUserDataFromEditor(noUpdate); + $('#property-userData-saved').show(); + $('#property-userData-button-save').attr('disabled', true); + if (savedJSONTimer !== null) { + clearTimeout(savedJSONTimer); + } + savedJSONTimer = setTimeout(function() { + hideUserDataSaved(); + }, EDITOR_TIMEOUT_DURATION); +} + +function clearMaterialData() { + let elMaterialData = elPropertyElements["materialData"]; + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value); +} + +function newJSONMaterialEditor() { + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + var data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); +} + +function saveMaterialData() { + saveJSONMaterialData(true); +} + function setMaterialDataFromEditor(noUpdate) { var json = null; try { @@ -399,16 +1386,10 @@ function setMaterialDataFromEditor(noUpdate) { } } -function setTextareaScrolling(element) { - var isScrolling = element.scrollHeight > element.offsetHeight; - element.setAttribute("scrolling", isScrolling ? "true" : "false"); -} - - var materialEditor = null; function createJSONMaterialEditor() { - var container = document.getElementById("materialdata-editor"); + var container = document.getElementById("property-materialData-editor"); var options = { search: false, mode: 'tree', @@ -426,7 +1407,7 @@ function createJSONMaterialEditor() { if (currentJSONString === '{"":""}') { return; } - $('#materialdata-save').attr('disabled', false); + $('#property-materialData-button-save').attr('disabled', false); } @@ -434,40 +1415,48 @@ function createJSONMaterialEditor() { materialEditor = new JSONEditor(container, options); } -function hideNewJSONMaterialEditorButton() { - $('#materialdata-new-editor').hide(); -} - function showSaveMaterialDataButton() { - $('#materialdata-save').show(); + $('#property-materialData-button-save').show(); } function hideSaveMaterialDataButton() { - $('#materialdata-save').hide(); + $('#property-materialData-button-save').hide(); +} + +function disableSaveMaterialDataButton() { + $('#property-materialData-button-save').attr('disabled', true); } function showNewJSONMaterialEditorButton() { - $('#materialdata-new-editor').show(); + $('#property-materialData-button-edit').show(); +} + +function hideNewJSONMaterialEditorButton() { + $('#property-materialData-button-edit').hide(); } function showMaterialDataTextArea() { - $('#property-material-data').show(); + $('#property-materialData').show(); } function hideMaterialDataTextArea() { - $('#property-material-data').hide(); + $('#property-materialData').hide(); +} + +function hideMaterialDataSaved() { + $('#property-materialData-saved').hide(); } function showStaticMaterialData() { if (materialEditor !== null) { - $('#static-materialdata').show(); - $('#static-materialdata').css('height', $('#materialdata-editor').height()); - $('#static-materialdata').text(materialEditor.getText()); + $('#property-materialData-static').show(); + $('#property-materialData-static').css('height', $('#property-materialData-editor').height()); + $('#property-materialData-static').text(materialEditor.getText()); } } function removeStaticMaterialData() { - $('#static-materialdata').hide(); + $('#property-materialData-static').hide(); } function setMaterialEditorJSON(json) { @@ -492,112 +1481,13 @@ var savedMaterialJSONTimer = null; function saveJSONMaterialData(noUpdate) { setMaterialDataFromEditor(noUpdate); - $('#materialdata-saved').show(); - $('#materialdata-save').attr('disabled', true); + $('#property-materialData-saved').show(); + $('#property-materialData-button-save').attr('disabled', true); if (savedMaterialJSONTimer !== null) { clearTimeout(savedMaterialJSONTimer); } savedMaterialJSONTimer = setTimeout(function() { - $('#materialdata-saved').hide(); - - }, EDITOR_TIMEOUT_DURATION); -} - -var editor = null; - -function createJSONEditor() { - var container = document.getElementById("userdata-editor"); - var options = { - search: false, - mode: 'tree', - modes: ['code', 'tree'], - name: 'userData', - onModeChange: function() { - $('.jsoneditor-poweredBy').remove(); - }, - onError: function(e) { - alert('JSON editor:' + e); - }, - onChange: function() { - var currentJSONString = editor.getText(); - - if (currentJSONString === '{"":""}') { - return; - } - $('#userdata-save').attr('disabled', false); - - - } - }; - editor = new JSONEditor(container, options); -} - -function hideNewJSONEditorButton() { - $('#userdata-new-editor').hide(); -} - -function showSaveUserDataButton() { - $('#userdata-save').show(); -} - -function hideSaveUserDataButton() { - $('#userdata-save').hide(); -} - -function showNewJSONEditorButton() { - $('#userdata-new-editor').show(); -} - -function showUserDataTextArea() { - $('#property-user-data').show(); -} - -function hideUserDataTextArea() { - $('#property-user-data').hide(); -} - -function showStaticUserData() { - if (editor !== null) { - $('#static-userdata').show(); - $('#static-userdata').css('height', $('#userdata-editor').height()); - $('#static-userdata').text(editor.getText()); - } -} - -function removeStaticUserData() { - $('#static-userdata').hide(); -} - -function setEditorJSON(json) { - editor.set(json); - if (editor.hasOwnProperty('expandAll')) { - editor.expandAll(); - } -} - -function getEditorJSON() { - return editor.get(); -} - -function deleteJSONEditor() { - if (editor !== null) { - editor.destroy(); - editor = null; - } -} - -var savedJSONTimer = null; - -function saveJSONUserData(noUpdate) { - setUserDataFromEditor(noUpdate); - $('#userdata-saved').show(); - $('#userdata-save').attr('disabled', true); - if (savedJSONTimer !== null) { - clearTimeout(savedJSONTimer); - } - savedJSONTimer = setTimeout(function() { - $('#userdata-saved').hide(); - + hideMaterialDataSaved(); }, EDITOR_TIMEOUT_DURATION); } @@ -610,14 +1500,14 @@ function bindAllNonJSONEditorElements() { // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { - if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear" || e.target.id === "materialdata-new-editor" || e.target.id === "materialdata-clear") { + if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { return; } else { - if ($('#userdata-editor').css('height') !== "0px") { - saveJSONUserData(true); + if ($('#property-userData-editor').css('height') !== "0px") { + saveUserData(); } - if ($('#materialdata-editor').css('height') !== "0px") { - saveJSONMaterialData(true); + if ($('#property-materialData-editor').css('height') !== "0px") { + saveMaterialData(); } } }); @@ -634,264 +1524,575 @@ function unbindAllInputs() { } } +function setTextareaScrolling(element) { + var isScrolling = element.scrollHeight > element.offsetHeight; + element.setAttribute("scrolling", isScrolling ? "true" : "false"); +} + function showParentMaterialNameBox(number, elNumber, elString) { if (number) { - $('#property-parent-material-id-number-container').show(); - $('#property-parent-material-id-string-container').hide(); + $('#property-submeshToReplace').parent().show(); + $('#property-materialNameToReplace').parent().hide(); elString.value = ""; } else { - $('#property-parent-material-id-string-container').show(); - $('#property-parent-material-id-number-container').hide(); + $('#property-materialNameToReplace').parent().show(); + $('#property-submeshToReplace').parent().hide(); elNumber.value = 0; } } +function rescaleDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); +} + +function moveSelectionToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); +} + +function moveAllToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); +} + +function resetToNaturalDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); +} + +function reloadScripts() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); +} + +function reloadServerScripts() { + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); +} + +function copySkyboxURLToAmbientURL() { + let skyboxURL = elPropertyElements["skybox.url"].value; + elPropertyElements["ambientLight.ambientURL"].value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL); +} + +function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { + let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; + elLabel.setAttribute("for", elementPropertyID); + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementPropertyID); + elInput.setAttribute("type", "number"); + elInput.setAttribute("class", subLabel); + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } + if (step !== undefined) { + elInput.setAttribute("step", step); + } + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elTuple.appendChild(elDiv); + return elInput; +} + +function addUnit(unit, elLabel) { + if (unit !== undefined) { + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "unit"); + elSpan.innerHTML = unit; + elLabel.appendChild(elSpan); + } +} + +function addButtons(elProperty, propertyID, buttons, newRow) { + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "row"); + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.setAttribute("type", "button"); + elButton.setAttribute("class", button.className); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } +} + +function showGroupsForType(type) { + if (type === "Box" || type === "Sphere") { + type = "Shape"; + } + + let typeGroups = GROUPS_PER_TYPE[type]; + + for (let groupKey in elGroups) { + let elGroup = elGroups[groupKey]; + if (typeGroups && typeGroups.indexOf(groupKey) > -1) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } + } +} + +function resetProperties() { + for (let propertyName in elPropertyElements) { + let elProperty = elPropertyElements[propertyName]; + if (elProperty instanceof Array) { + if (elProperty.length === 2 || elProperty.length === 3) { + // vec2/vec3 are array of 2/3 input numbers + elProperty[0].value = ""; + elProperty[1].value = ""; + if (elProperty[2] !== undefined) { + elProperty[2].value = ""; + } + } else if (elProperty.length === 4) { + // color is array of color picker and 3 input numbers + elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elProperty[1].value = ""; + elProperty[2].value = ""; + elProperty[3].value = ""; + } + } else if (elProperty.getAttribute("type") === "number") { + if (elProperty.getAttribute("defaultValue")) { + elProperty.value = elProperty.getAttribute("defaultValue"); + } else { + elProperty.value = ""; + } + } else if (elProperty.getAttribute("type") === "checkbox") { + elProperty.checked = false; + } else { + elProperty.value = ""; + } + } + + for (let showPropertyRule in showPropertyRules) { + let propertyShowRules = showPropertyRules[showPropertyRule]; + for (let propertyToHide in propertyShowRules) { + let elPropertyToHide = elPropertyElements[propertyToHide]; + if (elPropertyToHide) { + let parentNode = elPropertyToHide.parentNode; + if (parentNode === undefined && elPropertyToHide instanceof Array) { + parentNode = elPropertyToHide[0].parentNode; + } + parentNode.style.display = "none"; + } + } + } +} + function loaded() { - openEventBridge(function() { + openEventBridge(function() { + let elPropertiesList = document.getElementById("properties-list"); + + GROUPS.forEach(function(group) { + let elGroup; + if (group.addToGroup !== undefined) { + let fieldset = document.getElementById(group.addToGroup); + elGroup = document.createElement('div'); + fieldset.appendChild(elGroup); + } else { + elGroup = document.createElement('fieldset'); + elGroup.setAttribute("class", "major"); + elGroup.setAttribute("id", group.id); + elPropertiesList.appendChild(elGroup); + } - var elPropertiesList = document.getElementById("properties-list"); - var elID = document.getElementById("property-id"); - var elType = document.getElementById("property-type"); - var elTypeIcon = document.getElementById("type-icon"); - var elName = document.getElementById("property-name"); - var elLocked = document.getElementById("property-locked"); - var elVisible = document.getElementById("property-visible"); - var elPositionX = document.getElementById("property-pos-x"); - var elPositionY = document.getElementById("property-pos-y"); - var elPositionZ = document.getElementById("property-pos-z"); - var elMoveSelectionToGrid = document.getElementById("move-selection-to-grid"); - var elMoveAllToGrid = document.getElementById("move-all-to-grid"); + if (group.label !== undefined) { + let elLegend = document.createElement('legend'); + elLegend.innerText = group.label; + elLegend.setAttribute("class", "section-header"); + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", ".collapse-icon"); + elSpan.innerText = "M"; + elLegend.appendChild(elSpan); + elGroup.appendChild(elLegend); + } + + group.properties.forEach(function(property) { + let propertyType = property.type; + let propertyName = property.propertyName; + let propertyID = "property-" + propertyName; + propertyID = propertyID.replace(".", "-"); + + let elProperty; + if (propertyType === "sub-header") { + elProperty = document.createElement('legend'); + elProperty.innerText = property.label; + elProperty.setAttribute("class", "sub-section-header"); + } else { + elProperty = document.createElement('div'); + elProperty.setAttribute("id", "div-" + propertyName); + } + + if (group.twoColumn && property.column !== undefined && property.column !== -1) { + let columnName = group.id + "column" + property.column; + let elColumn = document.getElementById(columnName); + if (!elColumn) { + let columnDivName = group.id + "columnDiv"; + let elColumnDiv = document.getElementById(columnDivName); + if (!elColumnDiv) { + elColumnDiv = document.createElement('div'); + elColumnDiv.setAttribute("class", "two-column"); + elColumnDiv.setAttribute("id", group.id + "columnDiv"); + elGroup.appendChild(elColumnDiv); + } + elColumn = document.createElement('fieldset'); + elColumn.setAttribute("class", "column"); + elColumn.setAttribute("id", columnName); + elColumnDiv.appendChild(elColumn); + } + elColumn.appendChild(elProperty); + } else { + elGroup.appendChild(elProperty); + } + + let elLabel = document.createElement('label'); + elLabel.innerText = property.label; + elLabel.setAttribute("for", propertyID); + + switch (propertyType) { + case 'vec3': { + elProperty.setAttribute("class", "property " + property.vec3Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(property.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); + let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); + let elInputZ = createTupleNumberInput(elTuple, propertyID, property.subLabels[2], property.min, property.max, property.step); + + let inputChangeFunction; + let multiplier = 1; + if (property.multiplier !== undefined) { + multiplier = property.multiplier; + inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); + } else { + inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, elInputZ, property.multiplier); + } + elInputX.setAttribute("multiplier", multiplier); + elInputY.setAttribute("multiplier", multiplier); + elInputZ.setAttribute("multiplier", multiplier); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + elInputZ.addEventListener('change', inputChangeFunction); + + elPropertyElements[propertyName] = [elInputX, elInputY, elInputZ]; + break; + } + case 'vec2': { + elProperty.setAttribute("class", "property " + property.vec2Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(property.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); + let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); + + let inputChangeFunction; + let multiplier = 1; + if (property.multiplier !== undefined) { + multiplier = property.multiplier; + inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY); + } else { + inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, property.multiplier); + } + elInputX.setAttribute("multiplier", multiplier); + elInputY.setAttribute("multiplier", multiplier); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + + elPropertyElements[propertyName] = [elInputX, elInputY]; + break; + } + case 'color': { + elProperty.setAttribute("class", "property rgb fstuple"); + + let elColorPicker = document.createElement('div'); + elColorPicker.setAttribute("class", "color-picker"); + elColorPicker.setAttribute("id", propertyID); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputR = createTupleNumberInput(elTuple, propertyID, "red", 0, 255, 1); + let elInputG = createTupleNumberInput(elTuple, propertyID, "green", 0, 255, 1); + let elInputB = createTupleNumberInput(elTuple, propertyID, "blue", 0, 255, 1); + + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); + elInputR.addEventListener('change', inputChangeFunction); + elInputG.addEventListener('change', inputChangeFunction); + elInputB.addEventListener('change', inputChangeFunction); + + let colorPickerID = "#" + propertyID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + $(colorPickerID).attr('active', 'true'); + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elInputR.value, + "g": elInputG.value, + "b": elInputB.value + }); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + } + }); + + elPropertyElements[propertyName] = [elColorPicker, elInputR, elInputG, elInputB]; + break; + } + case 'string': { + elProperty.setAttribute("class", "property text"); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "text"); + if (property.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, false); + } + + elPropertyElements[propertyName] = elInput; + break; + } + case 'bool': { + elProperty.setAttribute("class", "property checkbox"); + + if (property.glyph !== undefined) { + elLabel.innerText = " " + property.label; + let elSpan = document.createElement('span'); + elSpan.innerHTML = property.glyph; + elLabel.insertBefore(elSpan, elLabel.firstChild); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "checkbox"); + + let inverse = property.inverse; + elInput.setAttribute("inverse", inverse ? "true" : "false"); + + elProperty.appendChild(elInput); + elProperty.appendChild(elLabel); + + let subPropertyOf = property.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.setAttribute("subPropertyOf", subPropertyOf); + elPropertyElements[propertyName] = elInput; + subProperties.push(propertyName); + elInput.addEventListener('change', function() { + updateCheckedSubProperty(subPropertyOf, properties[subPropertyOf], elInput, propertyName); + }); + } else { + elPropertyElements[propertyName] = elInput; + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, inverse)); + } + break; + } + case 'dropdown': { + elProperty.setAttribute("class", "property dropdown"); + + let elInput = document.createElement('select'); + elInput.setAttribute("id", propertyID); + + for (let optionKey in property.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = property.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + elPropertyElements[propertyName] = elInput; + break; + } + case 'number': { + elProperty.setAttribute("class", "property number"); + + addUnit(property.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "number"); + if (property.min !== undefined) { + elInput.setAttribute("min", property.min); + } + if (property.max !== undefined) { + elInput.setAttribute("max", property.max); + } + if (property.step !== undefined) { + elInput.setAttribute("step", property.step); + } + + let fixedDecimals = property.fixedDecimals !== undefined ? property.fixedDecimals : 0; + elInput.setAttribute("fixedDecimals", fixedDecimals); + + let defaultValue = property.defaultValue; + if (defaultValue !== undefined) { + elInput.setAttribute("defaultValue", defaultValue); + elInput.value = defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, true); + } + + elPropertyElements[propertyName] = elInput; + break; + } + case 'textarea': { + elProperty.setAttribute("class", "property textarea"); + + elProperty.appendChild(elLabel); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, true); + } + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", propertyID); + if (property.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elInput); + elPropertyElements[propertyName] = elInput; + break; + } + case 'icon': { + elProperty.setAttribute("class", "property value"); + + elLabel.setAttribute("id", propertyID); + elLabel.innerHTML = " " + property.label; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", propertyID + "-icon"); + icons[propertyName] = property.icons; - var elDimensionsX = document.getElementById("property-dim-x"); - var elDimensionsY = document.getElementById("property-dim-y"); - var elDimensionsZ = document.getElementById("property-dim-z"); - var elResetToNaturalDimensions = document.getElementById("reset-to-natural-dimensions"); - var elRescaleDimensionsPct = document.getElementById("dimension-rescale-pct"); - var elRescaleDimensionsButton = document.getElementById("dimension-rescale-button"); - - var elParentID = document.getElementById("property-parent-id"); - var elParentJointIndex = document.getElementById("property-parent-joint-index"); - - var elRegistrationX = document.getElementById("property-reg-x"); - var elRegistrationY = document.getElementById("property-reg-y"); - var elRegistrationZ = document.getElementById("property-reg-z"); - - var elRotationX = document.getElementById("property-rot-x"); - var elRotationY = document.getElementById("property-rot-y"); - var elRotationZ = document.getElementById("property-rot-z"); - - var elLinearVelocityX = document.getElementById("property-lvel-x"); - var elLinearVelocityY = document.getElementById("property-lvel-y"); - var elLinearVelocityZ = document.getElementById("property-lvel-z"); - var elLinearDamping = document.getElementById("property-ldamping"); - - var elAngularVelocityX = document.getElementById("property-avel-x"); - var elAngularVelocityY = document.getElementById("property-avel-y"); - var elAngularVelocityZ = document.getElementById("property-avel-z"); - var elAngularDamping = document.getElementById("property-adamping"); - - var elRestitution = document.getElementById("property-restitution"); - var elFriction = document.getElementById("property-friction"); - - var elGravityX = document.getElementById("property-grav-x"); - var elGravityY = document.getElementById("property-grav-y"); - var elGravityZ = document.getElementById("property-grav-z"); - - var elAccelerationX = document.getElementById("property-lacc-x"); - var elAccelerationY = document.getElementById("property-lacc-y"); - var elAccelerationZ = document.getElementById("property-lacc-z"); - - var elDensity = document.getElementById("property-density"); - var elCollisionless = document.getElementById("property-collisionless"); - var elDynamic = document.getElementById("property-dynamic"); - var elCollideStatic = document.getElementById("property-collide-static"); - var elCollideDynamic = document.getElementById("property-collide-dynamic"); - var elCollideKinematic = document.getElementById("property-collide-kinematic"); - var elCollideMyAvatar = document.getElementById("property-collide-myAvatar"); - var elCollideOtherAvatar = document.getElementById("property-collide-otherAvatar"); - var elCollisionSoundURL = document.getElementById("property-collision-sound-url"); - - var elGrabbable = document.getElementById("property-grabbable"); - - var elCloneable = document.getElementById("property-cloneable"); - var elCloneableDynamic = document.getElementById("property-cloneable-dynamic"); - var elCloneableAvatarEntity = document.getElementById("property-cloneable-avatarEntity"); - var elCloneableGroup = document.getElementById("group-cloneable-group"); - var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); - var elCloneableLimit = document.getElementById("property-cloneable-limit"); - - var elTriggerable = document.getElementById("property-triggerable"); - var elIgnoreIK = document.getElementById("property-ignore-ik"); - - var elLifetime = document.getElementById("property-lifetime"); - var elScriptURL = document.getElementById("property-script-url"); - var elScriptTimestamp = document.getElementById("property-script-timestamp"); - var elReloadScriptsButton = document.getElementById("reload-script-button"); - var elServerScripts = document.getElementById("property-server-scripts"); - var elReloadServerScriptsButton = document.getElementById("reload-server-scripts-button"); - var elServerScriptStatus = document.getElementById("server-script-status"); - var elServerScriptError = document.getElementById("server-script-error"); - var elUserData = document.getElementById("property-user-data"); - var elClearUserData = document.getElementById("userdata-clear"); - var elSaveUserData = document.getElementById("userdata-save"); - var elNewJSONEditor = document.getElementById('userdata-new-editor'); - var elColorControlVariant2 = document.getElementById("property-color-control2"); - var elColorRed = document.getElementById("property-color-red"); - var elColorGreen = document.getElementById("property-color-green"); - var elColorBlue = document.getElementById("property-color-blue"); - - var elShape = document.getElementById("property-shape"); - - var elCanCastShadow = document.getElementById("property-can-cast-shadow"); - - var elLightSpotLight = document.getElementById("property-light-spot-light"); - var elLightColor = document.getElementById("property-light-color"); - var elLightColorRed = document.getElementById("property-light-color-red"); - var elLightColorGreen = document.getElementById("property-light-color-green"); - var elLightColorBlue = document.getElementById("property-light-color-blue"); - - var elLightIntensity = document.getElementById("property-light-intensity"); - var elLightFalloffRadius = document.getElementById("property-light-falloff-radius"); - var elLightExponent = document.getElementById("property-light-exponent"); - var elLightCutoff = document.getElementById("property-light-cutoff"); - - var elModelURL = document.getElementById("property-model-url"); - var elShapeType = document.getElementById("property-shape-type"); - var elCompoundShapeURL = document.getElementById("property-compound-shape-url"); - var elModelAnimationURL = document.getElementById("property-model-animation-url"); - var elModelAnimationPlaying = document.getElementById("property-model-animation-playing"); - var elModelAnimationFPS = document.getElementById("property-model-animation-fps"); - var elModelAnimationFrame = document.getElementById("property-model-animation-frame"); - var elModelAnimationFirstFrame = document.getElementById("property-model-animation-first-frame"); - var elModelAnimationLastFrame = document.getElementById("property-model-animation-last-frame"); - var elModelAnimationLoop = document.getElementById("property-model-animation-loop"); - var elModelAnimationHold = document.getElementById("property-model-animation-hold"); - var elModelAnimationAllowTranslation = document.getElementById("property-model-animation-allow-translation"); - var elModelTextures = document.getElementById("property-model-textures"); - var elModelOriginalTextures = document.getElementById("property-model-original-textures"); - - var elMaterialURL = document.getElementById("property-material-url"); - //var elMaterialMappingMode = document.getElementById("property-material-mapping-mode"); - var elPriority = document.getElementById("property-priority"); - var elParentMaterialNameString = document.getElementById("property-parent-material-id-string"); - var elParentMaterialNameNumber = document.getElementById("property-parent-material-id-number"); - var elParentMaterialNameCheckbox = document.getElementById("property-parent-material-id-checkbox"); - var elMaterialMappingPosX = document.getElementById("property-material-mapping-pos-x"); - var elMaterialMappingPosY = document.getElementById("property-material-mapping-pos-y"); - var elMaterialMappingScaleX = document.getElementById("property-material-mapping-scale-x"); - var elMaterialMappingScaleY = document.getElementById("property-material-mapping-scale-y"); - var elMaterialMappingRot = document.getElementById("property-material-mapping-rot"); - var elMaterialData = document.getElementById("property-material-data"); - var elClearMaterialData = document.getElementById("materialdata-clear"); - var elSaveMaterialData = document.getElementById("materialdata-save"); - var elNewJSONMaterialEditor = document.getElementById('materialdata-new-editor'); - - var elImageURL = document.getElementById("property-image-url"); - - var elWebSourceURL = document.getElementById("property-web-source-url"); - var elWebDPI = document.getElementById("property-web-dpi"); - - var elDescription = document.getElementById("property-description"); - - var elHyperlinkHref = document.getElementById("property-hyperlink-href"); - - var elTextText = document.getElementById("property-text-text"); - var elTextLineHeight = document.getElementById("property-text-line-height"); - var elTextTextColor = document.getElementById("property-text-text-color"); - var elTextFaceCamera = document.getElementById("property-text-face-camera"); - var elTextTextColorRed = document.getElementById("property-text-text-color-red"); - var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); - var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); - var elTextBackgroundColor = document.getElementById("property-text-background-color"); - var elTextBackgroundColorRed = document.getElementById("property-text-background-color-red"); - var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); - var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); - - // Key light - var elZoneKeyLightModeInherit = document.getElementById("property-zone-key-light-mode-inherit"); - var elZoneKeyLightModeDisabled = document.getElementById("property-zone-key-light-mode-disabled"); - var elZoneKeyLightModeEnabled = document.getElementById("property-zone-key-light-mode-enabled"); - - var elZoneKeyLightColor = document.getElementById("property-zone-key-light-color"); - var elZoneKeyLightColorRed = document.getElementById("property-zone-key-light-color-red"); - var elZoneKeyLightColorGreen = document.getElementById("property-zone-key-light-color-green"); - var elZoneKeyLightColorBlue = document.getElementById("property-zone-key-light-color-blue"); - var elZoneKeyLightIntensity = document.getElementById("property-zone-key-intensity"); - var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); - var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); - - var elZoneKeyLightCastShadows = document.getElementById("property-zone-key-light-cast-shadows"); - - // Skybox - var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); - var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); - var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); - - // Ambient light - var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); - - var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); - var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); - var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); - - var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); - var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); - - // Haze - var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); - var elZoneHazeModeDisabled = document.getElementById("property-zone-haze-mode-disabled"); - var elZoneHazeModeEnabled = document.getElementById("property-zone-haze-mode-enabled"); - - var elZoneHazeRange = document.getElementById("property-zone-haze-range"); - var elZoneHazeColor = document.getElementById("property-zone-haze-color"); - var elZoneHazeColorRed = document.getElementById("property-zone-haze-color-red"); - var elZoneHazeColorGreen = document.getElementById("property-zone-haze-color-green"); - var elZoneHazeColorBlue = document.getElementById("property-zone-haze-color-blue"); - var elZoneHazeGlareColor = document.getElementById("property-zone-haze-glare-color"); - var elZoneHazeGlareColorRed = document.getElementById("property-zone-haze-glare-color-red"); - var elZoneHazeGlareColorGreen = document.getElementById("property-zone-haze-glare-color-green"); - var elZoneHazeGlareColorBlue = document.getElementById("property-zone-haze-glare-color-blue"); - var elZoneHazeEnableGlare = document.getElementById("property-zone-haze-enable-light-blend"); - var elZoneHazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); - - var elZoneHazeAltitudeEffect = document.getElementById("property-zone-haze-altitude-effect"); - var elZoneHazeBaseRef = document.getElementById("property-zone-haze-base"); - var elZoneHazeCeiling = document.getElementById("property-zone-haze-ceiling"); - - var elZoneHazeBackgroundBlend = document.getElementById("property-zone-haze-background-blend"); - - // Bloom - var elZoneBloomModeInherit = document.getElementById("property-zone-bloom-mode-inherit"); - var elZoneBloomModeDisabled = document.getElementById("property-zone-bloom-mode-disabled"); - var elZoneBloomModeEnabled = document.getElementById("property-zone-bloom-mode-enabled"); - - var elZoneBloomIntensity = document.getElementById("property-zone-bloom-intensity"); - var elZoneBloomThreshold = document.getElementById("property-zone-bloom-threshold"); - var elZoneBloomSize = document.getElementById("property-zone-bloom-size"); - - var elZoneSkyboxColor = document.getElementById("property-zone-skybox-color"); - var elZoneSkyboxColorRed = document.getElementById("property-zone-skybox-color-red"); - var elZoneSkyboxColorGreen = document.getElementById("property-zone-skybox-color-green"); - var elZoneSkyboxColorBlue = document.getElementById("property-zone-skybox-color-blue"); - var elZoneSkyboxURL = document.getElementById("property-zone-skybox-url"); - - var elZoneFlyingAllowed = document.getElementById("property-zone-flying-allowed"); - var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); - var elZoneFilterURL = document.getElementById("property-zone-filter-url"); - - var elVoxelVolumeSizeX = document.getElementById("property-voxel-volume-size-x"); - var elVoxelVolumeSizeY = document.getElementById("property-voxel-volume-size-y"); - var elVoxelVolumeSizeZ = document.getElementById("property-voxel-volume-size-z"); - var elVoxelSurfaceStyle = document.getElementById("property-voxel-surface-style"); - var elXTextureURL = document.getElementById("property-x-texture-url"); - var elYTextureURL = document.getElementById("property-y-texture-url"); - var elZTextureURL = document.getElementById("property-z-texture-url"); - - if (window.EventBridge !== undefined) { + elProperty.appendChild(elSpan); + elProperty.appendChild(elLabel); + elPropertyElements[propertyName] = [ elSpan, elLabel ]; + break; + } + case 'buttons': { + elProperty.setAttribute("class", "property Text"); + + let hasLabel = property.label !== undefined; + if (hasLabel) { + elProperty.appendChild(elLabel); + } + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, hasLabel); + } + + elPropertyElements[propertyName] = elProperty; + break; + } + case 'sub-header': { + if (propertyName !== undefined) { + elPropertyElements[propertyName] = elProperty; + } + break; + } + } + + let showPropertyRule = property.showPropertyRule; + if (showPropertyRule !== undefined) { + let dependentProperty = Object.keys(showPropertyRule)[0]; + let dependentPropertyValue = showPropertyRule[dependentProperty]; + if (!showPropertyRules[dependentProperty]) { + showPropertyRules[dependentProperty] = []; + } + showPropertyRules[dependentProperty][propertyName] = dependentPropertyValue; + } + }); + + elGroups[group.id] = elGroup; + }); + + if (window.EventBridge !== undefined) { var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type === "server_script_status") { + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); elServerScriptError.value = data.errorInfo; // If we just set elServerScriptError's diplay to block or none, we still end up with // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. @@ -913,218 +2114,33 @@ function loaded() { elServerScriptStatus.innerText = "Not running"; } } else if (data.type === "update" && data.selections) { - if (data.selections.length === 0) { if (lastEntityID !== null) { if (editor !== null) { - saveJSONUserData(true); + saveUserData(); deleteJSONEditor(); } if (materialEditor !== null) { - saveJSONMaterialData(true); + saveMaterialData(); deleteJSONMaterialEditor(); } } - - elTypeIcon.style.display = "none"; - elType.innerHTML = "No selection"; - elPropertiesList.className = ''; - - elID.value = ""; - elName.value = ""; - elLocked.checked = false; - elVisible.checked = false; - - elParentID.value = ""; - elParentJointIndex.value = ""; - - elColorRed.value = ""; - elColorGreen.value = ""; - elColorBlue.value = ""; - elColorControlVariant2.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - - elPositionX.value = ""; - elPositionY.value = ""; - elPositionZ.value = ""; - - elRotationX.value = ""; - elRotationY.value = ""; - elRotationZ.value = ""; - - elDimensionsX.value = ""; - elDimensionsY.value = ""; - elDimensionsZ.value = ""; - - elRegistrationX.value = ""; - elRegistrationY.value = ""; - elRegistrationZ.value = ""; - - elLinearVelocityX.value = ""; - elLinearVelocityY.value = ""; - elLinearVelocityZ.value = ""; - elLinearDamping.value = ""; - - elAngularVelocityX.value = ""; - elAngularVelocityY.value = ""; - elAngularVelocityZ.value = ""; - elAngularDamping.value = ""; - - elGravityX.value = ""; - elGravityY.value = ""; - elGravityZ.value = ""; - - elAccelerationX.value = ""; - elAccelerationY.value = ""; - elAccelerationZ.value = ""; - - elRestitution.value = ""; - elFriction.value = ""; - elDensity.value = ""; - - elCollisionless.checked = false; - elDynamic.checked = false; - - elCollideStatic.checked = false; - elCollideKinematic.checked = false; - elCollideDynamic.checked = false; - elCollideMyAvatar.checked = false; - elCollideOtherAvatar.checked = false; - - elGrabbable.checked = false; - elTriggerable.checked = false; - elIgnoreIK.checked = false; - - elCloneable.checked = false; - elCloneableDynamic.checked = false; - elCloneableAvatarEntity.checked = false; - elCloneableGroup.style.display = "none"; - elCloneableLimit.value = ""; - elCloneableLifetime.value = ""; - - showElements(document.getElementsByClassName('can-cast-shadow-section'), true); - elCanCastShadow.checked = false; - - elCollisionSoundURL.value = ""; - elLifetime.value = ""; - elScriptURL.value = ""; - elServerScripts.value = ""; - elHyperlinkHref.value = ""; - elDescription.value = ""; - - deleteJSONEditor(); - elUserData.value = ""; + + elPropertyElements["type"][0].style.display = "none"; + elPropertyElements["type"][1].innerHTML = NO_SELECTION; + elPropertiesList.className = ''; + showGroupsForType("None"); + + resetProperties(); + + deleteJSONEditor(); + elPropertyElements["userData"].value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); - - // Shape Properties - elShape.value = "Cube"; - setDropdownText(elShape); - - // Light Properties - elLightSpotLight.checked = false; - elLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elLightColorRed.value = ""; - elLightColorGreen.value = ""; - elLightColorBlue.value = ""; - elLightIntensity.value = ""; - elLightFalloffRadius.value = ""; - elLightExponent.value = ""; - elLightCutoff.value = ""; - - // Model Properties - elModelURL.value = ""; - elCompoundShapeURL.value = ""; - elShapeType.value = "none"; - setDropdownText(elShapeType); - elModelAnimationURL.value = "" - elModelAnimationPlaying.checked = false; - elModelAnimationFPS.value = ""; - elModelAnimationFrame.value = ""; - elModelAnimationFirstFrame.value = ""; - elModelAnimationLastFrame.value = ""; - elModelAnimationLoop.checked = false; - elModelAnimationHold.checked = false; - elModelAnimationAllowTranslation.checked = false; - elModelTextures.value = ""; - elModelOriginalTextures.value = ""; - - // Zone Properties - elZoneFlyingAllowed.checked = false; - elZoneGhostingAllowed.checked = false; - elZoneFilterURL.value = ""; - elZoneKeyLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneKeyLightColorRed.value = ""; - elZoneKeyLightColorGreen.value = ""; - elZoneKeyLightColorBlue.value = ""; - elZoneKeyLightIntensity.value = ""; - elZoneKeyLightDirectionX.value = ""; - elZoneKeyLightDirectionY.value = ""; - elZoneKeyLightCastShadows.checked = false; - elZoneAmbientLightIntensity.value = ""; - elZoneAmbientLightURL.value = ""; - elZoneHazeRange.value = ""; - elZoneHazeColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneHazeColorRed.value = ""; - elZoneHazeColorGreen.value = ""; - elZoneHazeColorBlue.value = ""; - elZoneHazeBackgroundBlend.value = 0; - elZoneHazeGlareColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneHazeGlareColorRed.value = ""; - elZoneHazeGlareColorGreen.value = ""; - elZoneHazeGlareColorBlue.value = ""; - elZoneHazeEnableGlare.checked = false; - elZoneHazeGlareAngle.value = ""; - elZoneHazeAltitudeEffect.checked = false; - elZoneHazeBaseRef.value = ""; - elZoneHazeCeiling.value = ""; - elZoneBloomIntensity.value = ""; - elZoneBloomThreshold.value = ""; - elZoneBloomSize.value = ""; - elZoneSkyboxColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneSkyboxColorRed.value = ""; - elZoneSkyboxColorGreen.value = ""; - elZoneSkyboxColorBlue.value = ""; - elZoneSkyboxURL.value = ""; - showElements(document.getElementsByClassName('keylight-section'), true); - showElements(document.getElementsByClassName('skybox-section'), true); - showElements(document.getElementsByClassName('ambient-section'), true); - showElements(document.getElementsByClassName('haze-section'), true); - showElements(document.getElementsByClassName('bloom-section'), true); - - // Text Properties - elTextText.value = ""; - elTextLineHeight.value = ""; - elTextFaceCamera.checked = false; - elTextTextColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elTextTextColorRed.value = ""; - elTextTextColorGreen.value = ""; - elTextTextColorBlue.value = ""; - elTextBackgroundColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elTextBackgroundColorRed.value = ""; - elTextBackgroundColorGreen.value = ""; - elTextBackgroundColorBlue.value = ""; - - // Image Properties - elImageURL.value = ""; - - // Web Properties - elWebSourceURL.value = ""; - elWebDPI.value = ""; - - // Material Properties - elMaterialURL.value = ""; - elParentMaterialNameNumber.value = ""; - elParentMaterialNameCheckbox.checked = false; - elPriority.value = ""; - elMaterialMappingPosX.value = ""; - elMaterialMappingPosY.value = ""; - elMaterialMappingScaleX.value = ""; - elMaterialMappingScaleY.value = ""; - elMaterialMappingRot.value = ""; - - deleteJSONMaterialEditor(); - elMaterialData.value = ""; + + deleteJSONMaterialEditor(); + elPropertyElements["materialData"].value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -1133,15 +2149,16 @@ function loaded() { } else if (data.selections.length > 1) { deleteJSONEditor(); deleteJSONMaterialEditor(); - var selections = data.selections; + + let selections = data.selections; - var ids = []; - var types = {}; - var numTypes = 0; + let ids = []; + let types = {}; + let numTypes = 0; - for (var i = 0; i < selections.length; i++) { + for (let i = 0; i < selections.length; i++) { ids.push(selections[i].id); - var currentSelectedType = selections[i].properties.type; + let currentSelectedType = selections[i].properties.type; if (types[currentSelectedType] === undefined) { types[currentSelectedType] = 0; numTypes += 1; @@ -1149,400 +2166,188 @@ function loaded() { types[currentSelectedType]++; } - var type = "Multiple"; + let type = "Multiple"; if (numTypes === 1) { type = selections[0].properties.type; } - - elType.innerHTML = type + " (" + data.selections.length + ")"; - elTypeIcon.innerHTML = ICON_FOR_TYPE[type]; - elTypeIcon.style.display = "inline-block"; - elPropertiesList.className = ''; - - elID.value = ""; - - disableProperties(); + + elPropertyElements["type"][0].innerHTML = ICON_FOR_TYPE[type]; + elPropertyElements["type"][0].style.display = "inline-block"; + elPropertyElements["type"][1].innerHTML = type + " (" + data.selections.length + ")"; + elPropertiesList.className = ''; + showGroupsForType(type); + + resetProperties(); + disableProperties(); } else { - properties = data.selections[0].properties; - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + + if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { if (editor !== null) { - saveJSONUserData(true); + saveUserData(); } if (materialEditor !== null) { - saveJSONMaterialData(true); + saveMaterialData(); } } - var doSelectElement = lastEntityID === '"' + properties.id + '"'; + let doSelectElement = lastEntityID === '"' + properties.id + '"'; // the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + properties.id + '"'; - elID.value = properties.id; // HTML workaround since image is not yet a separate entity type - var IMAGE_MODEL_NAME = 'default-image-model.fbx'; + let IMAGE_MODEL_NAME = 'default-image-model.fbx'; if (properties.type === "Model") { - var urlParts = properties.modelURL.split('/'); - var propsFilename = urlParts[urlParts.length - 1]; + let urlParts = properties.modelURL.split('/'); + let propsFilename = urlParts[urlParts.length - 1]; if (propsFilename === IMAGE_MODEL_NAME) { properties.type = "Image"; } } - + // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; + showGroupsForType(properties.type); + + for (let propertyName in elPropertyElements) { + let elProperty = elPropertyElements[propertyName]; + + let propertyValue; + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + if (properties[propertyGroupName] === undefined || properties[propertyGroupName][subPropertyName] === undefined) { + continue; + } + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + propertyValue = properties[propertyGroupName][subPropertyName][subSubPropertyName]; + } else { + propertyValue = properties[propertyGroupName][subPropertyName]; + } + } else { + propertyValue = properties[propertyName]; + } - elType.innerHTML = properties.type; - elTypeIcon.innerHTML = ICON_FOR_TYPE[properties.type]; - elTypeIcon.style.display = "inline-block"; - - elLocked.checked = properties.locked; - - elName.value = properties.name; - - elVisible.checked = properties.visible; - - elPositionX.value = properties.position.x.toFixed(4); - elPositionY.value = properties.position.y.toFixed(4); - elPositionZ.value = properties.position.z.toFixed(4); - - elDimensionsX.value = properties.dimensions.x.toFixed(4); - elDimensionsY.value = properties.dimensions.y.toFixed(4); - elDimensionsZ.value = properties.dimensions.z.toFixed(4); - - elParentID.value = properties.parentID; - elParentJointIndex.value = properties.parentJointIndex; - - elRegistrationX.value = properties.registrationPoint.x.toFixed(4); - elRegistrationY.value = properties.registrationPoint.y.toFixed(4); - elRegistrationZ.value = properties.registrationPoint.z.toFixed(4); - - elRotationX.value = properties.rotation.x.toFixed(4); - elRotationY.value = properties.rotation.y.toFixed(4); - elRotationZ.value = properties.rotation.z.toFixed(4); - - elLinearVelocityX.value = properties.velocity.x.toFixed(4); - elLinearVelocityY.value = properties.velocity.y.toFixed(4); - elLinearVelocityZ.value = properties.velocity.z.toFixed(4); - elLinearDamping.value = properties.damping.toFixed(2); - - elAngularVelocityX.value = (properties.angularVelocity.x * RADIANS_TO_DEGREES).toFixed(4); - elAngularVelocityY.value = (properties.angularVelocity.y * RADIANS_TO_DEGREES).toFixed(4); - elAngularVelocityZ.value = (properties.angularVelocity.z * RADIANS_TO_DEGREES).toFixed(4); - elAngularDamping.value = properties.angularDamping.toFixed(4); - - elRestitution.value = properties.restitution.toFixed(4); - elFriction.value = properties.friction.toFixed(4); - - elGravityX.value = properties.gravity.x.toFixed(4); - elGravityY.value = properties.gravity.y.toFixed(4); - elGravityZ.value = properties.gravity.z.toFixed(4); - - elAccelerationX.value = properties.acceleration.x.toFixed(4); - elAccelerationY.value = properties.acceleration.y.toFixed(4); - elAccelerationZ.value = properties.acceleration.z.toFixed(4); - - elDensity.value = properties.density.toFixed(4); - elCollisionless.checked = properties.collisionless; - elDynamic.checked = properties.dynamic; - - elCollideStatic.checked = properties.collidesWith.indexOf("static") > -1; - elCollideKinematic.checked = properties.collidesWith.indexOf("kinematic") > -1; - elCollideDynamic.checked = properties.collidesWith.indexOf("dynamic") > -1; - elCollideMyAvatar.checked = properties.collidesWith.indexOf("myAvatar") > -1; - elCollideOtherAvatar.checked = properties.collidesWith.indexOf("otherAvatar") > -1; - - elGrabbable.checked = properties.dynamic; - - elTriggerable.checked = false; - elIgnoreIK.checked = true; - - elCloneable.checked = properties.cloneable; - elCloneableDynamic.checked = properties.cloneDynamic; - elCloneableAvatarEntity.checked = properties.cloneAvatarEntity; - elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; - elCloneableLimit.value = properties.cloneLimit; - elCloneableLifetime.value = properties.cloneLifetime; - - var grabbablesSet = false; - var parsedUserData = {}; - try { - parsedUserData = JSON.parse(properties.userData); - - if ("grabbableKey" in parsedUserData) { - grabbablesSet = true; - var grabbableData = parsedUserData.grabbableKey; - if ("grabbable" in grabbableData) { - elGrabbable.checked = grabbableData.grabbable; - } else { - elGrabbable.checked = true; - } - if ("triggerable" in grabbableData) { - elTriggerable.checked = grabbableData.triggerable; - } else if ("wantsTrigger" in grabbableData) { - elTriggerable.checked = grabbableData.wantsTrigger; - } else { - elTriggerable.checked = false; - } - if ("ignoreIK" in grabbableData) { - elIgnoreIK.checked = grabbableData.ignoreIK; - } else { - elIgnoreIK.checked = true; - } - } - } catch (e) { - // TODO: What should go here? - } - if (!grabbablesSet) { - elGrabbable.checked = true; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - elCloneable.checked = false; - } - - elCollisionSoundURL.value = properties.collisionSoundURL; - elLifetime.value = properties.lifetime; - elScriptURL.value = properties.script; - elScriptTimestamp.value = properties.scriptTimestamp; - elServerScripts.value = properties.serverScripts; - - var json = null; - try { - json = JSON.parse(properties.userData); - } catch (e) { - // normal text - deleteJSONEditor(); - elUserData.value = properties.userData; - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - } - if (json !== null) { - if (editor === null) { - createJSONEditor(); - } - - setEditorJSON(json); - showSaveUserDataButton(); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - } - - var materialJson = null; - try { - materialJson = JSON.parse(properties.materialData); - } catch (e) { - // normal text - deleteJSONMaterialEditor(); - elMaterialData.value = properties.materialData; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - } - if (materialJson !== null) { - if (materialEditor === null) { - createJSONMaterialEditor(); - } - - setMaterialEditorJSON(materialJson); - showSaveMaterialDataButton(); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - } - - elHyperlinkHref.value = properties.href; - elDescription.value = properties.description; - - - if (properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - elShape.value = properties.shape; - setDropdownText(elShape); - } - - if (properties.type === "Shape" || properties.type === "Box" || - properties.type === "Sphere" || properties.type === "ParticleEffect") { - elColorRed.value = properties.color.red; - elColorGreen.value = properties.color.green; - elColorBlue.value = properties.color.blue; - elColorControlVariant2.style.backgroundColor = "rgb(" + properties.color.red + "," + - properties.color.green + "," + properties.color.blue + ")"; - } - - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - - elCanCastShadow.checked = properties.canCastShadow; - } - - if (properties.type === "Model") { - elModelURL.value = properties.modelURL; - elShapeType.value = properties.shapeType; - setDropdownText(elShapeType); - elCompoundShapeURL.value = properties.compoundShapeURL; - elModelAnimationURL.value = properties.animation.url; - elModelAnimationPlaying.checked = properties.animation.running; - elModelAnimationFPS.value = properties.animation.fps; - elModelAnimationFrame.value = properties.animation.currentFrame; - elModelAnimationFirstFrame.value = properties.animation.firstFrame; - elModelAnimationLastFrame.value = properties.animation.lastFrame; - elModelAnimationLoop.checked = properties.animation.loop; - elModelAnimationHold.checked = properties.animation.hold; - elModelAnimationAllowTranslation.checked = properties.animation.allowTranslation; - elModelTextures.value = properties.textures; - setTextareaScrolling(elModelTextures); - elModelOriginalTextures.value = properties.originalTextures; - setTextareaScrolling(elModelOriginalTextures); - } else if (properties.type === "Web") { - elWebSourceURL.value = properties.sourceUrl; - elWebDPI.value = properties.dpi; - } else if (properties.type === "Image") { + // workaround for shape Color & Light Color property fields sharing same property value "color" + if (propertyValue === undefined && propertyName === "lightColor") { + propertyValue = properties["color"] + } + + let isSubProperty = subProperties.indexOf(propertyName) > -1; + if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { + continue; + } + + if (elProperty instanceof Array) { + if (elProperty[1].getAttribute("type") === "number") { // vectors + if (elProperty.length === 2 || elProperty.length === 3) { + // vec2/vec3 are array of 2/3 input numbers + elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); + elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + if (elProperty[2] !== undefined) { + elProperty[2].value = (propertyValue.z * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + } + } else if (elProperty.length === 4) { + // color is array of color picker and 3 input numbers + elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; + elProperty[1].value = propertyValue.red; + elProperty[2].value = propertyValue.green; + elProperty[3].value = propertyValue.blue; + } + } else if (elProperty[0].nodeName === "SPAN") { // icons + // icon is array of elSpan (icon glyph) and elLabel + elProperty[0].innerHTML = icons[propertyName][propertyValue]; + elProperty[0].style.display = "inline-block"; + elProperty[1].innerHTML = propertyValue; //+ " (" + data.selections.length + ")"; + } + } else if (elProperty.getAttribute("subPropertyOf")) { + let propertyValue = properties[elProperty.getAttribute("subPropertyOf")]; + let subProperties = propertyValue.split(","); + elProperty.checked = subProperties.indexOf(propertyName) > -1; + } else if (elProperty.getAttribute("type") === "number") { + let fixedDecimals = elProperty.getAttribute("fixedDecimals"); + elProperty.value = propertyValue.toFixed(fixedDecimals); + } else if (elProperty.getAttribute("type") === "checkbox") { + let inverse = elProperty.getAttribute("inverse") === "true"; + elProperty.checked = inverse ? !propertyValue : propertyValue; + } else if (elProperty.nodeName === "TEXTAREA") { + elProperty.value = propertyValue; + setTextareaScrolling(elProperty); + } else if (elProperty.nodeName === "SELECT") { // dropdown + elProperty.value = propertyValue; + } else { + elProperty.value = propertyValue; + } + + let propertyShowRules = showPropertyRules[propertyName]; + if (propertyShowRules !== undefined) { + for (let propertyToShow in propertyShowRules) { + let showIfThisPropertyValue = propertyShowRules[propertyToShow]; + let show = String(propertyValue) === String(showIfThisPropertyValue); + let elPropertyToShow = elPropertyElements[propertyToShow]; + if (elPropertyToShow) { + let parentNode = elPropertyToShow.parentNode; + if (parentNode === undefined && elPropertyToShow instanceof Array) { + parentNode = elPropertyToShow[0].parentNode; + } + parentNode.style.display = show ? "block" : "none"; + } + } + } + } + + let elGrabbable = elPropertyElements["grabbable"]; + let elTriggerable = elPropertyElements["triggerable"]; + let elIgnoreIK = elPropertyElements["ignoreIK"]; + elGrabbable.checked = elPropertyElements["dynamic"].checked; + elTriggerable.checked = false; + elIgnoreIK.checked = true; + let grabbablesSet = false; + let parsedUserData = {}; + try { + parsedUserData = JSON.parse(properties.userData); + if ("grabbableKey" in parsedUserData) { + grabbablesSet = true; + let grabbableData = parsedUserData.grabbableKey; + if ("grabbable" in grabbableData) { + elGrabbable.checked = grabbableData.grabbable; + } else { + elGrabbable.checked = true; + } + if ("triggerable" in grabbableData) { + elTriggerable.checked = grabbableData.triggerable; + } else if ("wantsTrigger" in grabbableData) { + elTriggerable.checked = grabbableData.wantsTrigger; + } else { + elTriggerable.checked = false; + } + if ("ignoreIK" in grabbableData) { + elIgnoreIK.checked = grabbableData.ignoreIK; + } else { + elIgnoreIK.checked = true; + } + } + } catch (e) { + // TODO: What should go here? + } + if (!grabbablesSet) { + elGrabbable.checked = true; + elTriggerable.checked = false; + elIgnoreIK.checked = true; + } + + if (properties.type === "Image") { var imageLink = JSON.parse(properties.textures)["tex.picture"]; - elImageURL.value = imageLink; - } else if (properties.type === "Text") { - elTextText.value = properties.text; - elTextLineHeight.value = properties.lineHeight.toFixed(4); - elTextFaceCamera.checked = properties.faceCamera; - elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + - properties.textColor.green + "," + - properties.textColor.blue + ")"; - elTextTextColorRed.value = properties.textColor.red; - elTextTextColorGreen.value = properties.textColor.green; - elTextTextColorBlue.value = properties.textColor.blue; - elTextBackgroundColor.style.backgroundColor = "rgb(" + properties.backgroundColor.red + "," + - properties.backgroundColor.green + "," + - properties.backgroundColor.blue + ")"; - elTextBackgroundColorRed.value = properties.backgroundColor.red; - elTextBackgroundColorGreen.value = properties.backgroundColor.green; - elTextBackgroundColorBlue.value = properties.backgroundColor.blue; - } else if (properties.type === "Light") { - elLightSpotLight.checked = properties.isSpotlight; - - elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + - properties.color.green + "," + properties.color.blue + ")"; - elLightColorRed.value = properties.color.red; - elLightColorGreen.value = properties.color.green; - elLightColorBlue.value = properties.color.blue; - - elLightIntensity.value = properties.intensity.toFixed(1); - elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); - elLightExponent.value = properties.exponent.toFixed(2); - elLightCutoff.value = properties.cutoff.toFixed(2); - } else if (properties.type === "Zone") { - // Key light - elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); - elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); - elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); - - elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + - properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; - elZoneKeyLightColorRed.value = properties.keyLight.color.red; - elZoneKeyLightColorGreen.value = properties.keyLight.color.green; - elZoneKeyLightColorBlue.value = properties.keyLight.color.blue; - elZoneKeyLightIntensity.value = properties.keyLight.intensity.toFixed(2); - elZoneKeyLightDirectionX.value = properties.keyLight.direction.x.toFixed(2); - elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); - - elZoneKeyLightCastShadows.checked = properties.keyLight.castShadows; - - // Skybox - elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); - elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); - elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); - - // Ambient light - elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); - elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); - elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); - - elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); - elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; - - // Haze - elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); - elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); - - elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); - elZoneHazeColor.style.backgroundColor = "rgb(" + - properties.haze.hazeColor.red + "," + - properties.haze.hazeColor.green + "," + - properties.haze.hazeColor.blue + ")"; - - elZoneHazeColorRed.value = properties.haze.hazeColor.red; - elZoneHazeColorGreen.value = properties.haze.hazeColor.green; - elZoneHazeColorBlue.value = properties.haze.hazeColor.blue; - elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); - - elZoneHazeGlareColor.style.backgroundColor = "rgb(" + - properties.haze.hazeGlareColor.red + "," + - properties.haze.hazeGlareColor.green + "," + - properties.haze.hazeGlareColor.blue + ")"; - - elZoneHazeGlareColorRed.value = properties.haze.hazeGlareColor.red; - elZoneHazeGlareColorGreen.value = properties.haze.hazeGlareColor.green; - elZoneHazeGlareColorBlue.value = properties.haze.hazeGlareColor.blue; - - elZoneHazeEnableGlare.checked = properties.haze.hazeEnableGlare; - elZoneHazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); - - elZoneHazeAltitudeEffect.checked = properties.haze.hazeAltitudeEffect; - elZoneHazeBaseRef.value = properties.haze.hazeBaseRef.toFixed(0); - elZoneHazeCeiling.value = properties.haze.hazeCeiling.toFixed(0); - - elZoneBloomModeInherit.checked = (properties.bloomMode === 'inherit'); - elZoneBloomModeDisabled.checked = (properties.bloomMode === 'disabled'); - elZoneBloomModeEnabled.checked = (properties.bloomMode === 'enabled'); - - elZoneBloomIntensity.value = properties.bloom.bloomIntensity.toFixed(2); - elZoneBloomThreshold.value = properties.bloom.bloomThreshold.toFixed(2); - elZoneBloomSize.value = properties.bloom.bloomSize.toFixed(2); - - elShapeType.value = properties.shapeType; - elCompoundShapeURL.value = properties.compoundShapeURL; - - elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + - properties.skybox.color.green + "," + properties.skybox.color.blue + ")"; - elZoneSkyboxColorRed.value = properties.skybox.color.red; - elZoneSkyboxColorGreen.value = properties.skybox.color.green; - elZoneSkyboxColorBlue.value = properties.skybox.color.blue; - elZoneSkyboxURL.value = properties.skybox.url; - - elZoneFlyingAllowed.checked = properties.flyingAllowed; - elZoneGhostingAllowed.checked = properties.ghostingAllowed; - elZoneFilterURL.value = properties.filterURL; - - // Show/hide sections as required - showElements(document.getElementsByClassName('skybox-section'), - elZoneSkyboxModeEnabled.checked); - - showElements(document.getElementsByClassName('keylight-section'), - elZoneKeyLightModeEnabled.checked); - - showElements(document.getElementsByClassName('ambient-section'), - elZoneAmbientLightModeEnabled.checked); - - showElements(document.getElementsByClassName('haze-section'), - elZoneHazeModeEnabled.checked); - - showElements(document.getElementsByClassName('bloom-section'), - elZoneBloomModeEnabled.checked); - } else if (properties.type === "PolyVox") { - elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); - elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); - elVoxelVolumeSizeZ.value = properties.voxelVolumeSize.z.toFixed(2); - elVoxelSurfaceStyle.value = properties.voxelSurfaceStyle; - setDropdownText(elVoxelSurfaceStyle); - elXTextureURL.value = properties.xTextureURL; - elYTextureURL.value = properties.yTextureURL; - elZTextureURL.value = properties.zTextureURL; - } else if (properties.type === "Material") { - elMaterialURL.value = properties.materialURL; - //elMaterialMappingMode.value = properties.materialMappingMode; - //setDropdownText(elMaterialMappingMode); - elPriority.value = properties.priority; - if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { + elPropertyElements["image"].value = imageLink; + } else if (properties.type === "Material") { + let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; + let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; + let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; + if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { elParentMaterialNameString.value = properties.parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = false; @@ -1551,32 +2356,64 @@ function loaded() { showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = true; } - elMaterialMappingPosX.value = properties.materialMappingPos.x.toFixed(4); - elMaterialMappingPosY.value = properties.materialMappingPos.y.toFixed(4); - elMaterialMappingScaleX.value = properties.materialMappingScale.x.toFixed(4); - elMaterialMappingScaleY.value = properties.materialMappingScale.y.toFixed(4); - elMaterialMappingRot.value = properties.materialMappingRot.toFixed(2); + } + + let json = null; + try { + json = JSON.parse(properties.userData); + } catch (e) { + // normal text + deleteJSONEditor(); + elPropertyElements["userData"].value = properties.userData; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + hideUserDataSaved(); + } + if (json !== null) { + if (editor === null) { + createJSONEditor(); + } + setEditorJSON(json); + showSaveUserDataButton(); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + hideUserDataSaved(); } - // Only these types can cast a shadow - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - - showElements(document.getElementsByClassName('can-cast-shadow-section'), true); - } else { - showElements(document.getElementsByClassName('can-cast-shadow-section'), false); + let materialJson = null; + try { + materialJson = JSON.parse(properties.materialData); + } catch (e) { + // normal text + deleteJSONMaterialEditor(); + elPropertyElements["materialData"].value = properties.materialData; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + hideMaterialDataSaved(); } - - if (properties.locked) { - disableProperties(); - elLocked.removeAttribute('disabled'); - } else { - enableProperties(); - elSaveUserData.disabled = true; - elSaveMaterialData.disabled = true; + if (materialJson !== null) { + if (materialEditor === null) { + createJSONMaterialEditor(); + } + setMaterialEditorJSON(materialJson); + showSaveMaterialDataButton(); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + hideMaterialDataSaved(); } - - var activeElement = document.activeElement; + + if (properties.locked) { + disableProperties(); + elPropertyElements["locked"].removeAttribute('disabled'); + } else { + enableProperties(); + disableSaveUserDataButton(); + disableSaveMaterialDataButton() + } + + var activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); } @@ -1584,270 +2421,79 @@ function loaded() { } }); } - - elLocked.addEventListener('change', createEmitCheckedPropertyUpdateFunction('locked')); - elName.addEventListener('change', createEmitTextPropertyUpdateFunction('name')); - elHyperlinkHref.addEventListener('change', createEmitTextPropertyUpdateFunction('href')); - elDescription.addEventListener('change', createEmitTextPropertyUpdateFunction('description')); - elVisible.addEventListener('change', createEmitCheckedPropertyUpdateFunction('visible')); - - var positionChangeFunction = createEmitVec3PropertyUpdateFunction( - 'position', elPositionX, elPositionY, elPositionZ); - elPositionX.addEventListener('change', positionChangeFunction); - elPositionY.addEventListener('change', positionChangeFunction); - elPositionZ.addEventListener('change', positionChangeFunction); - - var dimensionsChangeFunction = createEmitVec3PropertyUpdateFunction( - 'dimensions', elDimensionsX, elDimensionsY, elDimensionsZ); - elDimensionsX.addEventListener('change', dimensionsChangeFunction); - elDimensionsY.addEventListener('change', dimensionsChangeFunction); - elDimensionsZ.addEventListener('change', dimensionsChangeFunction); - - elParentID.addEventListener('change', createEmitTextPropertyUpdateFunction('parentID')); - elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex', 0)); - - var registrationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'registrationPoint', elRegistrationX, elRegistrationY, elRegistrationZ); - elRegistrationX.addEventListener('change', registrationChangeFunction); - elRegistrationY.addEventListener('change', registrationChangeFunction); - elRegistrationZ.addEventListener('change', registrationChangeFunction); - - var rotationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'rotation', elRotationX, elRotationY, elRotationZ); - elRotationX.addEventListener('change', rotationChangeFunction); - elRotationY.addEventListener('change', rotationChangeFunction); - elRotationZ.addEventListener('change', rotationChangeFunction); - - var velocityChangeFunction = createEmitVec3PropertyUpdateFunction( - 'velocity', elLinearVelocityX, elLinearVelocityY, elLinearVelocityZ); - elLinearVelocityX.addEventListener('change', velocityChangeFunction); - elLinearVelocityY.addEventListener('change', velocityChangeFunction); - elLinearVelocityZ.addEventListener('change', velocityChangeFunction); - elLinearDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('damping')); - - var angularVelocityChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier( - 'angularVelocity', elAngularVelocityX, elAngularVelocityY, elAngularVelocityZ, DEGREES_TO_RADIANS); - elAngularVelocityX.addEventListener('change', angularVelocityChangeFunction); - elAngularVelocityY.addEventListener('change', angularVelocityChangeFunction); - elAngularVelocityZ.addEventListener('change', angularVelocityChangeFunction); - elAngularDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('angularDamping')); - - elRestitution.addEventListener('change', createEmitNumberPropertyUpdateFunction('restitution')); - elFriction.addEventListener('change', createEmitNumberPropertyUpdateFunction('friction')); - - var gravityChangeFunction = createEmitVec3PropertyUpdateFunction( - 'gravity', elGravityX, elGravityY, elGravityZ); - elGravityX.addEventListener('change', gravityChangeFunction); - elGravityY.addEventListener('change', gravityChangeFunction); - elGravityZ.addEventListener('change', gravityChangeFunction); - - var accelerationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'acceleration', elAccelerationX, elAccelerationY, elAccelerationZ); - elAccelerationX.addEventListener('change', accelerationChangeFunction); - elAccelerationY.addEventListener('change', accelerationChangeFunction); - elAccelerationZ.addEventListener('change', accelerationChangeFunction); - - elDensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('density')); - elCollisionless.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionless')); - elDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('dynamic')); - - elCollideDynamic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideDynamic, 'dynamic'); - }); - - elCollideKinematic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideKinematic, 'kinematic'); - }); - - elCollideStatic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideStatic, 'static'); - }); - elCollideMyAvatar.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideMyAvatar, 'myAvatar'); - }); - elCollideOtherAvatar.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideOtherAvatar, 'otherAvatar'); - }); - - elGrabbable.addEventListener('change', function() { - if (elCloneable.checked) { - elGrabbable.checked = false; - } + + // Server Script Status + let elServerScript = elPropertyElements["serverScripts"]; + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "property"); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", "property-serverScripts-status"); + elLabel.innerText = "Server Script Status"; + let elServerScriptStatus = document.createElement('span'); + elServerScriptStatus.setAttribute("id", "property-serverScripts-status"); + elDiv.appendChild(elLabel); + elDiv.appendChild(elServerScriptStatus); + elServerScript.parentNode.appendChild(elDiv); + + // Server Script Error + elDiv = document.createElement('div'); + elDiv.setAttribute("class", "property"); + let elServerScriptError = document.createElement('textarea'); + elServerScriptError.setAttribute("id", "property-serverScripts-error"); + elDiv.appendChild(elServerScriptError); + elServerScript.parentNode.appendChild(elDiv); + + let elScript = elPropertyElements["script"]; + elScript.parentNode.setAttribute("class", "property url refresh"); + elServerScript.parentNode.setAttribute("class", "property url refresh"); + + // User Data + let elUserData = elPropertyElements["userData"]; + elDiv = elUserData.parentNode; + let elStaticUserData = document.createElement('div'); + elStaticUserData.setAttribute("id", "property-userData-static"); + let elUserDataEditor = document.createElement('div'); + elUserDataEditor.setAttribute("id", "property-userData-editor"); + let elUserDataSaved = document.createElement('span'); + elUserDataSaved.setAttribute("id", "property-userData-saved"); + elUserDataSaved.innerText = "Saved!"; + elDiv.childNodes[2].appendChild(elUserDataSaved); + elDiv.insertBefore(elStaticUserData, elUserData); + elDiv.insertBefore(elUserDataEditor, elUserData); + + // Material Data + let elMaterialData = elPropertyElements["materialData"]; + elDiv = elMaterialData.parentNode; + let elStaticMaterialData = document.createElement('div'); + elStaticMaterialData.setAttribute("id", "property-materialData-static"); + let elMaterialDataEditor = document.createElement('div'); + elMaterialDataEditor.setAttribute("id", "property-materialData-editor"); + let elMaterialDataSaved = document.createElement('span'); + elMaterialDataSaved.setAttribute("id", "property-materialData-saved"); + elMaterialDataSaved.innerText = "Saved!"; + elDiv.childNodes[2].appendChild(elMaterialDataSaved); + elDiv.insertBefore(elStaticMaterialData, elMaterialData); + elDiv.insertBefore(elMaterialDataEditor, elMaterialData); + + // User Data Fields + let elGrabbable = elPropertyElements["grabbable"]; + let elTriggerable = elPropertyElements["triggerable"]; + let elIgnoreIK = elPropertyElements["ignoreIK"]; + elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); - - elCloneable.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneable')); - elCloneableDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneDynamic')); - elCloneableAvatarEntity.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneAvatarEntity')); - elCloneableLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLifetime')); - elCloneableLimit.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLimit')); - - elTriggerable.addEventListener('change', function() { + elTriggerable.addEventListener('change', function() { userDataChanger("grabbableKey", "triggerable", elTriggerable, elUserData, false, ['wantsTrigger']); }); elIgnoreIK.addEventListener('change', function() { userDataChanger("grabbableKey", "ignoreIK", elIgnoreIK, elUserData, true); }); - - elCollisionSoundURL.addEventListener('change', createEmitTextPropertyUpdateFunction('collisionSoundURL')); - - elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime')); - elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script')); - elScriptTimestamp.addEventListener('change', createEmitNumberPropertyUpdateFunction('scriptTimestamp')); - elServerScripts.addEventListener('change', createEmitTextPropertyUpdateFunction('serverScripts')); - elServerScripts.addEventListener('change', function() { - // invalidate the current status (so that same-same updates can still be observed visually) - elServerScriptStatus.innerText = PENDING_SCRIPT_STATUS; - }); - - elClearUserData.addEventListener("click", function() { - deleteJSONEditor(); - elUserData.value = ""; - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - updateProperty('userData', elUserData.value); - }); - - elSaveUserData.addEventListener("click", function() { - saveJSONUserData(true); - }); - - elUserData.addEventListener('change', createEmitTextPropertyUpdateFunction('userData')); - - elNewJSONEditor.addEventListener('click', function() { - deleteJSONEditor(); - createJSONEditor(); - var data = {}; - setEditorJSON(data); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - showSaveUserDataButton(); - }); - - elClearMaterialData.addEventListener("click", function() { - deleteJSONMaterialEditor(); - elMaterialData.value = ""; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - updateProperty('materialData', elMaterialData.value); - }); - - elSaveMaterialData.addEventListener("click", function() { - saveJSONMaterialData(true); - }); - - elMaterialData.addEventListener('change', createEmitTextPropertyUpdateFunction('materialData')); - - elNewJSONMaterialEditor.addEventListener('click', function() { - deleteJSONMaterialEditor(); - createJSONMaterialEditor(); - var data = {}; - setMaterialEditorJSON(data); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - showSaveMaterialDataButton(); - }); - - var colorChangeFunction = createEmitColorPropertyUpdateFunction( - 'color', elColorRed, elColorGreen, elColorBlue); - elColorRed.addEventListener('change', colorChangeFunction); - elColorGreen.addEventListener('change', colorChangeFunction); - elColorBlue.addEventListener('change', colorChangeFunction); - colorPickers['#property-color-control2'] = $('#property-color-control2').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-color-control2').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-color-control2'].colpickSetColor({ - "r": elColorRed.value, - "g": elColorGreen.value, - "b": elColorBlue.value}); - }, - onHide: function(colpick) { - $('#property-color-control2').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elLightSpotLight.addEventListener('change', createEmitCheckedPropertyUpdateFunction('isSpotlight')); - - var lightColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'color', elLightColorRed, elLightColorGreen, elLightColorBlue); - elLightColorRed.addEventListener('change', lightColorChangeFunction); - elLightColorGreen.addEventListener('change', lightColorChangeFunction); - elLightColorBlue.addEventListener('change', lightColorChangeFunction); - colorPickers['#property-light-color'] = $('#property-light-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-light-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-light-color'].colpickSetColor({ - "r": elLightColorRed.value, - "g": elLightColorGreen.value, - "b": elLightColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-light-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1)); - elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1)); - elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2)); - elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2)); - - elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); - - elCanCastShadow.addEventListener('change', createEmitCheckedPropertyUpdateFunction('canCastShadow')); - - elImageURL.addEventListener('change', createImageURLUpdateFunction('textures')); - - elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); - elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi', 0)); - - elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL')); - elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType')); - elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL')); - - elModelAnimationURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('animation', 'url')); - elModelAnimationPlaying.addEventListener('change',createEmitGroupCheckedPropertyUpdateFunction('animation', 'running')); - elModelAnimationFPS.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'fps')); - elModelAnimationFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame')); - elModelAnimationFirstFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame')); - elModelAnimationLastFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); - elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop')); - elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold')); - elModelAnimationAllowTranslation.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation')); - - elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); - - elMaterialURL.addEventListener('change', createEmitTextPropertyUpdateFunction('materialURL')); - //elMaterialMappingMode.addEventListener('change', createEmitTextPropertyUpdateFunction('materialMappingMode')); - elPriority.addEventListener('change', createEmitNumberPropertyUpdateFunction('priority', 0)); - - elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); + + // Special Property Callbacks + let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; + let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; + let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; + elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); elParentMaterialNameNumber.addEventListener('change', function () { updateProperty("parentMaterialName", this.value); }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { @@ -1858,339 +2504,43 @@ function loaded() { showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); } }); + + elPropertyElements["image"].addEventListener('change', createImageURLUpdateFunction('textures')); + + // Collapsible sections + let elCollapsible = document.getElementsByClassName("section-header"); - var materialMappingPosChangeFunction = createEmitVec2PropertyUpdateFunction('materialMappingPos', elMaterialMappingPosX, elMaterialMappingPosY); - elMaterialMappingPosX.addEventListener('change', materialMappingPosChangeFunction); - elMaterialMappingPosY.addEventListener('change', materialMappingPosChangeFunction); - var materialMappingScaleChangeFunction = createEmitVec2PropertyUpdateFunction('materialMappingScale', elMaterialMappingScaleX, elMaterialMappingScaleY); - elMaterialMappingScaleX.addEventListener('change', materialMappingScaleChangeFunction); - elMaterialMappingScaleY.addEventListener('change', materialMappingScaleChangeFunction); - elMaterialMappingRot.addEventListener('change', createEmitNumberPropertyUpdateFunction('materialMappingRot', 2)); + let toggleCollapsedEvent = function(event) { + let element = event.target.parentNode.parentNode; + let isCollapsed = element.dataset.collapsed !== "true"; + element.dataset.collapsed = isCollapsed ? "true" : false; + element.setAttribute("collapsed", isCollapsed ? "true" : "false"); + element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; + }; - elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); - elTextFaceCamera.addEventListener('change', createEmitCheckedPropertyUpdateFunction('faceCamera')); - elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); - var textTextColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'textColor', elTextTextColorRed, elTextTextColorGreen, elTextTextColorBlue); - elTextTextColorRed.addEventListener('change', textTextColorChangeFunction); - elTextTextColorGreen.addEventListener('change', textTextColorChangeFunction); - elTextTextColorBlue.addEventListener('change', textTextColorChangeFunction); - colorPickers['#property-text-text-color'] = $('#property-text-text-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-text-text-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-text-text-color'].colpickSetColor({ - "r": elTextTextColorRed.value, - "g": elTextTextColorGreen.value, - "b": elTextTextColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-text-text-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).attr('active', 'false'); - emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b); - } - }); + for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + let curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); + } + + // Textarea scrollbars + let elTextareas = document.getElementsByTagName("TEXTAREA"); - var textBackgroundColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'backgroundColor', elTextBackgroundColorRed, elTextBackgroundColorGreen, elTextBackgroundColorBlue); + let textareaOnChangeEvent = function(event) { + setTextareaScrolling(event.target); + }; - elTextBackgroundColorRed.addEventListener('change', textBackgroundColorChangeFunction); - elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction); - elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction); - colorPickers['#property-text-background-color'] = $('#property-text-background-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-text-background-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-text-background-color'].colpickSetColor({ - "r": elTextBackgroundColorRed.value, - "g": elTextBackgroundColorGreen.value, - "b": elTextBackgroundColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-text-background-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b); - } - }); - - // Key light - var keyLightModeChanged = createZoneComponentModeChangedFunction('keyLightMode', - elZoneKeyLightModeInherit, elZoneKeyLightModeDisabled, elZoneKeyLightModeEnabled); - - elZoneKeyLightModeInherit.addEventListener('change', keyLightModeChanged); - elZoneKeyLightModeDisabled.addEventListener('change', keyLightModeChanged); - elZoneKeyLightModeEnabled.addEventListener('change', keyLightModeChanged); - - colorPickers['#property-zone-key-light-color'] = $('#property-zone-key-light-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-key-light-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-key-light-color'].colpickSetColor({ - "r": elZoneKeyLightColorRed.value, - "g": elZoneKeyLightColorGreen.value, - "b": elZoneKeyLightColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-key-light-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); - } - }); - var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', - elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); - - elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightIntensity.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); - - var zoneKeyLightDirectionChangeFunction = createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', - elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); - - elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); - elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); - - elZoneKeyLightCastShadows.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('keyLight', 'castShadows')); - - // Skybox - var skyboxModeChanged = createZoneComponentModeChangedFunction('skyboxMode', - elZoneSkyboxModeInherit, elZoneSkyboxModeDisabled, elZoneSkyboxModeEnabled); - - elZoneSkyboxModeInherit.addEventListener('change', skyboxModeChanged); - elZoneSkyboxModeDisabled.addEventListener('change', skyboxModeChanged); - elZoneSkyboxModeEnabled.addEventListener('change', skyboxModeChanged); - - // Ambient light - elCopySkyboxURLToAmbientURL.addEventListener("click", function () { - document.getElementById("property-zone-key-ambient-url").value = properties.skybox.url; - properties.ambientLight.ambientURL = properties.skybox.url; - updateProperties(properties); - }); - - var ambientLightModeChanged = createZoneComponentModeChangedFunction('ambientLightMode', - elZoneAmbientLightModeInherit, elZoneAmbientLightModeDisabled, elZoneAmbientLightModeEnabled); - - elZoneAmbientLightModeInherit.addEventListener('change', ambientLightModeChanged); - elZoneAmbientLightModeDisabled.addEventListener('change', ambientLightModeChanged); - elZoneAmbientLightModeEnabled.addEventListener('change', ambientLightModeChanged); - - elZoneAmbientLightIntensity.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('ambientLight', 'ambientIntensity')); - - elZoneAmbientLightURL.addEventListener('change', - createEmitGroupTextPropertyUpdateFunction('ambientLight', 'ambientURL')); - - // Haze - var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', - elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled); - - elZoneHazeModeInherit.addEventListener('change', hazeModeChanged); - elZoneHazeModeDisabled.addEventListener('change', hazeModeChanged); - elZoneHazeModeEnabled.addEventListener('change', hazeModeChanged); - - elZoneHazeRange.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeRange')); - - colorPickers['#property-zone-haze-color'] = $('#property-zone-haze-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-haze-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-haze-color'].colpickSetColor({ - "r": elZoneHazeColorRed.value, - "g": elZoneHazeColorGreen.value, - "b": elZoneHazeColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-haze-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze'); - } - }); - var zoneHazeColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('haze', 'hazeColor', - elZoneHazeColorRed, - elZoneHazeColorGreen, - elZoneHazeColorBlue); - - elZoneHazeColorRed.addEventListener('change', zoneHazeColorChangeFunction); - elZoneHazeColorGreen.addEventListener('change', zoneHazeColorChangeFunction); - elZoneHazeColorBlue.addEventListener('change', zoneHazeColorChangeFunction); - - colorPickers['#property-zone-haze-glare-color'] = $('#property-zone-haze-glare-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-haze-glare-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-haze-glare-color'].colpickSetColor({ - "r": elZoneHazeGlareColorRed.value, - "g": elZoneHazeGlareColorGreen.value, - "b": elZoneHazeGlareColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-haze-glare-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze'); - } - }); - var zoneHazeGlareColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('haze', 'hazeGlareColor', - elZoneHazeGlareColorRed, - elZoneHazeGlareColorGreen, - elZoneHazeGlareColorBlue); - - elZoneHazeGlareColorRed.addEventListener('change', zoneHazeGlareColorChangeFunction); - elZoneHazeGlareColorGreen.addEventListener('change', zoneHazeGlareColorChangeFunction); - elZoneHazeGlareColorBlue.addEventListener('change', zoneHazeGlareColorChangeFunction); - - elZoneHazeEnableGlare.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); - elZoneHazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); - - elZoneHazeAltitudeEffect.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); - elZoneHazeCeiling.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeCeiling')); - elZoneHazeBaseRef.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBaseRef')); - - elZoneHazeBackgroundBlend.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); - - // Bloom - var bloomModeChanged = createZoneComponentModeChangedFunction('bloomMode', - elZoneBloomModeInherit, elZoneBloomModeDisabled, elZoneBloomModeEnabled); - - elZoneBloomModeInherit.addEventListener('change', bloomModeChanged); - elZoneBloomModeDisabled.addEventListener('change', bloomModeChanged); - elZoneBloomModeEnabled.addEventListener('change', bloomModeChanged); - - elZoneBloomIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomIntensity')); - elZoneBloomThreshold.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomThreshold')); - elZoneBloomSize.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomSize')); - - var zoneSkyboxColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('skybox', 'color', - elZoneSkyboxColorRed, elZoneSkyboxColorGreen, elZoneSkyboxColorBlue); - elZoneSkyboxColorRed.addEventListener('change', zoneSkyboxColorChangeFunction); - elZoneSkyboxColorGreen.addEventListener('change', zoneSkyboxColorChangeFunction); - elZoneSkyboxColorBlue.addEventListener('change', zoneSkyboxColorChangeFunction); - colorPickers['#property-zone-skybox-color'] = $('#property-zone-skybox-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-skybox-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-skybox-color'].colpickSetColor({ - "r": elZoneSkyboxColorRed.value, - "g": elZoneSkyboxColorGreen.value, - "b": elZoneSkyboxColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-skybox-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox'); - } - }); - - elZoneSkyboxURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('skybox', 'url')); - - elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); - elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); - elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); - - var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( - 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); - elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelVolumeSizeY.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelVolumeSizeZ.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelSurfaceStyle.addEventListener('change', createEmitTextPropertyUpdateFunction('voxelSurfaceStyle')); - elXTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('xTextureURL')); - elYTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('yTextureURL')); - elZTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('zTextureURL')); - - elMoveSelectionToGrid.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveSelectionToGrid" - })); - }); - elMoveAllToGrid.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveAllToGrid" - })); - }); - elResetToNaturalDimensions.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "resetToNaturalDimensions" - })); - }); - elRescaleDimensionsButton.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "rescaleDimensions", - percentage: parseFloat(elRescaleDimensionsPct.value) - })); - }); - elReloadScriptsButton.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadClientScripts" - })); - }); - elReloadServerScriptsButton.addEventListener("click", function() { - // invalidate the current status (so that same-same updates can still be observed visually) - elServerScriptStatus.innerText = PENDING_SCRIPT_STATUS; - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadServerScripts" - })); - }); - - document.addEventListener("keydown", function (keyDown) { + for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + let curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); + /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize + event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } + + document.addEventListener("keydown", function (keyDown) { if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { if (keyDown.shiftKey) { EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); @@ -2199,155 +2549,28 @@ function loaded() { } } }); + window.onblur = function() { // Fake a change event - var ev = document.createEvent("HTMLEvents"); + let ev = document.createEvent("HTMLEvents"); ev.initEvent("change", true, true); document.activeElement.dispatchEvent(ev); }; - - // For input and textarea elements, select all of the text on focus - var els = document.querySelectorAll("input, textarea"); - for (var i = 0; i < els.length; i++) { + + // For input and textarea elements, select all of the text on focus + let els = document.querySelectorAll("input, textarea"); + for (let i = 0; i < els.length; i++) { els[i].onfocus = function (e) { e.target.select(); }; } + + bindAllNonJSONEditorElements(); - bindAllNonJSONEditorElements(); - }); - - // Collapsible sections - var elCollapsible = document.getElementsByClassName("section-header"); - - var toggleCollapsedEvent = function(event) { - var element = event.target.parentNode.parentNode; - var isCollapsed = element.dataset.collapsed !== "true"; - element.dataset.collapsed = isCollapsed ? "true" : false; - element.setAttribute("collapsed", isCollapsed ? "true" : "false"); - element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; - }; - - for (var collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { - var curCollapsibleElement = elCollapsible[collapseIndex]; - curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); - } - - - // Textarea scrollbars - var elTextareas = document.getElementsByTagName("TEXTAREA"); - - var textareaOnChangeEvent = function(event) { - setTextareaScrolling(event.target); - }; - - for (var textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { - var curTextAreaElement = elTextareas[textAreaIndex]; - setTextareaScrolling(curTextAreaElement); - curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); - curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); - /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize - event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ - curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); - } - - // Dropdowns - // For each dropdown the following replacement is created in place of the original dropdown... - // Structure created: - //
- //
display textcarat
- //
- //
    - //
  • 0) { - var el = elDropdowns[0]; - el.parentNode.removeChild(el); - elDropdowns = document.getElementsByTagName("select"); - } + showGroupsForType("None"); + resetProperties(); + disableProperties(); + }); augmentSpinButtons(); From 7aba0a9082499ed98ae1359ef7b329566afaa295 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 9 Oct 2018 10:17:39 -0700 Subject: [PATCH 026/131] cleanup --- scripts/system/html/css/edit-style.css | 24 +- scripts/system/html/js/entityProperties.js | 2353 ++++++++++---------- 2 files changed, 1187 insertions(+), 1190 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index b7aac02c7c..e700b50839 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -614,6 +614,10 @@ hr { margin-top: 0; } +.checkbox-sub-props { + margin-top: 18px; +} + .property .number { float: left; } @@ -918,6 +922,14 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { outline: none; } +fieldset .checkbox-sub-props { + margin-top: 0; +} + +fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; +} + .column { vertical-align: top; } @@ -1269,7 +1281,7 @@ th#entity-hasScript { #base #property-type { padding: 5px 24px 5px 0; - border-right: 1px solid #808080; + border-right: 1px solid #808080; width: auto; display: inline-block; } @@ -1277,13 +1289,13 @@ th#entity-hasScript { #base #div-locked { position: absolute; top: 0px; - right: 140px; + right: 140px; } #base #div-visible { position: absolute; top: 20px; - right: 20px; + right: 20px; } #base .checkbox { @@ -1361,9 +1373,9 @@ input#property-scale-button-reset { } #property-serverScripts-status { - position: relative; - top: -3px; - right: -20px; + position: relative; + top: -3px; + right: -20px; } #properties-list #collision-info > fieldset:first-of-type { diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 03dad80be6..d8ffc2ee25 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1,5 +1,4 @@ // entityProperties.js -// entityProperties.js // // Created by Ryan Huffman on 13 Nov 2014 // Copyright 2014 High Fidelity, Inc. @@ -9,7 +8,7 @@ /* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, HifiEntityUI, JSONEditor, openEventBridge, setTimeout, window, _ $ */ - + const ICON_FOR_TYPE = { Box: "V", Sphere: "n", @@ -25,7 +24,7 @@ const ICON_FOR_TYPE = { Multiple: "", PolyLine: "", Material: "" -}; +}; const PI = 3.14159265358979; const DEGREES_TO_RADIANS = PI / 180.0; @@ -37,420 +36,406 @@ const GROUPS = [ { id: "base", properties: [ - { + { label: NO_SELECTION, type: "icon", - icons: ICON_FOR_TYPE, + icons: ICON_FOR_TYPE, propertyName: "type", }, - { + { label: "Name", type: "string", propertyName: "name", }, - { + { label: "ID", type: "string", propertyName: "id", readOnly: true, }, - { + { label: "Parent", type: "string", propertyName: "parentID", }, - { + { label: "Locked", - glyph: "", + glyph: "", type: "bool", propertyName: "locked", }, - { + { label: "Visible", - glyph: "", + glyph: "", type: "bool", propertyName: "visible", }, ] }, - { + { id: "shape", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Shape", type: "dropdown", - options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, propertyName: "shape", }, - { + { label: "Color", type: "color", propertyName: "color", }, - /* - { - label: "Material", - type: "string", - propertyName: "", - }, - */ ] }, - { + { id: "text", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Text", type: "string", propertyName: "text", }, - { + { label: "Text Color", type: "color", propertyName: "textColor", }, - { + { label: "Background Color", type: "color", propertyName: "backgroundColor", }, - /* - { - label: "Transparent Background", - type: "bool", - propertyName: "" - }, - */ - { - label: "Line Height", + { + label: "Line Height", type: "number", - min: 0, - step: 0.005, - fixedDecimals: 4, - unit: "m", + min: 0, + step: 0.005, + fixedDecimals: 4, + unit: "m", propertyName: "lineHeight" }, - { + { label: "Face Camera", type: "bool", propertyName: "faceCamera" }, ] }, - { + { id: "zone", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Flying Allowed", type: "bool", propertyName: "flyingAllowed", }, - { + { label: "Ghosting Allowed", type: "bool", propertyName: "ghostingAllowed", }, - { + { label: "Filter", type: "string", propertyName: "filterURL", }, - { + { label: "Key Light", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "keyLightMode", - + }, - { + { label: "Key Light Color", type: "color", propertyName: "keyLight.color", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Light Intensity", type: "number", - min: 0, - max: 10, - step: 0.1, - fixedDecimals: 2, + min: 0, + max: 10, + step: 0.1, + fixedDecimals: 2, propertyName: "keyLight.intensity", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Light Altitude", type: "number", - fixedDecimals: 2, - unit: "deg", + fixedDecimals: 2, + unit: "deg", propertyName: "keyLight.direction.y", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Light Azimuth", type: "number", - fixedDecimals: 2, - unit: "deg", + fixedDecimals: 2, + unit: "deg", propertyName: "keyLight.direction.x", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Cast Shadows", type: "bool", propertyName: "keyLight.castShadows", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Skybox", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "skyboxMode", }, - { + { label: "Skybox Color", type: "color", propertyName: "skybox.color", - showPropertyRule: { "skyboxMode": "enabled" }, + showPropertyRule: { "skyboxMode": "enabled" }, }, - { + { label: "Skybox URL", type: "string", propertyName: "skybox.url", - showPropertyRule: { "skyboxMode": "enabled" }, + showPropertyRule: { "skyboxMode": "enabled" }, }, - { + { type: "buttons", - buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], + buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], propertyName: "copyURLToAmbient", - showPropertyRule: { "skyboxMode": "enabled" }, + showPropertyRule: { "skyboxMode": "enabled" }, }, - { + { label: "Ambient Light", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "ambientLightMode", }, - { + { label: "Ambient Intensity", type: "number", - min: 0, - max: 10, - step: 0.1, - fixedDecimals: 2, + min: 0, + max: 10, + step: 0.1, + fixedDecimals: 2, propertyName: "ambientLight.ambientIntensity", - showPropertyRule: { "ambientLightMode": "enabled" }, + showPropertyRule: { "ambientLightMode": "enabled" }, }, - { + { label: "Ambient URL", type: "string", propertyName: "ambientLight.ambientURL", - showPropertyRule: { "ambientLightMode": "enabled" }, + showPropertyRule: { "ambientLightMode": "enabled" }, }, - { + { label: "Haze", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "hazeMode", }, - { + { label: "Range", type: "number", - min: 5, - max: 10000, - step: 5, - fixedDecimals: 0, - unit: "m", + min: 5, + max: 10000, + step: 5, + fixedDecimals: 0, + unit: "m", propertyName: "haze.hazeRange", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Use Altitude", type: "bool", propertyName: "haze.hazeAltitudeEffect", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Base", type: "number", - min: -1000, - max: 1000, - step: 10, - fixedDecimals: 0, - unit: "m", + min: -1000, + max: 1000, + step: 10, + fixedDecimals: 0, + unit: "m", propertyName: "haze.hazeBaseRef", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Ceiling", type: "number", - min: -1000, - max: 5000, - step: 10, - fixedDecimals: 0, - unit: "m", + min: -1000, + max: 5000, + step: 10, + fixedDecimals: 0, + unit: "m", propertyName: "haze.hazeCeiling", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Haze Color", type: "color", propertyName: "haze.hazeColor", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Background Blend", type: "number", - min: 0.0, - max: 1.0, - step: 0.01, - fixedDecimals: 2, + min: 0.0, + max: 1.0, + step: 0.01, + fixedDecimals: 2, propertyName: "haze.hazeBackgroundBlend", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Enable Glare", type: "bool", propertyName: "haze.hazeEnableGlare", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Glare Color", type: "color", propertyName: "haze.hazeGlareColor", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Glare Angle", type: "number", - min: 0, - max: 180, - step: 1, - fixedDecimals: 0, + min: 0, + max: 180, + step: 1, + fixedDecimals: 0, propertyName: "haze.hazeGlareAngle", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Bloom", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "bloomMode", }, - { + { label: "Bloom Intensity", type: "number", - min: 0, - max: 1, - step: 0.01, - fixedDecimals: 2, + min: 0, + max: 1, + step: 0.01, + fixedDecimals: 2, propertyName: "bloom.bloomIntensity", - showPropertyRule: { "bloomMode": "enabled" }, + showPropertyRule: { "bloomMode": "enabled" }, }, - { + { label: "Bloom Threshold", type: "number", - min: 0, - min: 1, - step: 0.01, - fixedDecimals: 2, + min: 0, + min: 1, + step: 0.01, + fixedDecimals: 2, propertyName: "bloom.bloomThreshold", - showPropertyRule: { "bloomMode": "enabled" }, + showPropertyRule: { "bloomMode": "enabled" }, }, - { + { label: "Bloom Size", type: "number", - min: 0, - min: 2, - step: 0.01, - fixedDecimals: 2, + min: 0, + min: 2, + step: 0.01, + fixedDecimals: 2, propertyName: "bloom.bloomSize", - showPropertyRule: { "bloomMode": "enabled" }, + showPropertyRule: { "bloomMode": "enabled" }, }, ] }, - { + { id: "model", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Model", type: "string", propertyName: "modelURL", }, - { + { label: "Collision Shape", type: "dropdown", - options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, propertyName: "shapeType", }, - { + { label: "Compound Shape", type: "string", propertyName: "compoundShapeURL", }, - { + { label: "Animation", type: "string", propertyName: "animation.url", }, - { + { label: "Play Automatically", type: "bool", propertyName: "animation.running", }, - { + { label: "Allow Transition", type: "bool", propertyName: "animation.allowTranslation", }, - { + { label: "Loop", type: "bool", propertyName: "animation.loop", }, - { + { label: "Hold", type: "bool", propertyName: "animation.hold", }, - { + { label: "Animation Frame", type: "number", propertyName: "animation.currentFrame", }, - { + { label: "First Frame", type: "number", propertyName: "animation.firstFrame", }, - { + { label: "Last Frame", type: "number", propertyName: "animation.lastFrame", }, - { + { label: "Animation FPS", type: "number", propertyName: "animation.fps", }, - { + { label: "Texture", type: "textarea", propertyName: "textures", }, - { + { label: "Original Texture", type: "textarea", propertyName: "originalTextures", - readOnly: true, + readOnly: true, }, ] }, - { + { id: "image", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Image", @@ -459,135 +444,135 @@ const GROUPS = [ }, ] }, - { + { id: "web", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Source", type: "string", propertyName: "sourceUrl", }, - { + { label: "Source Resolution", type: "number", propertyName: "dpi", }, ] }, - { + { id: "light", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Light Color", type: "color", propertyName: "lightColor", // this actually shares "color" property with shape Color - // but separating naming here to separate property fields + // but separating naming here to separate property fields }, - { + { label: "Intensity", type: "number", - min: 0, - step: 0.1, - fixedDecimals: 1, + min: 0, + step: 0.1, + fixedDecimals: 1, propertyName: "intensity", }, - { + { label: "Fall-Off Radius", type: "number", - min: 0, - step: 0.1, - fixedDecimals: 1, - unit: "m", + min: 0, + step: 0.1, + fixedDecimals: 1, + unit: "m", propertyName: "falloffRadius", }, - { + { label: "Spotlight", type: "bool", propertyName: "isSpotlight", }, - { + { label: "Spotlight Exponent", type: "number", - step: 0.01, - fixedDecimals: 2, + step: 0.01, + fixedDecimals: 2, propertyName: "exponent", }, - { + { label: "Spotlight Cut-Off", type: "number", - step: 0.01, - fixedDecimals: 2, + step: 0.01, + fixedDecimals: 2, propertyName: "cutoff", }, ] }, - { + { id: "material", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Material URL", type: "string", propertyName: "materialURL", }, - { + { label: "Material Data", type: "textarea", - buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, - { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], propertyName: "materialData", }, - { + { label: "Submesh to Replace", type: "number", - min: 0, - step: 1, + min: 0, + step: 1, propertyName: "submeshToReplace", }, - { + { label: "Material Name to Replace", type: "string", propertyName: "materialNameToReplace", }, - { + { label: "Select Submesh", type: "bool", propertyName: "selectSubmesh", }, - { + { label: "Priority", type: "number", - min: 0, + min: 0, propertyName: "priority", }, - { + { label: "Material Position", type: "vec2", - min: 0, - min: 1, - step: 0.1, - vec2Type: "xy", - subLabels: [ "x", "y" ], + min: 0, + min: 1, + step: 0.1, + vec2Type: "xy", + subLabels: [ "x", "y" ], propertyName: "materialMappingPos", }, - { + { label: "Material Scale", type: "vec2", - min: 0, - step: 0.1, - vec2Type: "wh", - subLabels: [ "width", "height" ], + min: 0, + step: 0.1, + vec2Type: "wh", + subLabels: [ "width", "height" ], propertyName: "materialMappingScale", }, - { + { label: "Material Rotation", type: "number", - step: 0.1, - fixedDecimals: 2, - unit: "deg", + step: 0.1, + fixedDecimals: 2, + unit: "deg", propertyName: "materialMappingRot", }, ] @@ -599,292 +584,292 @@ const GROUPS = [ { label: "Position", type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m", propertyName: "position", }, - { + { label: "Rotation", type: "vec3", - step: 0.1, - vec3Type: "pyr", - subLabels: [ "pitch", "yaw", "roll" ], - unit: "deg", + step: 0.1, + vec3Type: "pyr", + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg", propertyName: "rotation", }, - { + { label: "Dimension", type: "vec3", - step: 0.1, - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m", + step: 0.1, + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m", propertyName: "dimensions", }, - { + { label: "Scale", type: "number", - defaultValue: 100, - unit: "%", - buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, - { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + defaultValue: 100, + unit: "%", + buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], propertyName: "scale", }, - { + { label: "Pivot", type: "vec3", - step: 0.1, - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "(ratio of dimension)", + step: 0.1, + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "(ratio of dimension)", propertyName: "registrationPoint", }, - { + { label: "Align", type: "buttons", - buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, - { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], propertyName: "alignToGrid", }, ] }, - { + { id: "collision", label: "COLLISION", - twoColumn: true, + twoColumn: true, properties: [ { label: "Collides", type: "bool", propertyName: "collisionless", - inverse: true, - column: -1, // before two columns div + inverse: true, + column: -1, // before two columns div }, - { + { label: "Dynamic", type: "bool", propertyName: "dynamic", - column: -1, // before two columns div + column: -1, // before two columns div }, - { + { label: "Collides With", type: "sub-header", - propertyName: "collidesWithHeader", - showPropertyRule: { "collisionless": "false" }, - column: 1, + propertyName: "collidesWithHeader", + showPropertyRule: { "collisionless": "false" }, + column: 1, }, - { + { label: "", type: "sub-header", - propertyName: "collidesWithHeaderHelper", - showPropertyRule: { "collisionless": "false" }, - column: 2, + propertyName: "collidesWithHeaderHelper", + showPropertyRule: { "collisionless": "false" }, + column: 2, }, - { + { label: "Static Entities", type: "bool", propertyName: "static", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 1, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, }, - { + { label: "Dynamic Entities", type: "bool", propertyName: "dynamic", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 2, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 2, }, - { + { label: "Kinematic Entities", type: "bool", propertyName: "kinematic", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 1, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, }, - { + { label: "My Avatar", type: "bool", propertyName: "myAvatar", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 2, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 2, }, - { + { label: "Other Avatars", type: "bool", propertyName: "otherAvatar", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 1, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, }, - { + { label: "Collision sound URL", type: "string", propertyName: "collisionSoundURL", - showPropertyRule: { "collisionless": "false" }, + showPropertyRule: { "collisionless": "false" }, }, ] }, - { + { id: "behavior", label: "BEHAVIOR", - twoColumn: true, + twoColumn: true, properties: [ { label: "Grabbable", type: "bool", propertyName: "grabbable", - column: 1, + column: 1, }, - { + { label: "Triggerable", type: "bool", propertyName: "triggerable", - column: 2, + column: 2, }, - { + { label: "Cloneable", type: "bool", propertyName: "cloneable", - column: 1, + column: 1, }, - { + { label: "Ignore inverse kinematics", type: "bool", propertyName: "ignoreIK", - column: 2, + column: 2, }, - { + { label: "Clone Lifetime", type: "number", - unit: "s", + unit: "s", propertyName: "cloneLifetime", - showPropertyRule: { "cloneable": "true" }, - column: 1, + showPropertyRule: { "cloneable": "true" }, + column: 1, }, - { + { label: "Clone Limit", type: "number", propertyName: "cloneLimit", - showPropertyRule: { "cloneable": "true" }, - column: 1, + showPropertyRule: { "cloneable": "true" }, + column: 1, }, - { + { label: "Clone Dynamic", type: "bool", propertyName: "cloneDynamic", - showPropertyRule: { "cloneable": "true" }, - column: 1, + showPropertyRule: { "cloneable": "true" }, + column: 1, }, - { + { label: "Clone Avatar Entity", type: "bool", propertyName: "cloneAvatarEntity", - showPropertyRule: { "cloneable": "true" }, - column: 1, + showPropertyRule: { "cloneable": "true" }, + column: 1, }, - { + { label: "Can cast shadow", type: "bool", propertyName: "castShadow", }, - { + { label: "Script", type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], propertyName: "script", }, - { + { label: "Server Script", type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], propertyName: "serverScripts", }, - { + { label: "Lifetime", type: "number", - unit: "s", + unit: "s", propertyName: "lifetime", }, - { + { label: "User Data", type: "textarea", - buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, - { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], propertyName: "userData", }, - ] + ] }, - { + { id: "physics", label: "PHYSICS", properties: [ { label: "Linear Velocity", type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m/s", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s", propertyName: "velocity", }, - { + { label: "Linear Damping", type: "number", - fixedDecimals: 2, + fixedDecimals: 2, propertyName: "damping", }, - { + { label: "Angular Velocity", type: "vec3", - multiplier: DEGREES_TO_RADIANS, - vec3Type: "pyr", - subLabels: [ "pitch", "yaw", "roll" ], - unit: "deg/s", + multiplier: DEGREES_TO_RADIANS, + vec3Type: "pyr", + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg/s", propertyName: "angularVelocity", }, - { + { label: "Angular Damping", type: "number", - fixedDecimals: 4, + fixedDecimals: 4, propertyName: "angularDamping", }, - { + { label: "Bounciness", type: "number", - fixedDecimals: 4, + fixedDecimals: 4, propertyName: "restitution", }, - { + { label: "Friction", type: "number", - fixedDecimals: 4, + fixedDecimals: 4, propertyName: "friction", }, - { + { label: "Density", type: "number", - fixedDecimals: 4, + fixedDecimals: 4, propertyName: "density", }, - { + { label: "Gravity", type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m/s2", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s2", propertyName: "gravity", }, - { + { label: "Acceleration", type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m/s2", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s2", propertyName: "acceleration", }, ] @@ -980,21 +965,21 @@ function showElements(els, show) { function updateProperty(propertyName, propertyValue) { var properties = {}; - let splitPropertyName = propertyName.split('.'); - if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; - properties[propertyGroupName] = {}; - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - properties[propertyGroupName][subPropertyName] = {}; - properties[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; - } else { - properties[propertyGroupName][subPropertyName] = propertyValue; - } - } else { - properties[propertyName] = propertyValue; - } + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + properties[propertyGroupName] = {}; + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + properties[propertyGroupName][subPropertyName] = {}; + properties[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; + } else { + properties[propertyGroupName][subPropertyName] = propertyValue; + } + } else { + properties[propertyName] = propertyValue; + } updateProperties(properties); } @@ -1117,27 +1102,27 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen } function clearUserData() { - let elUserData = elPropertyElements["userData"]; - deleteJSONEditor(); - elUserData.value = ""; - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - updateProperty('userData', elUserData.value); + let elUserData = elPropertyElements["userData"]; + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + updateProperty('userData', elUserData.value); } function newJSONEditor() { - deleteJSONEditor(); - createJSONEditor(); - var data = {}; - setEditorJSON(data); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - showSaveUserDataButton(); + deleteJSONEditor(); + createJSONEditor(); + var data = {}; + setEditorJSON(data); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + showSaveUserDataButton(); } function saveUserData() { - saveJSONUserData(true); + saveJSONUserData(true); } function setUserDataFromEditor(noUpdate) { @@ -1287,7 +1272,7 @@ function hideUserDataTextArea() { } function hideUserDataSaved() { - $('#property-userData-saved').hide(); + $('#property-userData-saved').hide(); } function showStaticUserData() { @@ -1335,27 +1320,27 @@ function saveJSONUserData(noUpdate) { } function clearMaterialData() { - let elMaterialData = elPropertyElements["materialData"]; - deleteJSONMaterialEditor(); - elMaterialData.value = ""; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - updateProperty('materialData', elMaterialData.value); + let elMaterialData = elPropertyElements["materialData"]; + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value); } function newJSONMaterialEditor() { - deleteJSONMaterialEditor(); - createJSONMaterialEditor(); - var data = {}; - setMaterialEditorJSON(data); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - showSaveMaterialDataButton(); + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + var data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); } function saveMaterialData() { - saveJSONMaterialData(true); + saveJSONMaterialData(true); } function setMaterialDataFromEditor(noUpdate) { @@ -1424,7 +1409,7 @@ function hideSaveMaterialDataButton() { } function disableSaveMaterialDataButton() { - $('#property-materialData-button-save').attr('disabled', true); + $('#property-materialData-button-save').attr('disabled', true); } function showNewJSONMaterialEditorButton() { @@ -1444,7 +1429,7 @@ function hideMaterialDataTextArea() { } function hideMaterialDataSaved() { - $('#property-materialData-saved').hide(); + $('#property-materialData-saved').hide(); } function showStaticMaterialData() { @@ -1525,8 +1510,8 @@ function unbindAllInputs() { } function setTextareaScrolling(element) { - var isScrolling = element.scrollHeight > element.offsetHeight; - element.setAttribute("scrolling", isScrolling ? "true" : "false"); + var isScrolling = element.scrollHeight > element.offsetHeight; + element.setAttribute("scrolling", isScrolling ? "true" : "false"); } function showParentMaterialNameBox(number, elNumber, elString) { @@ -1542,557 +1527,557 @@ function showParentMaterialNameBox(number, elNumber, elString) { } function rescaleDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "rescaleDimensions", - percentage: parseFloat(document.getElementById("property-scale").value) - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); } function moveSelectionToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveSelectionToGrid" - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); } function moveAllToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveAllToGrid" - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); } function resetToNaturalDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "resetToNaturalDimensions" - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); } function reloadScripts() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadClientScripts" - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); } function reloadServerScripts() { - // invalidate the current status (so that same-same updates can still be observed visually) - document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadServerScripts" - })); + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); } function copySkyboxURLToAmbientURL() { - let skyboxURL = elPropertyElements["skybox.url"].value; - elPropertyElements["ambientLight.ambientURL"].value = skyboxURL; - updateProperty("ambientLight.ambientURL", skyboxURL); + let skyboxURL = elPropertyElements["skybox.url"].value; + elPropertyElements["ambientLight.ambientURL"].value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL); } function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { - let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); - let elDiv = document.createElement('div'); - let elLabel = document.createElement('label'); - elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; - elLabel.setAttribute("for", elementPropertyID); - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementPropertyID); - elInput.setAttribute("type", "number"); - elInput.setAttribute("class", subLabel); - if (min !== undefined) { - elInput.setAttribute("min", min); - } - if (max !== undefined) { - elInput.setAttribute("max", max); - } - if (step !== undefined) { - elInput.setAttribute("step", step); - } - elDiv.appendChild(elInput); - elDiv.appendChild(elLabel); - elTuple.appendChild(elDiv); - return elInput; + let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; + elLabel.setAttribute("for", elementPropertyID); + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementPropertyID); + elInput.setAttribute("type", "number"); + elInput.setAttribute("class", subLabel); + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } + if (step !== undefined) { + elInput.setAttribute("step", step); + } + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elTuple.appendChild(elDiv); + return elInput; } function addUnit(unit, elLabel) { - if (unit !== undefined) { - let elSpan = document.createElement('span'); - elSpan.setAttribute("class", "unit"); - elSpan.innerHTML = unit; - elLabel.appendChild(elSpan); - } + if (unit !== undefined) { + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "unit"); + elSpan.innerHTML = unit; + elLabel.appendChild(elSpan); + } } function addButtons(elProperty, propertyID, buttons, newRow) { - let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "row"); - - buttons.forEach(function(button) { - let elButton = document.createElement('input'); - elButton.setAttribute("type", "button"); - elButton.setAttribute("class", button.className); - elButton.setAttribute("id", propertyID + "-button-" + button.id); - elButton.setAttribute("value", button.label); - elButton.addEventListener("click", button.onClick); - if (newRow) { - elDiv.appendChild(elButton); - } else { - elProperty.appendChild(elButton); - } - }); - - if (newRow) { - elProperty.appendChild(document.createElement('br')); - elProperty.appendChild(elDiv); - } + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "row"); + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.setAttribute("type", "button"); + elButton.setAttribute("class", button.className); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } } function showGroupsForType(type) { - if (type === "Box" || type === "Sphere") { - type = "Shape"; - } - - let typeGroups = GROUPS_PER_TYPE[type]; + if (type === "Box" || type === "Sphere") { + type = "Shape"; + } + + let typeGroups = GROUPS_PER_TYPE[type]; - for (let groupKey in elGroups) { - let elGroup = elGroups[groupKey]; - if (typeGroups && typeGroups.indexOf(groupKey) > -1) { - elGroup.style.display = "block"; - } else { - elGroup.style.display = "none"; - } - } + for (let groupKey in elGroups) { + let elGroup = elGroups[groupKey]; + if (typeGroups && typeGroups.indexOf(groupKey) > -1) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } + } } function resetProperties() { - for (let propertyName in elPropertyElements) { - let elProperty = elPropertyElements[propertyName]; - if (elProperty instanceof Array) { - if (elProperty.length === 2 || elProperty.length === 3) { - // vec2/vec3 are array of 2/3 input numbers - elProperty[0].value = ""; - elProperty[1].value = ""; - if (elProperty[2] !== undefined) { - elProperty[2].value = ""; - } - } else if (elProperty.length === 4) { - // color is array of color picker and 3 input numbers - elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elProperty[1].value = ""; - elProperty[2].value = ""; - elProperty[3].value = ""; - } - } else if (elProperty.getAttribute("type") === "number") { - if (elProperty.getAttribute("defaultValue")) { - elProperty.value = elProperty.getAttribute("defaultValue"); - } else { - elProperty.value = ""; - } - } else if (elProperty.getAttribute("type") === "checkbox") { - elProperty.checked = false; - } else { - elProperty.value = ""; - } - } - - for (let showPropertyRule in showPropertyRules) { - let propertyShowRules = showPropertyRules[showPropertyRule]; - for (let propertyToHide in propertyShowRules) { - let elPropertyToHide = elPropertyElements[propertyToHide]; - if (elPropertyToHide) { - let parentNode = elPropertyToHide.parentNode; - if (parentNode === undefined && elPropertyToHide instanceof Array) { - parentNode = elPropertyToHide[0].parentNode; - } - parentNode.style.display = "none"; - } - } - } + for (let propertyName in elPropertyElements) { + let elProperty = elPropertyElements[propertyName]; + if (elProperty instanceof Array) { + if (elProperty.length === 2 || elProperty.length === 3) { + // vec2/vec3 are array of 2/3 input numbers + elProperty[0].value = ""; + elProperty[1].value = ""; + if (elProperty[2] !== undefined) { + elProperty[2].value = ""; + } + } else if (elProperty.length === 4) { + // color is array of color picker and 3 input numbers + elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elProperty[1].value = ""; + elProperty[2].value = ""; + elProperty[3].value = ""; + } + } else if (elProperty.getAttribute("type") === "number") { + if (elProperty.getAttribute("defaultValue")) { + elProperty.value = elProperty.getAttribute("defaultValue"); + } else { + elProperty.value = ""; + } + } else if (elProperty.getAttribute("type") === "checkbox") { + elProperty.checked = false; + } else { + elProperty.value = ""; + } + } + + for (let showPropertyRule in showPropertyRules) { + let propertyShowRules = showPropertyRules[showPropertyRule]; + for (let propertyToHide in propertyShowRules) { + let elPropertyToHide = elPropertyElements[propertyToHide]; + if (elPropertyToHide) { + let parentNode = elPropertyToHide.parentNode; + if (parentNode === undefined && elPropertyToHide instanceof Array) { + parentNode = elPropertyToHide[0].parentNode; + } + parentNode.style.display = "none"; + } + } + } } function loaded() { - openEventBridge(function() { - let elPropertiesList = document.getElementById("properties-list"); - - GROUPS.forEach(function(group) { - let elGroup; - if (group.addToGroup !== undefined) { - let fieldset = document.getElementById(group.addToGroup); - elGroup = document.createElement('div'); - fieldset.appendChild(elGroup); - } else { - elGroup = document.createElement('fieldset'); - elGroup.setAttribute("class", "major"); - elGroup.setAttribute("id", group.id); - elPropertiesList.appendChild(elGroup); - } + openEventBridge(function() { + let elPropertiesList = document.getElementById("properties-list"); + + GROUPS.forEach(function(group) { + let elGroup; + if (group.addToGroup !== undefined) { + let fieldset = document.getElementById(group.addToGroup); + elGroup = document.createElement('div'); + fieldset.appendChild(elGroup); + } else { + elGroup = document.createElement('fieldset'); + elGroup.setAttribute("class", "major"); + elGroup.setAttribute("id", group.id); + elPropertiesList.appendChild(elGroup); + } - if (group.label !== undefined) { - let elLegend = document.createElement('legend'); - elLegend.innerText = group.label; - elLegend.setAttribute("class", "section-header"); - let elSpan = document.createElement('span'); - elSpan.setAttribute("class", ".collapse-icon"); - elSpan.innerText = "M"; - elLegend.appendChild(elSpan); - elGroup.appendChild(elLegend); - } - - group.properties.forEach(function(property) { - let propertyType = property.type; - let propertyName = property.propertyName; - let propertyID = "property-" + propertyName; - propertyID = propertyID.replace(".", "-"); - - let elProperty; - if (propertyType === "sub-header") { - elProperty = document.createElement('legend'); - elProperty.innerText = property.label; - elProperty.setAttribute("class", "sub-section-header"); - } else { - elProperty = document.createElement('div'); - elProperty.setAttribute("id", "div-" + propertyName); - } - - if (group.twoColumn && property.column !== undefined && property.column !== -1) { - let columnName = group.id + "column" + property.column; - let elColumn = document.getElementById(columnName); - if (!elColumn) { - let columnDivName = group.id + "columnDiv"; - let elColumnDiv = document.getElementById(columnDivName); - if (!elColumnDiv) { - elColumnDiv = document.createElement('div'); - elColumnDiv.setAttribute("class", "two-column"); - elColumnDiv.setAttribute("id", group.id + "columnDiv"); - elGroup.appendChild(elColumnDiv); - } - elColumn = document.createElement('fieldset'); - elColumn.setAttribute("class", "column"); - elColumn.setAttribute("id", columnName); - elColumnDiv.appendChild(elColumn); - } - elColumn.appendChild(elProperty); - } else { - elGroup.appendChild(elProperty); - } - - let elLabel = document.createElement('label'); - elLabel.innerText = property.label; - elLabel.setAttribute("for", propertyID); - - switch (propertyType) { - case 'vec3': { - elProperty.setAttribute("class", "property " + property.vec3Type + " fstuple"); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - addUnit(property.unit, elLabel); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); - let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); - let elInputZ = createTupleNumberInput(elTuple, propertyID, property.subLabels[2], property.min, property.max, property.step); - - let inputChangeFunction; - let multiplier = 1; - if (property.multiplier !== undefined) { - multiplier = property.multiplier; - inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); - } else { - inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, elInputZ, property.multiplier); - } - elInputX.setAttribute("multiplier", multiplier); - elInputY.setAttribute("multiplier", multiplier); - elInputZ.setAttribute("multiplier", multiplier); - elInputX.addEventListener('change', inputChangeFunction); - elInputY.addEventListener('change', inputChangeFunction); - elInputZ.addEventListener('change', inputChangeFunction); - - elPropertyElements[propertyName] = [elInputX, elInputY, elInputZ]; - break; - } - case 'vec2': { - elProperty.setAttribute("class", "property " + property.vec2Type + " fstuple"); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - addUnit(property.unit, elLabel); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); - let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); - - let inputChangeFunction; - let multiplier = 1; - if (property.multiplier !== undefined) { - multiplier = property.multiplier; - inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY); - } else { - inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, property.multiplier); - } - elInputX.setAttribute("multiplier", multiplier); - elInputY.setAttribute("multiplier", multiplier); - elInputX.addEventListener('change', inputChangeFunction); - elInputY.addEventListener('change', inputChangeFunction); - - elPropertyElements[propertyName] = [elInputX, elInputY]; - break; - } - case 'color': { - elProperty.setAttribute("class", "property rgb fstuple"); - - let elColorPicker = document.createElement('div'); - elColorPicker.setAttribute("class", "color-picker"); - elColorPicker.setAttribute("id", propertyID); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - elProperty.appendChild(elColorPicker); - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputR = createTupleNumberInput(elTuple, propertyID, "red", 0, 255, 1); - let elInputG = createTupleNumberInput(elTuple, propertyID, "green", 0, 255, 1); - let elInputB = createTupleNumberInput(elTuple, propertyID, "blue", 0, 255, 1); - - let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); - elInputR.addEventListener('change', inputChangeFunction); - elInputG.addEventListener('change', inputChangeFunction); - elInputB.addEventListener('change', inputChangeFunction); - - let colorPickerID = "#" + propertyID; - colorPickers[colorPickerID] = $(colorPickerID).colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $(colorPickerID).attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers[colorPickerID].colpickSetColor({ - "r": elInputR.value, - "g": elInputG.value, - "b": elInputB.value - }); - }, - onHide: function(colpick) { - $(colorPickerID).attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elPropertyElements[propertyName] = [elColorPicker, elInputR, elInputG, elInputB]; - break; - } - case 'string': { - elProperty.setAttribute("class", "property text"); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "text"); - if (property.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, false); - } - - elPropertyElements[propertyName] = elInput; - break; - } - case 'bool': { - elProperty.setAttribute("class", "property checkbox"); - - if (property.glyph !== undefined) { - elLabel.innerText = " " + property.label; - let elSpan = document.createElement('span'); - elSpan.innerHTML = property.glyph; - elLabel.insertBefore(elSpan, elLabel.firstChild); - } - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "checkbox"); - - let inverse = property.inverse; - elInput.setAttribute("inverse", inverse ? "true" : "false"); - - elProperty.appendChild(elInput); - elProperty.appendChild(elLabel); - - let subPropertyOf = property.subPropertyOf; - if (subPropertyOf !== undefined) { - elInput.setAttribute("subPropertyOf", subPropertyOf); - elPropertyElements[propertyName] = elInput; - subProperties.push(propertyName); - elInput.addEventListener('change', function() { - updateCheckedSubProperty(subPropertyOf, properties[subPropertyOf], elInput, propertyName); - }); - } else { - elPropertyElements[propertyName] = elInput; - elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, inverse)); - } - break; - } - case 'dropdown': { - elProperty.setAttribute("class", "property dropdown"); - - let elInput = document.createElement('select'); - elInput.setAttribute("id", propertyID); - - for (let optionKey in property.options) { - let option = document.createElement('option'); - option.value = optionKey; - option.text = property.options[optionKey]; - elInput.add(option); - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - elPropertyElements[propertyName] = elInput; - break; - } - case 'number': { - elProperty.setAttribute("class", "property number"); - - addUnit(property.unit, elLabel); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "number"); - if (property.min !== undefined) { - elInput.setAttribute("min", property.min); - } - if (property.max !== undefined) { - elInput.setAttribute("max", property.max); - } - if (property.step !== undefined) { - elInput.setAttribute("step", property.step); - } - - let fixedDecimals = property.fixedDecimals !== undefined ? property.fixedDecimals : 0; - elInput.setAttribute("fixedDecimals", fixedDecimals); - - let defaultValue = property.defaultValue; - if (defaultValue !== undefined) { - elInput.setAttribute("defaultValue", defaultValue); - elInput.value = defaultValue; - } - - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, true); - } - - elPropertyElements[propertyName] = elInput; - break; - } - case 'textarea': { - elProperty.setAttribute("class", "property textarea"); - - elProperty.appendChild(elLabel); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, true); - } - - let elInput = document.createElement('textarea'); - elInput.setAttribute("id", propertyID); - if (property.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elInput); - elPropertyElements[propertyName] = elInput; - break; - } - case 'icon': { - elProperty.setAttribute("class", "property value"); - - elLabel.setAttribute("id", propertyID); - elLabel.innerHTML = " " + property.label; - - let elSpan = document.createElement('span'); - elSpan.setAttribute("id", propertyID + "-icon"); - icons[propertyName] = property.icons; + if (group.label !== undefined) { + let elLegend = document.createElement('legend'); + elLegend.innerText = group.label; + elLegend.setAttribute("class", "section-header"); + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", ".collapse-icon"); + elSpan.innerText = "M"; + elLegend.appendChild(elSpan); + elGroup.appendChild(elLegend); + } + + group.properties.forEach(function(property) { + let propertyType = property.type; + let propertyName = property.propertyName; + let propertyID = "property-" + propertyName; + propertyID = propertyID.replace(".", "-"); + + let elProperty; + if (propertyType === "sub-header") { + elProperty = document.createElement('legend'); + elProperty.innerText = property.label; + elProperty.setAttribute("class", "sub-section-header"); + } else { + elProperty = document.createElement('div'); + elProperty.setAttribute("id", "div-" + propertyName); + } + + if (group.twoColumn && property.column !== undefined && property.column !== -1) { + let columnName = group.id + "column" + property.column; + let elColumn = document.getElementById(columnName); + if (!elColumn) { + let columnDivName = group.id + "columnDiv"; + let elColumnDiv = document.getElementById(columnDivName); + if (!elColumnDiv) { + elColumnDiv = document.createElement('div'); + elColumnDiv.setAttribute("class", "two-column"); + elColumnDiv.setAttribute("id", group.id + "columnDiv"); + elGroup.appendChild(elColumnDiv); + } + elColumn = document.createElement('fieldset'); + elColumn.setAttribute("class", "column"); + elColumn.setAttribute("id", columnName); + elColumnDiv.appendChild(elColumn); + } + elColumn.appendChild(elProperty); + } else { + elGroup.appendChild(elProperty); + } + + let elLabel = document.createElement('label'); + elLabel.innerText = property.label; + elLabel.setAttribute("for", propertyID); + + switch (propertyType) { + case 'vec3': { + elProperty.setAttribute("class", "property " + property.vec3Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(property.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); + let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); + let elInputZ = createTupleNumberInput(elTuple, propertyID, property.subLabels[2], property.min, property.max, property.step); + + let inputChangeFunction; + let multiplier = 1; + if (property.multiplier !== undefined) { + multiplier = property.multiplier; + inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); + } else { + inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, elInputZ, property.multiplier); + } + elInputX.setAttribute("multiplier", multiplier); + elInputY.setAttribute("multiplier", multiplier); + elInputZ.setAttribute("multiplier", multiplier); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + elInputZ.addEventListener('change', inputChangeFunction); + + elPropertyElements[propertyName] = [elInputX, elInputY, elInputZ]; + break; + } + case 'vec2': { + elProperty.setAttribute("class", "property " + property.vec2Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(property.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); + let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); + + let inputChangeFunction; + let multiplier = 1; + if (property.multiplier !== undefined) { + multiplier = property.multiplier; + inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY); + } else { + inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, property.multiplier); + } + elInputX.setAttribute("multiplier", multiplier); + elInputY.setAttribute("multiplier", multiplier); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + + elPropertyElements[propertyName] = [elInputX, elInputY]; + break; + } + case 'color': { + elProperty.setAttribute("class", "property rgb fstuple"); + + let elColorPicker = document.createElement('div'); + elColorPicker.setAttribute("class", "color-picker"); + elColorPicker.setAttribute("id", propertyID); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputR = createTupleNumberInput(elTuple, propertyID, "red", 0, 255, 1); + let elInputG = createTupleNumberInput(elTuple, propertyID, "green", 0, 255, 1); + let elInputB = createTupleNumberInput(elTuple, propertyID, "blue", 0, 255, 1); + + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); + elInputR.addEventListener('change', inputChangeFunction); + elInputG.addEventListener('change', inputChangeFunction); + elInputB.addEventListener('change', inputChangeFunction); + + let colorPickerID = "#" + propertyID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + $(colorPickerID).attr('active', 'true'); + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elInputR.value, + "g": elInputG.value, + "b": elInputB.value + }); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + } + }); + + elPropertyElements[propertyName] = [elColorPicker, elInputR, elInputG, elInputB]; + break; + } + case 'string': { + elProperty.setAttribute("class", "property text"); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "text"); + if (property.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, false); + } + + elPropertyElements[propertyName] = elInput; + break; + } + case 'bool': { + elProperty.setAttribute("class", "property checkbox"); + + if (property.glyph !== undefined) { + elLabel.innerText = " " + property.label; + let elSpan = document.createElement('span'); + elSpan.innerHTML = property.glyph; + elLabel.insertBefore(elSpan, elLabel.firstChild); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "checkbox"); + + let inverse = property.inverse; + elInput.setAttribute("inverse", inverse ? "true" : "false"); + + elProperty.appendChild(elInput); + elProperty.appendChild(elLabel); + + let subPropertyOf = property.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.setAttribute("subPropertyOf", subPropertyOf); + elPropertyElements[propertyName] = elInput; + subProperties.push(propertyName); + elInput.addEventListener('change', function() { + updateCheckedSubProperty(subPropertyOf, properties[subPropertyOf], elInput, propertyName); + }); + } else { + elPropertyElements[propertyName] = elInput; + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, inverse)); + } + break; + } + case 'dropdown': { + elProperty.setAttribute("class", "property dropdown"); + + let elInput = document.createElement('select'); + elInput.setAttribute("id", propertyID); + + for (let optionKey in property.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = property.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + elPropertyElements[propertyName] = elInput; + break; + } + case 'number': { + elProperty.setAttribute("class", "property number"); + + addUnit(property.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "number"); + if (property.min !== undefined) { + elInput.setAttribute("min", property.min); + } + if (property.max !== undefined) { + elInput.setAttribute("max", property.max); + } + if (property.step !== undefined) { + elInput.setAttribute("step", property.step); + } + + let fixedDecimals = property.fixedDecimals !== undefined ? property.fixedDecimals : 0; + elInput.setAttribute("fixedDecimals", fixedDecimals); + + let defaultValue = property.defaultValue; + if (defaultValue !== undefined) { + elInput.setAttribute("defaultValue", defaultValue); + elInput.value = defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, true); + } + + elPropertyElements[propertyName] = elInput; + break; + } + case 'textarea': { + elProperty.setAttribute("class", "property textarea"); + + elProperty.appendChild(elLabel); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, true); + } + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", propertyID); + if (property.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elInput); + elPropertyElements[propertyName] = elInput; + break; + } + case 'icon': { + elProperty.setAttribute("class", "property value"); + + elLabel.setAttribute("id", propertyID); + elLabel.innerHTML = " " + property.label; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", propertyID + "-icon"); + icons[propertyName] = property.icons; - elProperty.appendChild(elSpan); - elProperty.appendChild(elLabel); - elPropertyElements[propertyName] = [ elSpan, elLabel ]; - break; - } - case 'buttons': { - elProperty.setAttribute("class", "property Text"); - - let hasLabel = property.label !== undefined; - if (hasLabel) { - elProperty.appendChild(elLabel); - } - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, hasLabel); - } - - elPropertyElements[propertyName] = elProperty; - break; - } - case 'sub-header': { - if (propertyName !== undefined) { - elPropertyElements[propertyName] = elProperty; - } - break; - } - } - - let showPropertyRule = property.showPropertyRule; - if (showPropertyRule !== undefined) { - let dependentProperty = Object.keys(showPropertyRule)[0]; - let dependentPropertyValue = showPropertyRule[dependentProperty]; - if (!showPropertyRules[dependentProperty]) { - showPropertyRules[dependentProperty] = []; - } - showPropertyRules[dependentProperty][propertyName] = dependentPropertyValue; - } - }); - - elGroups[group.id] = elGroup; - }); - - if (window.EventBridge !== undefined) { + elProperty.appendChild(elSpan); + elProperty.appendChild(elLabel); + elPropertyElements[propertyName] = [ elSpan, elLabel ]; + break; + } + case 'buttons': { + elProperty.setAttribute("class", "property Text"); + + let hasLabel = property.label !== undefined; + if (hasLabel) { + elProperty.appendChild(elLabel); + } + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, hasLabel); + } + + elPropertyElements[propertyName] = elProperty; + break; + } + case 'sub-header': { + if (propertyName !== undefined) { + elPropertyElements[propertyName] = elProperty; + } + break; + } + } + + let showPropertyRule = property.showPropertyRule; + if (showPropertyRule !== undefined) { + let dependentProperty = Object.keys(showPropertyRule)[0]; + let dependentPropertyValue = showPropertyRule[dependentProperty]; + if (!showPropertyRules[dependentProperty]) { + showPropertyRules[dependentProperty] = []; + } + showPropertyRules[dependentProperty][propertyName] = dependentPropertyValue; + } + }); + + elGroups[group.id] = elGroup; + }); + + if (window.EventBridge !== undefined) { var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type === "server_script_status") { - let elServerScriptError = document.getElementById("property-serverScripts-error"); - let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); elServerScriptError.value = data.errorInfo; // If we just set elServerScriptError's diplay to block or none, we still end up with // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. @@ -2125,22 +2110,22 @@ function loaded() { deleteJSONMaterialEditor(); } } - - elPropertyElements["type"][0].style.display = "none"; + + elPropertyElements["type"][0].style.display = "none"; elPropertyElements["type"][1].innerHTML = NO_SELECTION; - elPropertiesList.className = ''; - showGroupsForType("None"); - - resetProperties(); - - deleteJSONEditor(); + elPropertiesList.className = ''; + showGroupsForType("None"); + + resetProperties(); + + deleteJSONEditor(); elPropertyElements["userData"].value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); - - deleteJSONMaterialEditor(); - elPropertyElements["materialData"].value = ""; + + deleteJSONMaterialEditor(); + elPropertyElements["materialData"].value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -2149,8 +2134,8 @@ function loaded() { } else if (data.selections.length > 1) { deleteJSONEditor(); deleteJSONMaterialEditor(); - - let selections = data.selections; + + let selections = data.selections; let ids = []; let types = {}; @@ -2170,19 +2155,19 @@ function loaded() { if (numTypes === 1) { type = selections[0].properties.type; } - + elPropertyElements["type"][0].innerHTML = ICON_FOR_TYPE[type]; elPropertyElements["type"][0].style.display = "inline-block"; - elPropertyElements["type"][1].innerHTML = type + " (" + data.selections.length + ")"; - elPropertiesList.className = ''; - showGroupsForType(type); - - resetProperties(); - disableProperties(); + elPropertyElements["type"][1].innerHTML = type + " (" + data.selections.length + ")"; + elPropertiesList.className = ''; + showGroupsForType(type); + + resetProperties(); + disableProperties(); } else { properties = data.selections[0].properties; - - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + + if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { if (editor !== null) { saveUserData(); } @@ -2206,148 +2191,148 @@ function loaded() { properties.type = "Image"; } } - + // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; - showGroupsForType(properties.type); - - for (let propertyName in elPropertyElements) { - let elProperty = elPropertyElements[propertyName]; - - let propertyValue; - let splitPropertyName = propertyName.split('.'); - if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; - if (properties[propertyGroupName] === undefined || properties[propertyGroupName][subPropertyName] === undefined) { - continue; - } - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - propertyValue = properties[propertyGroupName][subPropertyName][subSubPropertyName]; - } else { - propertyValue = properties[propertyGroupName][subPropertyName]; - } - } else { - propertyValue = properties[propertyName]; - } + showGroupsForType(properties.type); + + for (let propertyName in elPropertyElements) { + let elProperty = elPropertyElements[propertyName]; + + let propertyValue; + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + if (properties[propertyGroupName] === undefined || properties[propertyGroupName][subPropertyName] === undefined) { + continue; + } + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + propertyValue = properties[propertyGroupName][subPropertyName][subSubPropertyName]; + } else { + propertyValue = properties[propertyGroupName][subPropertyName]; + } + } else { + propertyValue = properties[propertyName]; + } - // workaround for shape Color & Light Color property fields sharing same property value "color" - if (propertyValue === undefined && propertyName === "lightColor") { - propertyValue = properties["color"] - } - - let isSubProperty = subProperties.indexOf(propertyName) > -1; - if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { - continue; - } - - if (elProperty instanceof Array) { - if (elProperty[1].getAttribute("type") === "number") { // vectors - if (elProperty.length === 2 || elProperty.length === 3) { - // vec2/vec3 are array of 2/3 input numbers - elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); - elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); - if (elProperty[2] !== undefined) { - elProperty[2].value = (propertyValue.z * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); - } - } else if (elProperty.length === 4) { - // color is array of color picker and 3 input numbers - elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; - elProperty[1].value = propertyValue.red; - elProperty[2].value = propertyValue.green; - elProperty[3].value = propertyValue.blue; - } - } else if (elProperty[0].nodeName === "SPAN") { // icons - // icon is array of elSpan (icon glyph) and elLabel - elProperty[0].innerHTML = icons[propertyName][propertyValue]; - elProperty[0].style.display = "inline-block"; - elProperty[1].innerHTML = propertyValue; //+ " (" + data.selections.length + ")"; - } - } else if (elProperty.getAttribute("subPropertyOf")) { - let propertyValue = properties[elProperty.getAttribute("subPropertyOf")]; - let subProperties = propertyValue.split(","); - elProperty.checked = subProperties.indexOf(propertyName) > -1; - } else if (elProperty.getAttribute("type") === "number") { - let fixedDecimals = elProperty.getAttribute("fixedDecimals"); - elProperty.value = propertyValue.toFixed(fixedDecimals); - } else if (elProperty.getAttribute("type") === "checkbox") { - let inverse = elProperty.getAttribute("inverse") === "true"; - elProperty.checked = inverse ? !propertyValue : propertyValue; - } else if (elProperty.nodeName === "TEXTAREA") { - elProperty.value = propertyValue; - setTextareaScrolling(elProperty); - } else if (elProperty.nodeName === "SELECT") { // dropdown - elProperty.value = propertyValue; - } else { - elProperty.value = propertyValue; - } - - let propertyShowRules = showPropertyRules[propertyName]; - if (propertyShowRules !== undefined) { - for (let propertyToShow in propertyShowRules) { - let showIfThisPropertyValue = propertyShowRules[propertyToShow]; - let show = String(propertyValue) === String(showIfThisPropertyValue); - let elPropertyToShow = elPropertyElements[propertyToShow]; - if (elPropertyToShow) { - let parentNode = elPropertyToShow.parentNode; - if (parentNode === undefined && elPropertyToShow instanceof Array) { - parentNode = elPropertyToShow[0].parentNode; - } - parentNode.style.display = show ? "block" : "none"; - } - } - } - } - - let elGrabbable = elPropertyElements["grabbable"]; - let elTriggerable = elPropertyElements["triggerable"]; - let elIgnoreIK = elPropertyElements["ignoreIK"]; - elGrabbable.checked = elPropertyElements["dynamic"].checked; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - let grabbablesSet = false; - let parsedUserData = {}; - try { - parsedUserData = JSON.parse(properties.userData); - if ("grabbableKey" in parsedUserData) { - grabbablesSet = true; - let grabbableData = parsedUserData.grabbableKey; - if ("grabbable" in grabbableData) { - elGrabbable.checked = grabbableData.grabbable; - } else { - elGrabbable.checked = true; - } - if ("triggerable" in grabbableData) { - elTriggerable.checked = grabbableData.triggerable; - } else if ("wantsTrigger" in grabbableData) { - elTriggerable.checked = grabbableData.wantsTrigger; - } else { - elTriggerable.checked = false; - } - if ("ignoreIK" in grabbableData) { - elIgnoreIK.checked = grabbableData.ignoreIK; - } else { - elIgnoreIK.checked = true; - } - } - } catch (e) { - // TODO: What should go here? - } - if (!grabbablesSet) { - elGrabbable.checked = true; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - } - - if (properties.type === "Image") { + // workaround for shape Color & Light Color property fields sharing same property value "color" + if (propertyValue === undefined && propertyName === "lightColor") { + propertyValue = properties["color"] + } + + let isSubProperty = subProperties.indexOf(propertyName) > -1; + if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { + continue; + } + + if (elProperty instanceof Array) { + if (elProperty[1].getAttribute("type") === "number") { // vectors + if (elProperty.length === 2 || elProperty.length === 3) { + // vec2/vec3 are array of 2/3 input numbers + elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); + elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + if (elProperty[2] !== undefined) { + elProperty[2].value = (propertyValue.z * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + } + } else if (elProperty.length === 4) { + // color is array of color picker and 3 input numbers + elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; + elProperty[1].value = propertyValue.red; + elProperty[2].value = propertyValue.green; + elProperty[3].value = propertyValue.blue; + } + } else if (elProperty[0].nodeName === "SPAN") { // icons + // icon is array of elSpan (icon glyph) and elLabel + elProperty[0].innerHTML = icons[propertyName][propertyValue]; + elProperty[0].style.display = "inline-block"; + elProperty[1].innerHTML = propertyValue; //+ " (" + data.selections.length + ")"; + } + } else if (elProperty.getAttribute("subPropertyOf")) { + let propertyValue = properties[elProperty.getAttribute("subPropertyOf")]; + let subProperties = propertyValue.split(","); + elProperty.checked = subProperties.indexOf(propertyName) > -1; + } else if (elProperty.getAttribute("type") === "number") { + let fixedDecimals = elProperty.getAttribute("fixedDecimals"); + elProperty.value = propertyValue.toFixed(fixedDecimals); + } else if (elProperty.getAttribute("type") === "checkbox") { + let inverse = elProperty.getAttribute("inverse") === "true"; + elProperty.checked = inverse ? !propertyValue : propertyValue; + } else if (elProperty.nodeName === "TEXTAREA") { + elProperty.value = propertyValue; + setTextareaScrolling(elProperty); + } else if (elProperty.nodeName === "SELECT") { // dropdown + elProperty.value = propertyValue; + } else { + elProperty.value = propertyValue; + } + + let propertyShowRules = showPropertyRules[propertyName]; + if (propertyShowRules !== undefined) { + for (let propertyToShow in propertyShowRules) { + let showIfThisPropertyValue = propertyShowRules[propertyToShow]; + let show = String(propertyValue) === String(showIfThisPropertyValue); + let elPropertyToShow = elPropertyElements[propertyToShow]; + if (elPropertyToShow) { + let parentNode = elPropertyToShow.parentNode; + if (parentNode === undefined && elPropertyToShow instanceof Array) { + parentNode = elPropertyToShow[0].parentNode; + } + parentNode.style.display = show ? "block" : "none"; + } + } + } + } + + let elGrabbable = elPropertyElements["grabbable"]; + let elTriggerable = elPropertyElements["triggerable"]; + let elIgnoreIK = elPropertyElements["ignoreIK"]; + elGrabbable.checked = elPropertyElements["dynamic"].checked; + elTriggerable.checked = false; + elIgnoreIK.checked = true; + let grabbablesSet = false; + let parsedUserData = {}; + try { + parsedUserData = JSON.parse(properties.userData); + if ("grabbableKey" in parsedUserData) { + grabbablesSet = true; + let grabbableData = parsedUserData.grabbableKey; + if ("grabbable" in grabbableData) { + elGrabbable.checked = grabbableData.grabbable; + } else { + elGrabbable.checked = true; + } + if ("triggerable" in grabbableData) { + elTriggerable.checked = grabbableData.triggerable; + } else if ("wantsTrigger" in grabbableData) { + elTriggerable.checked = grabbableData.wantsTrigger; + } else { + elTriggerable.checked = false; + } + if ("ignoreIK" in grabbableData) { + elIgnoreIK.checked = grabbableData.ignoreIK; + } else { + elIgnoreIK.checked = true; + } + } + } catch (e) { + // TODO: What should go here? + } + if (!grabbablesSet) { + elGrabbable.checked = true; + elTriggerable.checked = false; + elIgnoreIK.checked = true; + } + + if (properties.type === "Image") { var imageLink = JSON.parse(properties.textures)["tex.picture"]; elPropertyElements["image"].value = imageLink; - } else if (properties.type === "Material") { - let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; - let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; - let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; - if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { + } else if (properties.type === "Material") { + let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; + let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; + let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; + if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { elParentMaterialNameString.value = properties.parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = false; @@ -2356,9 +2341,9 @@ function loaded() { showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = true; } - } - - let json = null; + } + + let json = null; try { json = JSON.parse(properties.userData); } catch (e) { @@ -2368,7 +2353,7 @@ function loaded() { showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); - hideUserDataSaved(); + hideUserDataSaved(); } if (json !== null) { if (editor === null) { @@ -2377,8 +2362,8 @@ function loaded() { setEditorJSON(json); showSaveUserDataButton(); hideUserDataTextArea(); - hideNewJSONEditorButton(); - hideUserDataSaved(); + hideNewJSONEditorButton(); + hideUserDataSaved(); } let materialJson = null; @@ -2391,7 +2376,7 @@ function loaded() { showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); - hideMaterialDataSaved(); + hideMaterialDataSaved(); } if (materialJson !== null) { if (materialEditor === null) { @@ -2401,19 +2386,19 @@ function loaded() { showSaveMaterialDataButton(); hideMaterialDataTextArea(); hideNewJSONMaterialEditorButton(); - hideMaterialDataSaved(); + hideMaterialDataSaved(); } - - if (properties.locked) { - disableProperties(); - elPropertyElements["locked"].removeAttribute('disabled'); - } else { - enableProperties(); - disableSaveUserDataButton(); - disableSaveMaterialDataButton() - } - - var activeElement = document.activeElement; + + if (properties.locked) { + disableProperties(); + elPropertyElements["locked"].removeAttribute('disabled'); + } else { + enableProperties(); + disableSaveUserDataButton(); + disableSaveMaterialDataButton() + } + + var activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); } @@ -2421,79 +2406,79 @@ function loaded() { } }); } - - // Server Script Status - let elServerScript = elPropertyElements["serverScripts"]; - let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "property"); - let elLabel = document.createElement('label'); - elLabel.setAttribute("for", "property-serverScripts-status"); - elLabel.innerText = "Server Script Status"; - let elServerScriptStatus = document.createElement('span'); - elServerScriptStatus.setAttribute("id", "property-serverScripts-status"); - elDiv.appendChild(elLabel); - elDiv.appendChild(elServerScriptStatus); - elServerScript.parentNode.appendChild(elDiv); - - // Server Script Error - elDiv = document.createElement('div'); - elDiv.setAttribute("class", "property"); - let elServerScriptError = document.createElement('textarea'); - elServerScriptError.setAttribute("id", "property-serverScripts-error"); - elDiv.appendChild(elServerScriptError); - elServerScript.parentNode.appendChild(elDiv); - - let elScript = elPropertyElements["script"]; - elScript.parentNode.setAttribute("class", "property url refresh"); - elServerScript.parentNode.setAttribute("class", "property url refresh"); - - // User Data - let elUserData = elPropertyElements["userData"]; - elDiv = elUserData.parentNode; - let elStaticUserData = document.createElement('div'); - elStaticUserData.setAttribute("id", "property-userData-static"); - let elUserDataEditor = document.createElement('div'); - elUserDataEditor.setAttribute("id", "property-userData-editor"); - let elUserDataSaved = document.createElement('span'); - elUserDataSaved.setAttribute("id", "property-userData-saved"); - elUserDataSaved.innerText = "Saved!"; - elDiv.childNodes[2].appendChild(elUserDataSaved); - elDiv.insertBefore(elStaticUserData, elUserData); - elDiv.insertBefore(elUserDataEditor, elUserData); - - // Material Data - let elMaterialData = elPropertyElements["materialData"]; - elDiv = elMaterialData.parentNode; - let elStaticMaterialData = document.createElement('div'); - elStaticMaterialData.setAttribute("id", "property-materialData-static"); - let elMaterialDataEditor = document.createElement('div'); - elMaterialDataEditor.setAttribute("id", "property-materialData-editor"); - let elMaterialDataSaved = document.createElement('span'); - elMaterialDataSaved.setAttribute("id", "property-materialData-saved"); - elMaterialDataSaved.innerText = "Saved!"; - elDiv.childNodes[2].appendChild(elMaterialDataSaved); - elDiv.insertBefore(elStaticMaterialData, elMaterialData); - elDiv.insertBefore(elMaterialDataEditor, elMaterialData); - - // User Data Fields - let elGrabbable = elPropertyElements["grabbable"]; - let elTriggerable = elPropertyElements["triggerable"]; - let elIgnoreIK = elPropertyElements["ignoreIK"]; - elGrabbable.addEventListener('change', function() { + + // Server Script Status + let elServerScript = elPropertyElements["serverScripts"]; + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "property"); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", "property-serverScripts-status"); + elLabel.innerText = "Server Script Status"; + let elServerScriptStatus = document.createElement('span'); + elServerScriptStatus.setAttribute("id", "property-serverScripts-status"); + elDiv.appendChild(elLabel); + elDiv.appendChild(elServerScriptStatus); + elServerScript.parentNode.appendChild(elDiv); + + // Server Script Error + elDiv = document.createElement('div'); + elDiv.setAttribute("class", "property"); + let elServerScriptError = document.createElement('textarea'); + elServerScriptError.setAttribute("id", "property-serverScripts-error"); + elDiv.appendChild(elServerScriptError); + elServerScript.parentNode.appendChild(elDiv); + + let elScript = elPropertyElements["script"]; + elScript.parentNode.setAttribute("class", "property url refresh"); + elServerScript.parentNode.setAttribute("class", "property url refresh"); + + // User Data + let elUserData = elPropertyElements["userData"]; + elDiv = elUserData.parentNode; + let elStaticUserData = document.createElement('div'); + elStaticUserData.setAttribute("id", "property-userData-static"); + let elUserDataEditor = document.createElement('div'); + elUserDataEditor.setAttribute("id", "property-userData-editor"); + let elUserDataSaved = document.createElement('span'); + elUserDataSaved.setAttribute("id", "property-userData-saved"); + elUserDataSaved.innerText = "Saved!"; + elDiv.childNodes[2].appendChild(elUserDataSaved); + elDiv.insertBefore(elStaticUserData, elUserData); + elDiv.insertBefore(elUserDataEditor, elUserData); + + // Material Data + let elMaterialData = elPropertyElements["materialData"]; + elDiv = elMaterialData.parentNode; + let elStaticMaterialData = document.createElement('div'); + elStaticMaterialData.setAttribute("id", "property-materialData-static"); + let elMaterialDataEditor = document.createElement('div'); + elMaterialDataEditor.setAttribute("id", "property-materialData-editor"); + let elMaterialDataSaved = document.createElement('span'); + elMaterialDataSaved.setAttribute("id", "property-materialData-saved"); + elMaterialDataSaved.innerText = "Saved!"; + elDiv.childNodes[2].appendChild(elMaterialDataSaved); + elDiv.insertBefore(elStaticMaterialData, elMaterialData); + elDiv.insertBefore(elMaterialDataEditor, elMaterialData); + + // User Data Fields + let elGrabbable = elPropertyElements["grabbable"]; + let elTriggerable = elPropertyElements["triggerable"]; + let elIgnoreIK = elPropertyElements["ignoreIK"]; + elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); - elTriggerable.addEventListener('change', function() { + elTriggerable.addEventListener('change', function() { userDataChanger("grabbableKey", "triggerable", elTriggerable, elUserData, false, ['wantsTrigger']); }); elIgnoreIK.addEventListener('change', function() { userDataChanger("grabbableKey", "ignoreIK", elIgnoreIK, elUserData, true); }); - - // Special Property Callbacks - let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; - let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; - let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; - elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); + + // Special Property Callbacks + let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; + let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; + let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; + elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); elParentMaterialNameNumber.addEventListener('change', function () { updateProperty("parentMaterialName", this.value); }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { @@ -2504,43 +2489,43 @@ function loaded() { showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); } }); - - elPropertyElements["image"].addEventListener('change', createImageURLUpdateFunction('textures')); - - // Collapsible sections - let elCollapsible = document.getElementsByClassName("section-header"); + + elPropertyElements["image"].addEventListener('change', createImageURLUpdateFunction('textures')); + + // Collapsible sections + let elCollapsible = document.getElementsByClassName("section-header"); - let toggleCollapsedEvent = function(event) { - let element = event.target.parentNode.parentNode; - let isCollapsed = element.dataset.collapsed !== "true"; - element.dataset.collapsed = isCollapsed ? "true" : false; - element.setAttribute("collapsed", isCollapsed ? "true" : "false"); - element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; - }; + let toggleCollapsedEvent = function(event) { + let element = event.target.parentNode.parentNode; + let isCollapsed = element.dataset.collapsed !== "true"; + element.dataset.collapsed = isCollapsed ? "true" : false; + element.setAttribute("collapsed", isCollapsed ? "true" : "false"); + element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; + }; - for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { - let curCollapsibleElement = elCollapsible[collapseIndex]; - curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); - } - - // Textarea scrollbars - let elTextareas = document.getElementsByTagName("TEXTAREA"); + for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + let curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); + } + + // Textarea scrollbars + let elTextareas = document.getElementsByTagName("TEXTAREA"); - let textareaOnChangeEvent = function(event) { - setTextareaScrolling(event.target); - }; + let textareaOnChangeEvent = function(event) { + setTextareaScrolling(event.target); + }; - for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { - let curTextAreaElement = elTextareas[textAreaIndex]; - setTextareaScrolling(curTextAreaElement); - curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); - curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); - /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize - event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ - curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); - } - - document.addEventListener("keydown", function (keyDown) { + for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + let curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); + /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize + event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } + + document.addEventListener("keydown", function (keyDown) { if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { if (keyDown.shiftKey) { EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); @@ -2549,28 +2534,28 @@ function loaded() { } } }); - + window.onblur = function() { // Fake a change event let ev = document.createEvent("HTMLEvents"); ev.initEvent("change", true, true); document.activeElement.dispatchEvent(ev); }; - - // For input and textarea elements, select all of the text on focus + + // For input and textarea elements, select all of the text on focus let els = document.querySelectorAll("input, textarea"); for (let i = 0; i < els.length; i++) { els[i].onfocus = function (e) { e.target.select(); }; } - - bindAllNonJSONEditorElements(); + + bindAllNonJSONEditorElements(); - showGroupsForType("None"); - resetProperties(); - disableProperties(); - }); + showGroupsForType("None"); + resetProperties(); + disableProperties(); + }); augmentSpinButtons(); From 8642edd144242f061b493e843ed68d2805c5ddcf Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 13 Sep 2018 18:18:23 -0700 Subject: [PATCH 027/131] Added acceleration limit filter to controller system --- interface/resources/controllers/vive.json | 9 +- .../src/controllers/impl/Filter.cpp | 2 + .../filters/AccelerationLimiterFilter.cpp | 163 ++++++++++++++++++ .../impl/filters/AccelerationLimiterFilter.h | 40 +++++ 4 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp create mode 100644 libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 8a7744efb3..442584a2ce 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -51,8 +51,13 @@ { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, - { "from": "Vive.LeftHand", "to": "Standard.LeftHand"}, - { "from": "Vive.RightHand", "to": "Standard.RightHand"}, + { "from": "Vive.LeftHand", "to": "Standard.LeftHand", + "filters" : [{"type" : "accelerationLimiter", "rotationLimit" : 4000.0, "translationLimit": 200.0}] + }, + + { "from": "Vive.RightHand", "to": "Standard.RightHand", + "filters" : [{"type" : "accelerationLimiter", "rotationLimit" : 2000.0, "translationLimit": 100.0}] + }, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp index 6e6dc816d0..f230fb83dc 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -31,6 +31,7 @@ #include "filters/RotateFilter.h" #include "filters/LowVelocityFilter.h" #include "filters/ExponentialSmoothingFilter.h" +#include "filters/AccelerationLimiterFilter.h" using namespace controller; @@ -51,6 +52,7 @@ REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform") REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate") REGISTER_FILTER_CLASS_INSTANCE(LowVelocityFilter, "lowVelocity") REGISTER_FILTER_CLASS_INSTANCE(ExponentialSmoothingFilter, "exponentialSmoothing") +REGISTER_FILTER_CLASS_INSTANCE(AccelerationLimiterFilter, "accelerationLimiter") const QString JSON_FILTER_TYPE = QStringLiteral("type"); const QString JSON_FILTER_PARAMS = QStringLiteral("params"); diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp new file mode 100644 index 0000000000..1f63b28786 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -0,0 +1,163 @@ +// +// Created by Anthony Thibault 2018/11/09 +// Copyright 2018 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 "AccelerationLimiterFilter.h" + +#include +#include +#include "../../UserInputMapper.h" +#include "../../Input.h" +#include +#include +#include + +static const QString JSON_ROTATION_LIMIT = QStringLiteral("rotationLimit"); +static const QString JSON_TRANSLATION_LIMIT = QStringLiteral("translationLimit"); + +static glm::vec3 angularVelFromDeltaRot(const glm::quat& deltaQ, float dt) { + // Measure the angular velocity of a delta rotation quaternion by using quaternion logarithm. + // The logarithm of a unit quternion returns the axis of rotation with a length of one half the angle of rotation in the imaginary part. + // The real part will be 0. Then we multiply it by 2 / dt. turning it into the angular velocity, (except for the extra w = 0 part). + glm::quat omegaQ((2.0f / dt) * glm::log(deltaQ)); + return glm::vec3(omegaQ.x, omegaQ.y, omegaQ.z); +} + +static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { + // Convert angular velocity into a delta quaternion by using quaternion exponent. + // The exponent of quaternion will return a delta rotation around the axis of the imaginary part, by twice the angle as determined by the length of that imaginary part. + // It is the inverse of the logarithm step in angularVelFromDeltaRot + glm::quat omegaQ(0.0f, omega.x, omega.y, omega.z); + return glm::exp((dt / 2.0f) * omegaQ); +} + +static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, float dt, const float aLimit) { + + // measure the linear velocities of this step and the previoius step + glm::vec3 v1 = (x3 - x1) / (2.0f * dt); + glm::vec3 v0 = (x2 - x0) / (2.0f * dt); + + // compute the acceleration + const glm::vec3 a = (v1 - v0) / dt; + + // clamp the acceleration if it is over the limit + float aLen = glm::length(a); + + if (aLen > aLimit) { + // Solve for a new `v1`, such that `a` does not exceed `aLimit` + // This combines two steps: + // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: + // `newA = a * (aLimit / aLen)` + // 2) Computing new `v1` + // `v1 = newA * dt + v0` + // We combine the scalars from step 1 and step 2 into a single term to avoid having to do multiple scalar-vec3 multiplies. + v1 = a * ((aLimit * dt) / aLen) + v0; + + // apply limited v1 to compute filtered x3 + return v1 * dt + x2; + } else { + // did not exceed limit, no filtering necesary + return x3; + } +} + +static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, float dt, const float aLimit) { + + // ensure quaternions have the same polarity + glm::quat q0 = q0In; + glm::quat q1 = glm::dot(q0In, q1In) < 0.0f ? -q1In : q1In; + glm::quat q2 = glm::dot(q1In, q2In) < 0.0f ? -q2In : q2In; + glm::quat q3 = glm::dot(q2In, q3In) < 0.0f ? -q3In : q3In; + + // measure the angular velocities of this step and the previous step + glm::vec3 w1 = angularVelFromDeltaRot(q3 * glm::inverse(q1), 2.0f * dt); + glm::vec3 w0 = angularVelFromDeltaRot(q2 * glm::inverse(q0), 2.0f * dt); + + const glm::vec3 a = (w1 - w0) / dt; + + // clamp the acceleration if it is over the limit + float aLen = glm::length(a); + if (aLen > aLimit) { + // solve for a new w1, such that a does not exceed the accLimit + w1 = a * ((aLimit * dt) / aLen) + w0; + + // apply limited w1 to compute filtered q3 + return deltaRotFromAngularVel(w1, dt) * q2; + } else { + // did not exceed limit, no filtering necesary + return q3; + } +} + +namespace controller { + + Pose AccelerationLimiterFilter::apply(Pose value) const { + + if (value.isValid()) { + + // to perform filtering in sensor space, we need to compute the transformations. + auto userInputMapper = DependencyManager::get(); + const InputCalibrationData calibrationData = userInputMapper->getInputCalibrationData(); + glm::mat4 sensorToAvatarMat = glm::inverse(calibrationData.avatarMat) * calibrationData.sensorToWorldMat; + glm::mat4 avatarToSensorMat = glm::inverse(calibrationData.sensorToWorldMat) * calibrationData.avatarMat; + + // transform pose into sensor space. + Pose sensorValue = value.transform(avatarToSensorMat); + + if (_prevValid) { + + const float DELTA_TIME = 0.01111111f; + + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, DELTA_TIME, _translationLimit); + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, DELTA_TIME, _rotationLimit); + + // remember previous values. + _prevPos[0] = _prevPos[1]; + _prevPos[1] = _prevPos[2]; + _prevPos[2] = sensorValue.translation; + _prevRot[0] = _prevRot[1]; + _prevRot[1] = _prevRot[2]; + _prevRot[2] = sensorValue.rotation; + + // transform back into avatar space + return sensorValue.transform(sensorToAvatarMat); + } else { + // initialize previous values with the current sample. + _prevPos[0] = sensorValue.translation; + _prevPos[1] = sensorValue.translation; + _prevPos[2] = sensorValue.translation; + _prevRot[0] = sensorValue.rotation; + _prevRot[1] = sensorValue.rotation; + _prevRot[2] = sensorValue.rotation; + _prevValid = true; + + // no previous value to smooth with, so return value unchanged + return value; + } + } else { + // mark previous poses as invalid. + _prevValid = false; + + // return invalid value unchanged + return value; + } + } + + bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { + if (parameters.isObject()) { + auto obj = parameters.toObject(); + if (obj.contains(JSON_ROTATION_LIMIT) && obj.contains(JSON_TRANSLATION_LIMIT)) { + _rotationLimit = (float)obj[JSON_ROTATION_LIMIT].toDouble(); + _translationLimit = (float)obj[JSON_TRANSLATION_LIMIT].toDouble(); + return true; + } + } + return false; + } + +} diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h new file mode 100644 index 0000000000..22a1ca530b --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -0,0 +1,40 @@ +// +// Created by Anthony Thibault 2018/11/09 +// Copyright 2018 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_Controllers_Filters_Acceleration_Limiter_h +#define hifi_Controllers_Filters_Acceleration_Limiter_h + +#include "../Filter.h" + +namespace controller { + + class AccelerationLimiterFilter : public Filter { + REGISTER_FILTER_CLASS(AccelerationLimiterFilter); + + public: + AccelerationLimiterFilter() {} + AccelerationLimiterFilter(float rotationLimit, float translationLimit) : + _rotationLimit(rotationLimit), + _translationLimit(translationLimit) {} + + float apply(float value) const override { return value; } + Pose apply(Pose value) const override; + bool parseParameters(const QJsonValue& parameters) override; + + private: + float _rotationLimit { FLT_MAX }; + float _translationLimit { FLT_MAX }; + + mutable glm::vec3 _prevPos[3]; // sensor space + mutable glm::quat _prevRot[3]; // sensor space + mutable bool _prevValid { false }; + }; + +} + +#endif From d15cc86735cc65a2fafaef4e2cd71040efb07db2 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 18 Sep 2018 11:53:17 -0700 Subject: [PATCH 028/131] Added deceleration limit to AccelerationLimiterFilter --- interface/resources/controllers/vive.json | 8 +++- .../filters/AccelerationLimiterFilter.cpp | 41 ++++++++++++------- .../impl/filters/AccelerationLimiterFilter.h | 9 ++-- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 442584a2ce..e737fec594 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -52,11 +52,15 @@ { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand", - "filters" : [{"type" : "accelerationLimiter", "rotationLimit" : 4000.0, "translationLimit": 200.0}] + "filters" : [{"type" : "accelerationLimiter", + "rotationAccelerationLimit" : 4000.0, "rotationDecelerationLimit" : 8000.0, + "translationAccelerationLimit": 200.0, "translationDecelerationLimit": 400.0}] }, { "from": "Vive.RightHand", "to": "Standard.RightHand", - "filters" : [{"type" : "accelerationLimiter", "rotationLimit" : 2000.0, "translationLimit": 100.0}] + "filters" : [{"type" : "accelerationLimiter", + "rotationAccelerationLimit" : 2000.0, "rotationDecelerationLimit" : 4000.0, + "translationAccelerationLimit": 100.0, "translationDecelerationLimit": 200.0}] }, { diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp index 1f63b28786..234dff1c65 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -17,8 +17,10 @@ #include #include -static const QString JSON_ROTATION_LIMIT = QStringLiteral("rotationLimit"); -static const QString JSON_TRANSLATION_LIMIT = QStringLiteral("translationLimit"); +static const QString JSON_ROTATION_ACCELERATION_LIMIT = QStringLiteral("rotationAccelerationLimit"); +static const QString JSON_ROTATION_DECELERATION_LIMIT = QStringLiteral("rotationDecelerationLimit"); +static const QString JSON_TRANSLATION_ACCELERATION_LIMIT = QStringLiteral("translationAccelerationLimit"); +static const QString JSON_TRANSLATION_DECELERATION_LIMIT = QStringLiteral("translationDecelerationLimit"); static glm::vec3 angularVelFromDeltaRot(const glm::quat& deltaQ, float dt) { // Measure the angular velocity of a delta rotation quaternion by using quaternion logarithm. @@ -36,7 +38,7 @@ static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { return glm::exp((dt / 2.0f) * omegaQ); } -static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, float dt, const float aLimit) { +static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, float dt, const float accLimit, const float decLimit) { // measure the linear velocities of this step and the previoius step glm::vec3 v1 = (x3 - x1) / (2.0f * dt); @@ -48,7 +50,10 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con // clamp the acceleration if it is over the limit float aLen = glm::length(a); - if (aLen > aLimit) { + // pick limit based on if we are accelerating or decelerating. + float limit = glm::length(v1) > glm::length(v0) ? accLimit : decLimit; + + if (aLen > limit) { // Solve for a new `v1`, such that `a` does not exceed `aLimit` // This combines two steps: // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: @@ -56,7 +61,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con // 2) Computing new `v1` // `v1 = newA * dt + v0` // We combine the scalars from step 1 and step 2 into a single term to avoid having to do multiple scalar-vec3 multiplies. - v1 = a * ((aLimit * dt) / aLen) + v0; + v1 = a * ((limit * dt) / aLen) + v0; // apply limited v1 to compute filtered x3 return v1 * dt + x2; @@ -66,7 +71,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con } } -static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, float dt, const float aLimit) { +static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, float dt, const float accLimit, const float decLimit) { // ensure quaternions have the same polarity glm::quat q0 = q0In; @@ -79,12 +84,15 @@ static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, co glm::vec3 w0 = angularVelFromDeltaRot(q2 * glm::inverse(q0), 2.0f * dt); const glm::vec3 a = (w1 - w0) / dt; + float aLen = glm::length(a); + + // pick limit based on if we are accelerating or decelerating. + float limit = glm::length(w1) > glm::length(w0) ? accLimit : decLimit; // clamp the acceleration if it is over the limit - float aLen = glm::length(a); - if (aLen > aLimit) { + if (aLen > limit) { // solve for a new w1, such that a does not exceed the accLimit - w1 = a * ((aLimit * dt) / aLen) + w0; + w1 = a * ((limit * dt) / aLen) + w0; // apply limited w1 to compute filtered q3 return deltaRotFromAngularVel(w1, dt) * q2; @@ -113,8 +121,10 @@ namespace controller { const float DELTA_TIME = 0.01111111f; - sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, DELTA_TIME, _translationLimit); - sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, DELTA_TIME, _rotationLimit); + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, + DELTA_TIME, _translationAccelerationLimit, _translationDecelerationLimit); + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, + DELTA_TIME, _rotationAccelerationLimit, _rotationDecelerationLimit); // remember previous values. _prevPos[0] = _prevPos[1]; @@ -151,9 +161,12 @@ namespace controller { bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { if (parameters.isObject()) { auto obj = parameters.toObject(); - if (obj.contains(JSON_ROTATION_LIMIT) && obj.contains(JSON_TRANSLATION_LIMIT)) { - _rotationLimit = (float)obj[JSON_ROTATION_LIMIT].toDouble(); - _translationLimit = (float)obj[JSON_TRANSLATION_LIMIT].toDouble(); + if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_ROTATION_DECELERATION_LIMIT) && + obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_DECELERATION_LIMIT)) { + _rotationAccelerationLimit = (float)obj[JSON_ROTATION_ACCELERATION_LIMIT].toDouble(); + _rotationDecelerationLimit = (float)obj[JSON_ROTATION_DECELERATION_LIMIT].toDouble(); + _translationAccelerationLimit = (float)obj[JSON_TRANSLATION_ACCELERATION_LIMIT].toDouble(); + _translationDecelerationLimit = (float)obj[JSON_TRANSLATION_DECELERATION_LIMIT].toDouble(); return true; } } diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h index 22a1ca530b..6a6c7f8c33 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -18,17 +18,16 @@ namespace controller { public: AccelerationLimiterFilter() {} - AccelerationLimiterFilter(float rotationLimit, float translationLimit) : - _rotationLimit(rotationLimit), - _translationLimit(translationLimit) {} float apply(float value) const override { return value; } Pose apply(Pose value) const override; bool parseParameters(const QJsonValue& parameters) override; private: - float _rotationLimit { FLT_MAX }; - float _translationLimit { FLT_MAX }; + float _rotationAccelerationLimit { FLT_MAX }; + float _rotationDecelerationLimit { FLT_MAX }; + float _translationAccelerationLimit { FLT_MAX }; + float _translationDecelerationLimit { FLT_MAX }; mutable glm::vec3 _prevPos[3]; // sensor space mutable glm::quat _prevRot[3]; // sensor space From 7777f3edd0233f8850a25edc9c963ce4517c4c7f Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 19 Sep 2018 16:08:16 -0700 Subject: [PATCH 029/131] Added OutOfRangeDataStrategy parameter to Controller Settings The openvr SDK provides a way to gauge the quality of tracking on a given device via the eTrackingResult enum. * Drop - Only Running_OK is considered valid, all other eTrackingResults will return invalid poses. * Freeze - Only Running_OK is considered valid, but other valid TrackingResults will return the last Running_OK pose. In esseces this will freeze the puck in place at the last good value. * None - All valid eTrackingResults will be valid, including OutOfRange and Calibrating results. This is the default. --- interface/resources/controllers/vive.json | 13 +-- .../qml/hifi/tablet/OpenVrConfiguration.qml | 41 +++++++- plugins/openvr/src/ViveControllerManager.cpp | 95 ++++++++++++++----- plugins/openvr/src/ViveControllerManager.h | 8 ++ 4 files changed, 118 insertions(+), 39 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index e737fec594..8a7744efb3 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -51,17 +51,8 @@ { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, - { "from": "Vive.LeftHand", "to": "Standard.LeftHand", - "filters" : [{"type" : "accelerationLimiter", - "rotationAccelerationLimit" : 4000.0, "rotationDecelerationLimit" : 8000.0, - "translationAccelerationLimit": 200.0, "translationDecelerationLimit": 400.0}] - }, - - { "from": "Vive.RightHand", "to": "Standard.RightHand", - "filters" : [{"type" : "accelerationLimiter", - "rotationAccelerationLimit" : 2000.0, "rotationDecelerationLimit" : 4000.0, - "translationAccelerationLimit": 100.0, "translationDecelerationLimit": 200.0}] - }, + { "from": "Vive.LeftHand", "to": "Standard.LeftHand"}, + { "from": "Vive.RightHand", "to": "Standard.RightHand"}, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index c2aff08e35..f91642105f 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -822,11 +822,44 @@ Flickable { } } + Row { + id: outOfRangeDataStrategyRow + anchors.top: viveInDesktop.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 15 + + RalewayRegular { + id: outOfRangeDataStrategyLabel + size: 12 + text: "Out Of Range Data Strategy:" + color: hifi.colors.lightGrayText + topPadding: 5 + } + + HifiControls.ComboBox { + id: outOfRangeDataStrategyComboBox + + height: 25 + width: 100 + + editable: true + colorScheme: hifi.colorSchemes.dark + model: ["None", "Freeze", "Drop"] + label: "" + + onCurrentIndexChanged: { + sendConfigurationSettings(); + } + } + } + RalewayBold { id: viveDesktopText - size: 10 + size: 12 text: "Use " + stack.selectedPlugin + " devices in desktop mode" - color: hifi.colors.white + color: hifi.colors.lightGrayText anchors { left: viveInDesktop.right @@ -946,6 +979,7 @@ Flickable { viveInDesktop.checked = desktopMode; hmdInDesktop.checked = hmdDesktopPosition; + outOfRangeDataStrategyComboBox.currentIndex = outOfRangeDataStrategyComboBox.model.indexOf(settings.outOfRangeDataStrategy); initializeButtonState(); updateCalibrationText(); @@ -1107,7 +1141,8 @@ Flickable { "armCircumference": armCircumference.realValue, "shoulderWidth": shoulderWidth.realValue, "desktopMode": viveInDesktop.checked, - "hmdDesktopTracking": hmdInDesktop.checked + "hmdDesktopTracking": hmdInDesktop.checked, + "outOfRangeDataStrategy": outOfRangeDataStrategyComboBox.model[outOfRangeDataStrategyComboBox.currentIndex] } return settingsObject; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 3e26f304f8..cc3ca523df 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -129,6 +129,28 @@ static glm::mat4 calculateResetMat() { return glm::mat4(); } +static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeDataStrategy strategy) { + switch (strategy) { + default: + case ViveControllerManager::OutOfRangeDataStrategy::None: + return "None"; + case ViveControllerManager::OutOfRangeDataStrategy::Freeze: + return "Freeze"; + case ViveControllerManager::OutOfRangeDataStrategy::Drop: + return "Drop"; + } +} + +static ViveControllerManager::OutOfRangeDataStrategy stringToOutOfRangeDataStrategy(const QString& string) { + if (string == "Drop") { + return ViveControllerManager::OutOfRangeDataStrategy::Drop; + } else if (string == "Freeze") { + return ViveControllerManager::OutOfRangeDataStrategy::Freeze; + } else { + return ViveControllerManager::OutOfRangeDataStrategy::None; + } +} + bool ViveControllerManager::isDesktopMode() { if (_container) { return !_container->getActiveDisplayPlugin()->isHmd(); @@ -288,8 +310,10 @@ void ViveControllerManager::loadSettings() { if (_inputDevice) { const double DEFAULT_ARM_CIRCUMFERENCE = 0.33; const double DEFAULT_SHOULDER_WIDTH = 0.48; + const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "None"; _inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble(); _inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble(); + _inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString()); } } settings.endGroup(); @@ -303,6 +327,7 @@ void ViveControllerManager::saveSettings() const { if (_inputDevice) { settings.setValue(QString("armCircumference"), _inputDevice->_armCircumference); settings.setValue(QString("shoulderWidth"), _inputDevice->_shoulderWidth); + settings.setValue(QString("outOfRangeDataStrategy"), outOfRangeDataStrategyToString(_inputDevice->_outOfRangeDataStrategy)); } } settings.endGroup(); @@ -446,6 +471,8 @@ void ViveControllerManager::InputDevice::configureCalibrationSettings(const QJso hmdDesktopTracking = iter.value().toBool(); } else if (iter.key() == "desktopMode") { hmdDesktopMode = iter.value().toBool(); + } else if (iter.key() == "outOfRangeDataStrategy") { + _outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(iter.value().toString()); } iter++; } @@ -468,6 +495,7 @@ QJsonObject ViveControllerManager::InputDevice::configurationSettings() { configurationSettings["puckCount"] = (int)_validTrackedObjects.size(); configurationSettings["armCircumference"] = (double)_armCircumference * M_TO_CM; configurationSettings["shoulderWidth"] = (double)_shoulderWidth * M_TO_CM; + configurationSettings["outOfRangeDataStrategy"] = outOfRangeDataStrategyToString(_outOfRangeDataStrategy); return configurationSettings; } @@ -484,6 +512,10 @@ void ViveControllerManager::InputDevice::emitCalibrationStatus() { emit inputConfiguration->calibrationStatus(status); } +static controller::Pose buildPose(const glm::mat4& mat, const glm::vec3& linearVelocity, const glm::vec3& angularVelocity) { + return controller::Pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity); +} + void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { uint32_t poseIndex = controller::TRACKED_OBJECT_00 + deviceIndex; printDeviceTrackingResultChange(deviceIndex); @@ -492,35 +524,48 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid && poseIndex <= controller::TRACKED_OBJECT_15) { - mat4& mat = mat4(); - vec3 linearVelocity = vec3(); - vec3 angularVelocity = vec3(); - // check if the device is tracking out of range, then process the correct pose depending on the result. - if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult != vr::TrackingResult_Running_OutOfRange) { - mat = _nextSimPoseData.poses[deviceIndex]; - linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; - angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex]; - } else { - mat = _lastSimPoseData.poses[deviceIndex]; - linearVelocity = _lastSimPoseData.linearVelocities[deviceIndex]; - angularVelocity = _lastSimPoseData.angularVelocities[deviceIndex]; - - // make sure that we do not overwrite the pose in the _lastSimPose with incorrect data. - _nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex]; - _nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex]; - _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; + controller::Pose pose; + switch (_outOfRangeDataStrategy) { + case OutOfRangeDataStrategy::Drop: + default: + // Drop - Mark all non Running_OK results as invald + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) { + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + } else { + pose.valid = false; + } + break; + case OutOfRangeDataStrategy::None: + // None - Ignore eTrackingResult all together + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + break; + case OutOfRangeDataStrategy::Freeze: + // Freeze - Dont invalide non Running_OK poses, instead just return the last good pose. + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) { + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + } else { + pose = buildPose(_lastSimPoseData.poses[deviceIndex], _lastSimPoseData.linearVelocities[deviceIndex], _lastSimPoseData.angularVelocities[deviceIndex]); + // make sure that we do not overwrite the pose in the _lastSimPose with incorrect data. + _nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex]; + _nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex]; + _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; + } + break; } - controller::Pose pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity); + if (pose.valid) { + // transform into avatar frame + glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; + _poseStateMap[poseIndex] = pose.transform(controllerToAvatar); - // transform into avatar frame - glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; - _poseStateMap[poseIndex] = pose.transform(controllerToAvatar); - - // but _validTrackedObjects remain in sensor frame - _validTrackedObjects.push_back(std::make_pair(poseIndex, pose)); - _trackedControllers++; + // but _validTrackedObjects remain in sensor frame + _validTrackedObjects.push_back(std::make_pair(poseIndex, pose)); + _trackedControllers++; + } else { + // insert invalid pose into state map + _poseStateMap[poseIndex] = pose; + } } else { controller::Pose invalidPose; _poseStateMap[poseIndex] = invalidPose; diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 30f8590062..f59ed9d62a 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -60,11 +60,18 @@ public: virtual void saveSettings() const override; virtual void loadSettings() override; + enum class OutOfRangeDataStrategy { + None, + Freeze, + Drop + }; + private: class InputDevice : public controller::InputDevice { public: InputDevice(vr::IVRSystem*& system); bool isHeadControllerMounted() const { return _overrideHead; } + private: // Device functions controller::Input::NamedVector getAvailableInputs() const override; @@ -162,6 +169,7 @@ private: FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; std::string _headsetName {""}; + OutOfRangeDataStrategy _outOfRangeDataStrategy { OutOfRangeDataStrategy::None }; std::vector _validTrackedObjects; std::map _pucksPostOffset; From 527c0d41952336c23428f2ddfb534072fdf95ac5 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 20 Sep 2018 11:34:15 -0700 Subject: [PATCH 030/131] Added accelerationFitlerApp. Attempts to make deceleration limit filter work better. --- .../filters/AccelerationLimiterFilter.cpp | 38 +++- .../impl/filters/AccelerationLimiterFilter.h | 2 + scripts/developer/accelerationFilterApp.js | 214 ++++++++++++++++++ 3 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 scripts/developer/accelerationFilterApp.js diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp index 234dff1c65..677e0af75a 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -38,7 +38,8 @@ static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { return glm::exp((dt / 2.0f) * omegaQ); } -static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, float dt, const float accLimit, const float decLimit) { +static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, + const glm::vec3& unfilteredVelocity, float dt, const float accLimit, const float decLimit) { // measure the linear velocities of this step and the previoius step glm::vec3 v1 = (x3 - x1) / (2.0f * dt); @@ -50,8 +51,8 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con // clamp the acceleration if it is over the limit float aLen = glm::length(a); - // pick limit based on if we are accelerating or decelerating. - float limit = glm::length(v1) > glm::length(v0) ? accLimit : decLimit; + // pick limit based on if we are moving faster then our target + float limit = glm::length(v1) > glm::length(unfilteredVelocity) ? accLimit : decLimit; if (aLen > limit) { // Solve for a new `v1`, such that `a` does not exceed `aLimit` @@ -71,7 +72,8 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con } } -static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, float dt, const float accLimit, const float decLimit) { +static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, + const glm::vec3& unfilteredVelocity, float dt, const float accLimit, const float decLimit) { // ensure quaternions have the same polarity glm::quat q0 = q0In; @@ -86,8 +88,8 @@ static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, co const glm::vec3 a = (w1 - w0) / dt; float aLen = glm::length(a); - // pick limit based on if we are accelerating or decelerating. - float limit = glm::length(w1) > glm::length(w0) ? accLimit : decLimit; + // pick limit based on if we are moving faster then our target + float limit = glm::length(w1) > glm::length(unfilteredVelocity) ? accLimit : decLimit; // clamp the acceleration if it is over the limit if (aLen > limit) { @@ -121,9 +123,14 @@ namespace controller { const float DELTA_TIME = 0.01111111f; - sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, + glm::vec3 unfilteredTranslation = sensorValue.translation; + glm::vec3 unfilteredLinearVel = (unfilteredTranslation - _unfilteredPrevPos[1]) / (2.0f * DELTA_TIME); + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, unfilteredLinearVel, DELTA_TIME, _translationAccelerationLimit, _translationDecelerationLimit); - sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, + glm::quat unfilteredRot = sensorValue.rotation; + glm::quat unfilteredPrevRot = glm::dot(unfilteredRot, _unfilteredPrevRot[1]) < 0.0f ? -_unfilteredPrevRot[1] : _unfilteredPrevRot[1]; + glm::vec3 unfilteredAngularVel = angularVelFromDeltaRot(unfilteredRot * glm::inverse(unfilteredPrevRot), 2.0f * DELTA_TIME); + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, unfilteredAngularVel, DELTA_TIME, _rotationAccelerationLimit, _rotationDecelerationLimit); // remember previous values. @@ -134,6 +141,13 @@ namespace controller { _prevRot[1] = _prevRot[2]; _prevRot[2] = sensorValue.rotation; + _unfilteredPrevPos[0] = _unfilteredPrevPos[1]; + _unfilteredPrevPos[1] = _unfilteredPrevPos[2]; + _unfilteredPrevPos[2] = unfilteredTranslation; + _unfilteredPrevRot[0] = _unfilteredPrevRot[1]; + _unfilteredPrevRot[1] = _unfilteredPrevRot[2]; + _unfilteredPrevRot[2] = unfilteredRot; + // transform back into avatar space return sensorValue.transform(sensorToAvatarMat); } else { @@ -144,6 +158,14 @@ namespace controller { _prevRot[0] = sensorValue.rotation; _prevRot[1] = sensorValue.rotation; _prevRot[2] = sensorValue.rotation; + + _unfilteredPrevPos[0] = sensorValue.translation; + _unfilteredPrevPos[1] = sensorValue.translation; + _unfilteredPrevPos[2] = sensorValue.translation; + _unfilteredPrevRot[0] = sensorValue.rotation; + _unfilteredPrevRot[1] = sensorValue.rotation; + _unfilteredPrevRot[2] = sensorValue.rotation; + _prevValid = true; // no previous value to smooth with, so return value unchanged diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h index 6a6c7f8c33..06eeef1579 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -31,6 +31,8 @@ namespace controller { mutable glm::vec3 _prevPos[3]; // sensor space mutable glm::quat _prevRot[3]; // sensor space + mutable glm::vec3 _unfilteredPrevPos[3]; // sensor space + mutable glm::quat _unfilteredPrevRot[3]; // sensor space mutable bool _prevValid { false }; }; diff --git a/scripts/developer/accelerationFilterApp.js b/scripts/developer/accelerationFilterApp.js new file mode 100644 index 0000000000..52109c0f5d --- /dev/null +++ b/scripts/developer/accelerationFilterApp.js @@ -0,0 +1,214 @@ +var LEFT_HAND_INDEX = 0; +var RIGHT_HAND_INDEX = 1; +var LEFT_FOOT_INDEX = 2; +var RIGHT_FOOT_INDEX = 3; +var HIPS_INDEX = 4; +var SPINE2_INDEX = 5; + +var mappingJson = { + name: "com.highfidelity.testing.accelerationTest", + channels: [ + { + from: "Vive.LeftHand", + to: "Standard.LeftHand", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + rotationDecelerationLimit: 4000.0, + translationAccelerationLimit: 100.0, + translationDecelerationLimit: 200.0 + } + ] + }, + { + from: "Vive.RightHand", + to: "Standard.RightHand", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + rotationDecelerationLimit: 4000.0, + translationAccelerationLimit: 100.0, + translationDecelerationLimit: 200.0 + } + ] + }, + { + from: "Vive.LeftFoot", + to: "Standard.LeftFoot", + filters: [ + { + type: "exponentialSmoothing", + rotation: 0.15, + translation: 0.3 + } + ] + }, + { + from: "Vive.RightFoot", + to: "Standard.RightFoot", + filters: [ + { + type: "exponentialSmoothing", + rotation: 0.15, + translation: 0.3 + } + ] + }, + { + from: "Vive.Hips", + to: "Standard.Hips", + filters: [ + { + type: "exponentialSmoothing", + rotation: 0.15, + translation: 0.3 + } + ] + }, + { + from: "Vive.Spine2", + to: "Standard.Spine2", + filters: [ + { + type: "exponentialSmoothing", + rotation: 0.15, + translation: 0.3 + } + ] + } + ] +}; + +// +// tablet app boiler plate +// + +var TABLET_BUTTON_NAME = "ACCFILT"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/accelerationFilterApp.html"; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg" +}); + +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +}); + +var shown = false; + +function onScreenChanged(type, url) { + if (type === "Web" && url === HTML_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} + +function getTranslationAccelerationLimit(i) { + return mappingJson.channels[i].filters[0].translationAccelerationLimit; +} +function setTranslationAccelerationLimit(i, value) { + mappingJson.channels[i].filters[0].translationAccelerationLimit = value; + mappingChanged(); +} +function getTranslationDecelerationLimit(i) { + return mappingJson.channels[i].filters[0].translationDecelerationLimit; +} +function setTranslationDecelerationLimit(i, value) { + mappingJson.channels[i].filters[0].translationDecelerationLimit = value; mappingChanged(); +} +function getRotationAccelerationLimit(i) { + return mappingJson.channels[i].filters[0].rotationAccelerationLimit; +} +function setRotationAccelerationLimit(i, value) { + mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; mappingChanged(); +} +function getRotationDecelerationLimit(i) { + return mappingJson.channels[i].filters[0].rotationDecelerationLimit; +} +function setRotationDecelerationLimit(i, value) { + mappingJson.channels[i].filters[0].rotationDecelerationLimit = value; mappingChanged(); +} + +function onWebEventReceived(msg) { + if (msg.name === "init-complete") { + var values = [ + {name: "left-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-translation-deceleration-limit", val: getTranslationDecelerationLimit(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-rotation-deceleration-limit", val: getRotationDecelerationLimit(LEFT_HAND_INDEX), checked: false} + ]; + tablet.emitScriptEvent(JSON.stringify(values)); + } else if (msg.name === "left-hand-translation-acceleration-limit") { + setTranslationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-hand-translation-deceleration-limit") { + setTranslationDecelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-hand-rotation-acceleration-limit") { + setRotationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-hand-rotation-deceleration-limit") { + setRotationDecelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } +} + +tablet.screenChanged.connect(onScreenChanged); + +function shutdownTabletApp() { + tablet.removeButton(tabletButton); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +} + +// +// end tablet app boiler plate +// + +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +mappingChanged(); + +Script.scriptEnding.connect(function() { + if (mapping) { + mapping.disable(); + } + tablet.removeButton(tabletButton); +}); + From 8c8b30c0f9e16ae89139e5670741cd728dc80dd9 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 20 Sep 2018 13:03:17 -0700 Subject: [PATCH 031/131] Removed deceleration updated app --- .../filters/AccelerationLimiterFilter.cpp | 33 ++---- scripts/developer/accelerationFilterApp.js | 102 ++++++++++-------- 2 files changed, 66 insertions(+), 69 deletions(-) diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp index 677e0af75a..aacbdd2cea 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -39,7 +39,7 @@ static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { } static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, - const glm::vec3& unfilteredVelocity, float dt, const float accLimit, const float decLimit) { + float dt, const float accLimit) { // measure the linear velocities of this step and the previoius step glm::vec3 v1 = (x3 - x1) / (2.0f * dt); @@ -52,9 +52,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con float aLen = glm::length(a); // pick limit based on if we are moving faster then our target - float limit = glm::length(v1) > glm::length(unfilteredVelocity) ? accLimit : decLimit; - - if (aLen > limit) { + if (aLen > accLimit) { // Solve for a new `v1`, such that `a` does not exceed `aLimit` // This combines two steps: // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: @@ -62,7 +60,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con // 2) Computing new `v1` // `v1 = newA * dt + v0` // We combine the scalars from step 1 and step 2 into a single term to avoid having to do multiple scalar-vec3 multiplies. - v1 = a * ((limit * dt) / aLen) + v0; + v1 = a * ((accLimit * dt) / aLen) + v0; // apply limited v1 to compute filtered x3 return v1 * dt + x2; @@ -73,7 +71,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con } static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, - const glm::vec3& unfilteredVelocity, float dt, const float accLimit, const float decLimit) { + float dt, const float accLimit) { // ensure quaternions have the same polarity glm::quat q0 = q0In; @@ -88,13 +86,10 @@ static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, co const glm::vec3 a = (w1 - w0) / dt; float aLen = glm::length(a); - // pick limit based on if we are moving faster then our target - float limit = glm::length(w1) > glm::length(unfilteredVelocity) ? accLimit : decLimit; - // clamp the acceleration if it is over the limit - if (aLen > limit) { + if (aLen > accLimit) { // solve for a new w1, such that a does not exceed the accLimit - w1 = a * ((limit * dt) / aLen) + w0; + w1 = a * ((accLimit * dt) / aLen) + w0; // apply limited w1 to compute filtered q3 return deltaRotFromAngularVel(w1, dt) * q2; @@ -124,14 +119,11 @@ namespace controller { const float DELTA_TIME = 0.01111111f; glm::vec3 unfilteredTranslation = sensorValue.translation; - glm::vec3 unfilteredLinearVel = (unfilteredTranslation - _unfilteredPrevPos[1]) / (2.0f * DELTA_TIME); - sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, unfilteredLinearVel, - DELTA_TIME, _translationAccelerationLimit, _translationDecelerationLimit); + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, + DELTA_TIME, _translationAccelerationLimit); glm::quat unfilteredRot = sensorValue.rotation; - glm::quat unfilteredPrevRot = glm::dot(unfilteredRot, _unfilteredPrevRot[1]) < 0.0f ? -_unfilteredPrevRot[1] : _unfilteredPrevRot[1]; - glm::vec3 unfilteredAngularVel = angularVelFromDeltaRot(unfilteredRot * glm::inverse(unfilteredPrevRot), 2.0f * DELTA_TIME); - sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, unfilteredAngularVel, - DELTA_TIME, _rotationAccelerationLimit, _rotationDecelerationLimit); + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, + DELTA_TIME, _rotationAccelerationLimit); // remember previous values. _prevPos[0] = _prevPos[1]; @@ -183,12 +175,9 @@ namespace controller { bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { if (parameters.isObject()) { auto obj = parameters.toObject(); - if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_ROTATION_DECELERATION_LIMIT) && - obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_DECELERATION_LIMIT)) { + if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT)) { _rotationAccelerationLimit = (float)obj[JSON_ROTATION_ACCELERATION_LIMIT].toDouble(); - _rotationDecelerationLimit = (float)obj[JSON_ROTATION_DECELERATION_LIMIT].toDouble(); _translationAccelerationLimit = (float)obj[JSON_TRANSLATION_ACCELERATION_LIMIT].toDouble(); - _translationDecelerationLimit = (float)obj[JSON_TRANSLATION_DECELERATION_LIMIT].toDouble(); return true; } } diff --git a/scripts/developer/accelerationFilterApp.js b/scripts/developer/accelerationFilterApp.js index 52109c0f5d..a2ef937e52 100644 --- a/scripts/developer/accelerationFilterApp.js +++ b/scripts/developer/accelerationFilterApp.js @@ -9,72 +9,68 @@ var mappingJson = { name: "com.highfidelity.testing.accelerationTest", channels: [ { - from: "Vive.LeftHand", - to: "Standard.LeftHand", + from: "Standard.LeftHand", + to: "Actions.LeftHand", filters: [ { type: "accelerationLimiter", rotationAccelerationLimit: 2000.0, - rotationDecelerationLimit: 4000.0, translationAccelerationLimit: 100.0, - translationDecelerationLimit: 200.0 } ] }, { - from: "Vive.RightHand", - to: "Standard.RightHand", + from: "Standard.RightHand", + to: "Actions.RightHand", filters: [ { type: "accelerationLimiter", rotationAccelerationLimit: 2000.0, - rotationDecelerationLimit: 4000.0, translationAccelerationLimit: 100.0, - translationDecelerationLimit: 200.0 } ] }, { - from: "Vive.LeftFoot", - to: "Standard.LeftFoot", + from: "Standard.LeftFoot", + to: "Actions.LeftFoot", filters: [ { - type: "exponentialSmoothing", - rotation: 0.15, - translation: 0.3 + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, } ] }, { - from: "Vive.RightFoot", - to: "Standard.RightFoot", + from: "Standard.RightFoot", + to: "Actions.RightFoot", filters: [ { - type: "exponentialSmoothing", - rotation: 0.15, - translation: 0.3 + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, } ] }, { - from: "Vive.Hips", - to: "Standard.Hips", + from: "Standard.Hips", + to: "Actions.Hips", filters: [ { - type: "exponentialSmoothing", - rotation: 0.15, - translation: 0.3 + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, } ] }, { - from: "Vive.Spine2", - to: "Standard.Spine2", + from: "Standard.Spine2", + to: "Actions.Spine2", filters: [ { - type: "exponentialSmoothing", - rotation: 0.15, - translation: 0.3 + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, } ] } @@ -86,7 +82,7 @@ var mappingJson = { // var TABLET_BUTTON_NAME = "ACCFILT"; -var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/accelerationFilterApp.html"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/accelerationFilterApp.html?2"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tabletButton = tablet.addButton({ @@ -132,42 +128,54 @@ function setTranslationAccelerationLimit(i, value) { mappingJson.channels[i].filters[0].translationAccelerationLimit = value; mappingChanged(); } -function getTranslationDecelerationLimit(i) { - return mappingJson.channels[i].filters[0].translationDecelerationLimit; -} -function setTranslationDecelerationLimit(i, value) { - mappingJson.channels[i].filters[0].translationDecelerationLimit = value; mappingChanged(); -} function getRotationAccelerationLimit(i) { return mappingJson.channels[i].filters[0].rotationAccelerationLimit; } function setRotationAccelerationLimit(i, value) { mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; mappingChanged(); } -function getRotationDecelerationLimit(i) { - return mappingJson.channels[i].filters[0].rotationDecelerationLimit; -} -function setRotationDecelerationLimit(i, value) { - mappingJson.channels[i].filters[0].rotationDecelerationLimit = value; mappingChanged(); -} function onWebEventReceived(msg) { if (msg.name === "init-complete") { var values = [ {name: "left-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, - {name: "left-hand-translation-deceleration-limit", val: getTranslationDecelerationLimit(LEFT_HAND_INDEX), checked: false}, {name: "left-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, - {name: "left-hand-rotation-deceleration-limit", val: getRotationDecelerationLimit(LEFT_HAND_INDEX), checked: false} + {name: "right-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(RIGHT_HAND_INDEX), checked: false}, + {name: "right-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(RIGHT_HAND_INDEX), checked: false}, + {name: "left-foot-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_FOOT_INDEX), checked: false}, + {name: "left-foot-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_FOOT_INDEX), checked: false}, + {name: "right-foot-translation-acceleration-limit", val: getTranslationAccelerationLimit(RIGHT_FOOT_INDEX), checked: false}, + {name: "right-foot-rotation-acceleration-limit", val: getRotationAccelerationLimit(RIGHT_FOOT_INDEX), checked: false}, + {name: "hips-translation-acceleration-limit", val: getTranslationAccelerationLimit(HIPS_INDEX), checked: false}, + {name: "hips-rotation-acceleration-limit", val: getRotationAccelerationLimit(HIPS_INDEX), checked: false}, + {name: "spine2-translation-acceleration-limit", val: getTranslationAccelerationLimit(SPINE2_INDEX), checked: false}, + {name: "spine2-rotation-acceleration-limit", val: getRotationAccelerationLimit(SPINE2_INDEX), checked: false} ]; tablet.emitScriptEvent(JSON.stringify(values)); } else if (msg.name === "left-hand-translation-acceleration-limit") { setTranslationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); - } else if (msg.name === "left-hand-translation-deceleration-limit") { - setTranslationDecelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); } else if (msg.name === "left-hand-rotation-acceleration-limit") { setRotationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); - } else if (msg.name === "left-hand-rotation-deceleration-limit") { - setRotationDecelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-hand-translation-acceleration-limit") { + setTranslationAccelerationLimit(RIGHT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-hand-rotation-acceleration-limit") { + setRotationAccelerationLimit(RIGHT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-foot-translation-acceleration-limit") { + setTranslationAccelerationLimit(LEFT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-foot-rotation-acceleration-limit") { + setRotationAccelerationLimit(LEFT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-foot-translation-acceleration-limit") { + setTranslationAccelerationLimit(RIGHT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-foot-rotation-acceleration-limit") { + setRotationAccelerationLimit(RIGHT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "hips-translation-acceleration-limit") { + setTranslationAccelerationLimit(HIPS_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "hips-rotation-acceleration-limit") { + setRotationAccelerationLimit(HIPS_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "spine2-translation-acceleration-limit") { + setTranslationAccelerationLimit(SPINE2_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "spine2-rotation-acceleration-limit") { + setRotationAccelerationLimit(SPINE2_INDEX, parseInt(msg.val, 10)); } } From a776f7e55a98bd09c729a2ca888d6b33b0d5ada6 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 25 Sep 2018 10:24:30 -0700 Subject: [PATCH 032/131] Changed default for OutOfRange data to Drop. --- plugins/openvr/src/ViveControllerManager.cpp | 2 +- plugins/openvr/src/ViveControllerManager.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index cc3ca523df..69797340dd 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -310,7 +310,7 @@ void ViveControllerManager::loadSettings() { if (_inputDevice) { const double DEFAULT_ARM_CIRCUMFERENCE = 0.33; const double DEFAULT_SHOULDER_WIDTH = 0.48; - const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "None"; + const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "Drop"; _inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble(); _inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble(); _inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString()); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index f59ed9d62a..06e13e1c49 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -169,7 +169,7 @@ private: FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; std::string _headsetName {""}; - OutOfRangeDataStrategy _outOfRangeDataStrategy { OutOfRangeDataStrategy::None }; + OutOfRangeDataStrategy _outOfRangeDataStrategy { OutOfRangeDataStrategy::Drop }; std::vector _validTrackedObjects; std::map _pucksPostOffset; From 0283c9fbdc34fcee1b81232f215afee02c146e17 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 2 Oct 2018 16:20:00 -0700 Subject: [PATCH 033/131] Added CriticallyDampedSpringPoseHelper to help smooth out hips. --- interface/src/avatar/MySkeletonModel.cpp | 43 ++++++++----------- interface/src/avatar/MySkeletonModel.h | 6 ++- libraries/animation/src/AnimUtil.h | 53 ++++++++++++++++++++++++ libraries/animation/src/Rig.cpp | 3 ++ libraries/animation/src/Rig.h | 2 + 5 files changed, 79 insertions(+), 28 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 3084542472..f13a4c8e10 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -36,6 +36,7 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); + // check for pinned hips. auto hipsIndex = myAvatar->getJointIndex("Hips"); if (myAvatar->isJointPinned(hipsIndex)) { @@ -199,49 +200,38 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); - if (!_prevHipsValid) { - AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); - _prevHips = hips; - } - - AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); - // timescale in seconds const float TRANS_HORIZ_TIMESCALE = 0.15f; const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch. const float ROT_TIMESCALE = 0.15f; const float FLY_IDLE_TRANSITION_TIMESCALE = 0.25f; - float transHorizAlpha, transVertAlpha, rotAlpha; if (_flyIdleTimer < 0.0f) { - transHorizAlpha = glm::min(deltaTime / TRANS_HORIZ_TIMESCALE, 1.0f); - transVertAlpha = glm::min(deltaTime / TRANS_VERT_TIMESCALE, 1.0f); - rotAlpha = glm::min(deltaTime / ROT_TIMESCALE, 1.0f); + _smoothHipsHelper.setHorizontalTranslationTimescale(TRANS_HORIZ_TIMESCALE); + _smoothHipsHelper.setVerticalTranslationTimescale(TRANS_VERT_TIMESCALE); + _smoothHipsHelper.setRotationTimescale(ROT_TIMESCALE); } else { - transHorizAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); - transVertAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); - rotAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); + _smoothHipsHelper.setHorizontalTranslationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); + _smoothHipsHelper.setVerticalTranslationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); + _smoothHipsHelper.setRotationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); } - // smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation. - float hipsY = hips.trans().y; - hips.trans() = lerp(_prevHips.trans(), hips.trans(), transHorizAlpha); - hips.trans().y = lerp(_prevHips.trans().y, hipsY, transVertAlpha); - hips.rot() = safeLerp(_prevHips.rot(), hips.rot(), rotAlpha); - - _prevHips = hips; - _prevHipsValid = true; + AnimPose sensorHips = computeHipsInSensorFrame(myAvatar, isFlying); + if (!_prevIsEstimatingHips) { + _smoothHipsHelper.teleport(sensorHips); + } + sensorHips = _smoothHipsHelper.update(sensorHips, deltaTime); glm::mat4 invRigMat = glm::inverse(myAvatar->getTransform().getMatrix() * Matrices::Y_180); AnimPose sensorToRigPose(invRigMat * myAvatar->getSensorToWorldMatrix()); - params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips; + params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * sensorHips; params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; // set spine2 if we have hand controllers if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && - myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && - !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { + myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && + !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { AnimPose currentSpine2Pose; AnimPose currentHeadPose; @@ -267,8 +257,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } + _prevIsEstimatingHips = true; } else { - _prevHipsValid = false; + _prevIsEstimatingHips = false; } params.isTalking = head->getTimeWithoutTalking() <= 1.5f; diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index ebef9796a4..9a3559ddf7 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -10,6 +10,7 @@ #define hifi_MySkeletonModel_h #include +#include #include "MyAvatar.h" /// A skeleton loaded from a model. @@ -26,11 +27,12 @@ public: private: void updateFingers(); - AnimPose _prevHips; // sensor frame - bool _prevHipsValid { false }; + CriticallyDampedSpringPoseHelper _smoothHipsHelper; // sensor frame bool _prevIsFlying { false }; float _flyIdleTimer { 0.0f }; + float _prevIsEstimatingHips { false }; + std::map _jointRotationFrameOffsetMap; }; diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 9300f1a7a0..1880550435 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -38,4 +38,57 @@ AnimPose boneLookAt(const glm::vec3& target, const AnimPose& bone); // and returns a bodyRot that is also z-forward and y-up glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& up); + +// Uses a approximation of a critically damped spring to smooth full AnimPoses. +// It provides seperate timescales for horizontal, vertical and rotation components. +// The timescale is roughly how much time it will take the spring will reach halfway toward it's target. +class CriticallyDampedSpringPoseHelper { +public: + CriticallyDampedSpringPoseHelper() : _prevPoseValid(false) {} + + void setHorizontalTranslationTimescale(float timescale) { + _horizontalTranslationTimescale = timescale; + } + void setVerticalTranslationTimescale(float timescale) { + _verticalTranslationTimescale = timescale; + } + void setRotationTimescale(float timescale) { + _rotationTimescale = timescale; + } + + AnimPose update(const AnimPose& pose, float deltaTime) { + if (!_prevPoseValid) { + _prevPose = pose; + _prevPoseValid = true; + } + + const float horizontalTranslationAlpha = glm::min(deltaTime / _horizontalTranslationTimescale, 1.0f); + const float verticalTranslationAlpha = glm::min(deltaTime / _verticalTranslationTimescale, 1.0f); + const float rotationAlpha = glm::min(deltaTime / _rotationTimescale, 1.0f); + + const float poseY = pose.trans().y; + AnimPose newPose = _prevPose; + newPose.trans() = lerp(_prevPose.trans(), pose.trans(), horizontalTranslationAlpha); + newPose.trans().y = lerp(_prevPose.trans().y, poseY, verticalTranslationAlpha); + newPose.rot() = safeLerp(_prevPose.rot(), pose.rot(), rotationAlpha); + + _prevPose = newPose; + _prevPoseValid = true; + + return newPose; + } + + void teleport(const AnimPose& pose) { + _prevPoseValid = true; + _prevPose = pose; + } + +protected: + AnimPose _prevPose; + float _horizontalTranslationTimescale { 0.15f }; + float _verticalTranslationTimescale { 0.15f }; + float _rotationTimescale { 0.15f }; + bool _prevPoseValid; +}; + #endif diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 91d4e0f9d3..a1f3fbe8c8 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1609,6 +1609,7 @@ glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int up void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) { if (!_animSkeleton || !_animNode) { + _previousControllerParameters = params; return; } @@ -1700,6 +1701,8 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } } } + + _previousControllerParameters = params; } void Rig::initAnimGraph(const QUrl& url) { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 48f00d4e5d..4b1c994605 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -394,6 +394,8 @@ protected: AnimContext _lastContext; AnimVariantMap _lastAnimVars; + + ControllerParameters _previousControllerParameters; }; #endif /* defined(__hifi__Rig__) */ From 4e751237401e076b374950b00b34a9b4a7171311 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 2 Oct 2018 18:07:26 -0700 Subject: [PATCH 034/131] Blend between non-estimated and estimated hips in Rig --- libraries/animation/src/AnimUtil.h | 37 ++++++++++++++++++++++++++++++ libraries/animation/src/Rig.cpp | 23 +++++++++++++++++-- libraries/animation/src/Rig.h | 2 ++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 1880550435..cf190e8dbf 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -91,4 +91,41 @@ protected: bool _prevPoseValid; }; +class SnapshotBlendPoseHelper { +public: + SnapshotBlendPoseHelper() : _snapshotValid(false) {} + + void setBlendDuration(float duration) { + _duration = duration; + } + + void setSnapshot(const AnimPose& pose) { + _snapshotValid = true; + _snapshotPose = pose; + _timer = _duration; + } + + AnimPose update(const AnimPose& targetPose, float deltaTime) { + _timer -= deltaTime; + if (_timer > 0.0f) { + float alpha = (_duration - _timer) / _duration; + + // ease in expo + alpha = 1.0f - powf(2.0f, -10.0f * alpha); + + AnimPose newPose = targetPose; + newPose.blend(_snapshotPose, alpha); + return newPose; + } else { + return targetPose; + } + } + +protected: + AnimPose _snapshotPose; + float _duration { 1.0f }; + float _timer { 0.0f }; + bool _snapshotValid { false }; +}; + #endif diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a1f3fbe8c8..d076ce5029 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1620,7 +1620,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled; bool rightHandEnabled = params.primaryControllerFlags[PrimaryControllerType_RightHand] & (uint8_t)ControllerFlags::Enabled; bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; + bool prevHipsEnabled = _previousControllerParameters.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; bool hipsEstimated = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; + bool prevHipsEstimated = _previousControllerParameters.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; bool leftFootEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftFoot] & (uint8_t)ControllerFlags::Enabled; bool rightFootEnabled = params.primaryControllerFlags[PrimaryControllerType_RightFoot] & (uint8_t)ControllerFlags::Enabled; bool spine2Enabled = params.primaryControllerFlags[PrimaryControllerType_Spine2] & (uint8_t)ControllerFlags::Enabled; @@ -1659,9 +1661,26 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } if (hipsEnabled) { + + // Apply a bit of smoothing when the hips toggle between estimated and non-estimated poses. + // This should help smooth out problems with the vive tracker when the sensor is occluded. + if (prevHipsEnabled && hipsEstimated != prevHipsEstimated) { + // blend from a snapshot of the previous hips. + const float HIPS_BLEND_DURATION = 0.3f; + _hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION); + _hipsBlendHelper.setSnapshot(_previousControllerParameters.primaryControllerPoses[PrimaryControllerType_Hips]); + } else if (!prevHipsEnabled) { + // we have no sensible value to blend from. + const float HIPS_BLEND_DURATION = 0.0f; + _hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION); + _hipsBlendHelper.setSnapshot(params.primaryControllerPoses[PrimaryControllerType_Hips]); + } + + AnimPose hips = _hipsBlendHelper.update(params.primaryControllerPoses[PrimaryControllerType_Hips], dt); + _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); - _animVars.set("hipsPosition", params.primaryControllerPoses[PrimaryControllerType_Hips].trans()); - _animVars.set("hipsRotation", params.primaryControllerPoses[PrimaryControllerType_Hips].rot()); + _animVars.set("hipsPosition", hips.trans()); + _animVars.set("hipsRotation", hips.rot()); } else { _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 4b1c994605..3a2fa2d036 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -24,6 +24,7 @@ #include "AnimNode.h" #include "AnimNodeLoader.h" #include "SimpleMovingAverage.h" +#include "AnimUtil.h" class Rig; class AnimInverseKinematics; @@ -395,6 +396,7 @@ protected: AnimContext _lastContext; AnimVariantMap _lastAnimVars; + SnapshotBlendPoseHelper _hipsBlendHelper; ControllerParameters _previousControllerParameters; }; From 60d102c9bfa7ffa542f827d6869f7ae76a5c524c Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 9 Oct 2018 12:42:57 -0700 Subject: [PATCH 035/131] styling fixes --- scripts/system/html/css/edit-style.css | 39 ++---- scripts/system/html/js/entityProperties.js | 151 ++++++++++++++++++--- 2 files changed, 142 insertions(+), 48 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index e700b50839..f69ace2401 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -478,13 +478,11 @@ input[type=checkbox]:checked + label:hover { box-shadow: none; } -#properties-list > fieldset#properties-header { +#properties-list > fieldset#properties-base { margin-top: 0px; padding-bottom: 0px; } - - #properties-list > fieldset > legend { position: relative; display: table; @@ -896,7 +894,7 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { } #properties-list fieldset .two-column { - padding-top:21px; + padding-top: 21px; display: flex; } @@ -917,7 +915,7 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { font-family: Raleway-Regular; font-size: 12px; color: #afafaf; - height: 28px; + height: 20px; text-transform: uppercase; outline: none; } @@ -1270,7 +1268,7 @@ th#entity-hasScript { color: #afafaf; } -#base #property-type-icon { +#properties-base #property-type-icon { font-family: hifi-glyphs; font-size: 31px; color: #00b4ef; @@ -1279,52 +1277,39 @@ th#entity-hasScript { display: none; } -#base #property-type { +#properties-base #property-type { padding: 5px 24px 5px 0; border-right: 1px solid #808080; width: auto; display: inline-block; } -#base #div-locked { +#properties-base #div-property-locked { position: absolute; top: 0px; right: 140px; } -#base #div-visible { +#properties-base #div-property-visible { position: absolute; top: 20px; right: 20px; } -#base .checkbox { - position: relative; - top: -1px; -} - -#base .checkbox:last-child { - padding-left: 24px; -} - -#base .checkbox label { - background-position-y: 1px; -} - -#base .checkbox label span { +#properties-base .checkbox label span { font-family: HiFi-Glyphs; font-size: 20px; padding-right: 6px; vertical-align: top; position: relative; - top: -4px; + top: -2px; } -#base input[type=checkbox]:checked + label span { +#properties-base input[type=checkbox]:checked + label span { color: #ffffff; } -#base + hr { +#properties-base + hr { margin-top: 12px; } @@ -1387,4 +1372,4 @@ input#property-scale-button-reset { #properties-list { display: flex; flex-direction: column; -} \ No newline at end of file +} diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d8ffc2ee25..46f16dd0a0 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -775,7 +775,7 @@ const GROUPS = [ { label: "Can cast shadow", type: "bool", - propertyName: "castShadow", + propertyName: "canCastShadow", }, { label: "Script", @@ -957,12 +957,6 @@ function disableProperties() { } } -function showElements(els, show) { - for (var i = 0; i < els.length; i++) { - els[i].style.display = (show) ? 'table' : 'none'; - } -} - function updateProperty(propertyName, propertyValue) { var properties = {}; let splitPropertyName = propertyName.split('.'); @@ -1688,11 +1682,17 @@ function resetProperties() { for (let propertyToHide in propertyShowRules) { let elPropertyToHide = elPropertyElements[propertyToHide]; if (elPropertyToHide) { - let parentNode = elPropertyToHide.parentNode; - if (parentNode === undefined && elPropertyToHide instanceof Array) { - parentNode = elPropertyToHide[0].parentNode; + let nodeToHide = elPropertyToHide; + if (elPropertyToHide.nodeName !== "DIV") { + let parentNode = elPropertyToHide.parentNode; + if (parentNode === undefined && elPropertyToHide instanceof Array) { + parentNode = elPropertyToHide[0].parentNode; + } + if (parentNode !== undefined) { + nodeToHide = parentNode; + } } - parentNode.style.display = "none"; + nodeToHide.style.display = "none"; } } } @@ -1705,13 +1705,13 @@ function loaded() { GROUPS.forEach(function(group) { let elGroup; if (group.addToGroup !== undefined) { - let fieldset = document.getElementById(group.addToGroup); + let fieldset = document.getElementById("properties-" + group.addToGroup); elGroup = document.createElement('div'); fieldset.appendChild(elGroup); } else { elGroup = document.createElement('fieldset'); elGroup.setAttribute("class", "major"); - elGroup.setAttribute("id", group.id); + elGroup.setAttribute("id", "properties-" + group.id); elPropertiesList.appendChild(elGroup); } @@ -1739,7 +1739,7 @@ function loaded() { elProperty.setAttribute("class", "sub-section-header"); } else { elProperty = document.createElement('div'); - elProperty.setAttribute("id", "div-" + propertyName); + elProperty.setAttribute("id", "div-" + propertyID); } if (group.twoColumn && property.column !== undefined && property.column !== -1) { @@ -1944,6 +1944,7 @@ function loaded() { let elInput = document.createElement('select'); elInput.setAttribute("id", propertyID); + elInput.setAttribute("propertyName", propertyName); for (let optionKey in property.options) { let option = document.createElement('option'); @@ -2035,7 +2036,7 @@ function loaded() { break; } case 'buttons': { - elProperty.setAttribute("class", "property Text"); + elProperty.setAttribute("class", "property text"); let hasLabel = property.label !== undefined; if (hasLabel) { @@ -2234,7 +2235,7 @@ function loaded() { elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); if (elProperty[2] !== undefined) { - elProperty[2].value = (propertyValue.z * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + elProperty[2].value = (propertyValue.z * 1 / elProperty[2].getAttribute("multiplier")).toFixed(4); } } else if (elProperty.length === 4) { // color is array of color picker and 3 input numbers @@ -2262,8 +2263,9 @@ function loaded() { } else if (elProperty.nodeName === "TEXTAREA") { elProperty.value = propertyValue; setTextareaScrolling(elProperty); - } else if (elProperty.nodeName === "SELECT") { // dropdown + } else if (elProperty.nodeName === "DT") { // dropdown elProperty.value = propertyValue; + setDropdownText(elProperty); } else { elProperty.value = propertyValue; } @@ -2275,11 +2277,17 @@ function loaded() { let show = String(propertyValue) === String(showIfThisPropertyValue); let elPropertyToShow = elPropertyElements[propertyToShow]; if (elPropertyToShow) { - let parentNode = elPropertyToShow.parentNode; - if (parentNode === undefined && elPropertyToShow instanceof Array) { - parentNode = elPropertyToShow[0].parentNode; + let nodeToShow = elPropertyToShow; + if (elPropertyToShow.nodeName !== "DIV") { + let parentNode = elPropertyToShow.parentNode; + if (parentNode === undefined && elPropertyToShow instanceof Array) { + parentNode = elPropertyToShow[0].parentNode; + } + if (parentNode !== undefined) { + nodeToShow = parentNode; + } } - parentNode.style.display = show ? "block" : "none"; + nodeToShow.style.display = show ? "table" : "none"; } } } @@ -2524,6 +2532,107 @@ function loaded() { event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); } + + // Dropdowns + // For each dropdown the following replacement is created in place of the original dropdown... + // Structure created: + //
    + //
    display textcarat
    + //
    + //
      + //
    • 0) { + var el = elDropdowns[0]; + el.parentNode.removeChild(el); + elDropdowns = document.getElementsByTagName("select"); + } document.addEventListener("keydown", function (keyDown) { if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { From 8d56a313e891664af3ba118dbc573eda5a05c811 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 9 Oct 2018 14:05:26 -0700 Subject: [PATCH 036/131] renamed viveTrackedObjects.js to drawTrackedObjects.js Removed requirement of having a Vive hooked up. Input recordings can have trackedObjects within them. --- ...TrackedObjects.js => drawTrackedObjects.js} | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) rename scripts/developer/tests/{viveTrackedObjects.js => drawTrackedObjects.js} (62%) diff --git a/scripts/developer/tests/viveTrackedObjects.js b/scripts/developer/tests/drawTrackedObjects.js similarity index 62% rename from scripts/developer/tests/viveTrackedObjects.js rename to scripts/developer/tests/drawTrackedObjects.js index 1d60f658d9..c7d886c319 100644 --- a/scripts/developer/tests/viveTrackedObjects.js +++ b/scripts/developer/tests/drawTrackedObjects.js @@ -21,16 +21,14 @@ function shutdown() { var BLUE = {x: 0, y: 0, z: 1, w: 1}; function update(dt) { - if (Controller.Hardware.Vive) { - TRACKED_OBJECT_POSES.forEach(function (key) { - var pose = Controller.getPoseValue(Controller.Standard[key]); - if (pose.valid) { - DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE); - } else { - DebugDraw.removeMyAvatarMarker(key); - } - }); - } + TRACKED_OBJECT_POSES.forEach(function (key) { + var pose = Controller.getPoseValue(Controller.Standard[key]); + if (pose.valid) { + DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE); + } else { + DebugDraw.removeMyAvatarMarker(key); + } + }); } init(); From c0ceb713ec6315f24feb5bf52cd92c2cb522af3f Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 9 Oct 2018 16:48:28 -0700 Subject: [PATCH 037/131] Spine2 IKTarget now smoothly interpolates when ik target is disabled. Also all smooth ik interpolations use easeInExpo instead of linear curves. --- .../animation/src/AnimInverseKinematics.cpp | 20 ++++++++++++++++--- libraries/animation/src/AnimTwoBoneIK.cpp | 7 +++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 71094cc6e1..399eaf3fab 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -253,11 +253,25 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< if (numLoops == MAX_IK_LOOPS) { for (size_t i = 0; i < _prevJointChainInfoVec.size(); i++) { if (_prevJointChainInfoVec[i].timer > 0.0f) { + float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[i].timer) / JOINT_CHAIN_INTERP_TIME; + + // ease in expo + alpha = 1.0f - powf(2.0f, -10.0f * alpha); + size_t chainSize = std::min(_prevJointChainInfoVec[i].jointInfoVec.size(), jointChainInfoVec[i].jointInfoVec.size()); - for (size_t j = 0; j < chainSize; j++) { - jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); - jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); + + if (jointChainInfoVec[i].target.getType() != IKTarget::Type::Unknown) { + // if we are interping into an enabled target type, i.e. not off, lerp the rot and the trans. + for (size_t j = 0; j < chainSize; j++) { + jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); + jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); + } + } else { + // if we are interping into a disabled target type, keep the rot & trans the same, but lerp the weight down to zero. + jointChainInfoVec[i].target.setType((int)_prevJointChainInfoVec[i].target.getType()); + jointChainInfoVec[i].target.setWeight(_prevJointChainInfoVec[i].target.getWeight() * (1.0f - alpha)); + jointChainInfoVec[i].jointInfoVec = _prevJointChainInfoVec[i].jointInfoVec; } } } diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index d68240d176..8960b15940 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -201,14 +201,17 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const if (_interpType != InterpType::None) { _interpAlpha += _interpAlphaVel * dt; + // ease in expo + float easeInAlpha = 1.0f - powf(2.0f, -10.0f * _interpAlpha); + if (_interpAlpha < 1.0f) { AnimChain interpChain; if (_interpType == InterpType::SnapshotToUnderPoses) { interpChain = underChain; - interpChain.blend(_snapshotChain, _interpAlpha); + interpChain.blend(_snapshotChain, easeInAlpha); } else if (_interpType == InterpType::SnapshotToSolve) { interpChain = ikChain; - interpChain.blend(_snapshotChain, _interpAlpha); + interpChain.blend(_snapshotChain, easeInAlpha); } // copy interpChain into _poses interpChain.outputRelativePoses(_poses); From 82918a5c31c64767fbbcf3b77332564e193b4134 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 10 Oct 2018 12:31:16 -0700 Subject: [PATCH 038/131] Add animation support --- interface/src/avatar/AvatarManager.cpp | 41 +++++++++++++++++++ interface/src/avatar/AvatarManager.h | 1 + .../src/avatars-renderer/Avatar.h | 14 +++++++ 3 files changed, 56 insertions(+) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d31b201dc7..1f2c9e462d 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -83,10 +83,18 @@ AvatarManager::AvatarManager(QObject* parent) : const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing + const QString START_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; + const QString MIDDLE_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; + const QString END_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; + _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; _transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; _transitConfig._isDistanceBased = true; + + _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 14); + _transitConfig._middleTransitAnimation = AvatarTransit::TransitAnimation(MIDDLE_ANIMATION_URL, 15, 0); + _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 16, 38); } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -134,6 +142,39 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { _space = space; } +void AvatarManager::playTransitAnimations(AvatarTransit::Status status) { + auto startAnimation = _transitConfig._startTransitAnimation; + auto middleAnimation = _transitConfig._middleTransitAnimation; + auto endAnimation = _transitConfig._endTransitAnimation; + + const float REFERENCE_FPS = 30.0f; + switch (status) { + case AvatarTransit::Status::START_FRAME: + qDebug() << "START_FRAME"; + _myAvatar->overrideAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, + startAnimation._firstFrame + startAnimation._frameCount); + break; + case AvatarTransit::Status::START_TRANSIT: + qDebug() << "START_TRANSIT"; + _myAvatar->overrideAnimation(middleAnimation._animationUrl, REFERENCE_FPS, false, middleAnimation._firstFrame, + middleAnimation._firstFrame + middleAnimation._frameCount); + break; + case AvatarTransit::Status::END_TRANSIT: + qDebug() << "END_TRANSIT"; + _myAvatar->overrideAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, + endAnimation._firstFrame + endAnimation._frameCount); + break; + case AvatarTransit::Status::END_FRAME: + qDebug() << "END_FRAME"; + _myAvatar->restoreAnimation(); + break; + case AvatarTransit::Status::IDLE: + break; + case AvatarTransit::Status::TRANSITING: + break; + } +} + void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 209b976c44..0dc39e991b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -213,6 +213,7 @@ private: // frequently grabs a read lock on the hash to get a given avatar by ID void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + void playTransitAnimations(AvatarTransit::Status status); QVector _avatarsToFade; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 1087f74c07..21e359051f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -54,9 +54,11 @@ class AvatarTransit { public: enum Status { IDLE = 0, + START_FRAME, START_TRANSIT, TRANSITING, END_TRANSIT, + END_FRAME, ABORT_TRANSIT }; @@ -67,6 +69,15 @@ public: EASE_IN_OUT }; + struct TransitAnimation { + TransitAnimation(){}; + TransitAnimation(const QString& animationUrl, int firstFrame, int frameCount) : + _firstFrame(firstFrame), _frameCount(frameCount), _animationUrl(animationUrl){}; + int _firstFrame; + int _frameCount; + QString _animationUrl; + }; + struct TransitConfig { TransitConfig() {}; int _totalFrames { 0 }; @@ -74,6 +85,9 @@ public: bool _isDistanceBased { false }; float _triggerDistance { 0 }; EaseType _easeType { EaseType::EASE_OUT }; + TransitAnimation _startTransitAnimation; + TransitAnimation _middleTransitAnimation; + TransitAnimation _endTransitAnimation; }; AvatarTransit() {}; From 09c3b39f0d14202a6efb17425a68e4ea46efd0c1 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 10 Oct 2018 14:00:16 -0700 Subject: [PATCH 039/131] Update puck-attach.js with model that matches the orientation of the pucks --- scripts/developer/tests/puck-attach.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index 04d5db5710..019a911535 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -85,7 +85,7 @@ function entityExists(entityID) { return Object.keys(Entities.getEntityProperties(entityID)).length > 0; } -var VIVE_PUCK_MODEL = "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj"; +var VIVE_PUCK_MODEL = "https://s3.amazonaws.com/hifi-public/tony/vive_tracker_puck_y180z180.obj"; var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres @@ -304,4 +304,4 @@ tabletButton.clicked.connect(function () { tablet.gotoWebScreen(TABLET_APP_URL); } }); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE From 31e665ab8bc737bfa248d25434f8256ceb44ad48 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 10 Oct 2018 16:49:20 -0700 Subject: [PATCH 040/131] CR changes - runtime property data rework --- scripts/system/html/js/entityProperties.js | 1554 +++++++++++--------- 1 file changed, 850 insertions(+), 704 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 46f16dd0a0..2841414d87 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -6,8 +6,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, - HifiEntityUI, JSONEditor, openEventBridge, setTimeout, window, _ $ */ +/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, + EventBridge, JSONEditor, openEventBridge, setTimeout, window, _ $ */ const ICON_FOR_TYPE = { Box: "V", @@ -40,6 +40,7 @@ const GROUPS = [ label: NO_SELECTION, type: "icon", icons: ICON_FOR_TYPE, + defaultValue: "Box", propertyName: "type", }, { @@ -79,7 +80,10 @@ const GROUPS = [ { label: "Shape", type: "dropdown", - options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", + Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", + Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", + Circle: "Circle", Quad: "Quad" }, propertyName: "shape", }, { @@ -208,7 +212,8 @@ const GROUPS = [ }, { type: "buttons", - buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], + buttons: [ { id: "copy", label: "Copy URL To Ambient", + className: "black", onClick: copySkyboxURLToAmbientURL } ], propertyName: "copyURLToAmbient", showPropertyRule: { "skyboxMode": "enabled" }, }, @@ -367,7 +372,9 @@ const GROUPS = [ { label: "Collision Shape", type: "dropdown", - options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , + "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , + "static-mesh": "Exact - All polygons (non-dynamic only)" }, propertyName: "shapeType", }, { @@ -467,8 +474,8 @@ const GROUPS = [ { label: "Light Color", type: "color", - propertyName: "lightColor", // this actually shares "color" property with shape Color - // but separating naming here to separate property fields + propertyName: "lightColor", // this actually shares "color" property with shape Color but + // separating naming here to distinguish property element/data }, { label: "Intensity", @@ -521,8 +528,8 @@ const GROUPS = [ label: "Material Data", type: "textarea", buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, - { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], propertyName: "materialData", }, { @@ -613,7 +620,7 @@ const GROUPS = [ defaultValue: 100, unit: "%", buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, - { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], propertyName: "scale", }, { @@ -628,8 +635,8 @@ const GROUPS = [ { label: "Align", type: "buttons", - buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, - { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], propertyName: "alignToGrid", }, ] @@ -642,8 +649,8 @@ const GROUPS = [ { label: "Collides", type: "bool", - propertyName: "collisionless", inverse: true, + propertyName: "collisionless", column: -1, // before two columns div }, { @@ -655,14 +662,14 @@ const GROUPS = [ { label: "Collides With", type: "sub-header", - propertyName: "collidesWithHeader", + propertyName: "collidesWithHeader", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, column: 1, }, { label: "", type: "sub-header", - propertyName: "collidesWithHeaderHelper", + propertyName: "collidesWithHeaderHelper", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, column: 2, }, @@ -711,6 +718,7 @@ const GROUPS = [ type: "string", propertyName: "collisionSoundURL", showPropertyRule: { "collisionless": "false" }, + // having no column number means place this after two columns div }, ] }, @@ -772,7 +780,7 @@ const GROUPS = [ showPropertyRule: { "cloneable": "true" }, column: 1, }, - { + { // below properties having no column number means place them after two columns div label: "Can cast shadow", type: "bool", propertyName: "canCastShadow", @@ -799,8 +807,8 @@ const GROUPS = [ label: "User Data", type: "textarea", buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, - { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], propertyName: "userData", }, ] @@ -898,11 +906,9 @@ const MATERIAL_PREFIX_STRING = "mat::"; const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; var elGroups = {}; -var elPropertyElements = {}; -var showPropertyRules = {}; -var subProperties = []; -var icons = {}; +var properties = {}; var colorPickers = {}; +var selectedEntityProperties; var lastEntityID = null; function debugPrint(message) { @@ -914,6 +920,17 @@ function debugPrint(message) { ); } + +// GENERAL PROPERTY/GROUP FUNCTIONS + +function getPropertyElement(propertyName) { + return properties[propertyName].el; +} + +function getPropertyElementID(propertyName) { + return "property-" + propertyName; +} + function enableChildren(el, selector) { var elSelectors = el.querySelectorAll(selector); for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { @@ -931,7 +948,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = elPropertyElements["locked"]; + var elLocked = getPropertyElement("locked"); if (elLocked.checked === false) { removeStaticUserData(); @@ -945,7 +962,7 @@ function disableProperties() { for (var pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = elPropertyElements["locked"]; + var elLocked = getPropertyElement("locked"); if (elLocked.checked === true) { if ($('#property-userData-editor').css('display') === "block") { @@ -957,34 +974,148 @@ function disableProperties() { } } +function showPropertyElement(propertyName, show) { + let elProperty = properties[propertyName].el; + let elNode = elProperty; + if (elNode.nodeName !== "DIV") { + let elParent = elProperty.parentNode; + if (elParent === undefined && elProperty instanceof Array) { + elParent = elProperty[0].parentNode; + } + if (elParent !== undefined) { + elNode = elParent; + } + } + elNode.style.display = show ? "table" : "none"; +} + +function resetProperties() { + for (let propertyName in properties) { + let elProperty = properties[propertyName].el; + let propertyData = properties[propertyName].data; + + switch (propertyData.type) { + case 'string': { + elProperty.value = ""; + break; + } + case 'bool': { + elProperty.checked = false; + break; + } + case 'number': { + if (propertyData.defaultValue !== undefined) { + elProperty.value = propertyData.defaultValue; + } else { + elProperty.value = ""; + } + break; + } + case 'vec3': + case 'vec2': { + // vec2/vec3 are array of 2/3 elInput numbers + elProperty[0].value = ""; + elProperty[1].value = ""; + if (elProperty[2] !== undefined) { + elProperty[2].value = ""; + } + break; + } + case 'color': { + // color is array of color picker and 3 elInput numbers + elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elProperty[1].value = ""; + elProperty[2].value = ""; + elProperty[3].value = ""; + break; + } + case 'dropdown': { + elProperty.value = ""; + setDropdownText(elProperty); + break; + } + case 'textarea': { + elProperty.value = ""; + setTextareaScrolling(elProperty); + break; + } + case 'icon': { + // icon is array of elSpan (icon glyph) and elLabel + if (propertyData.defaultValue !== undefined) { + elProperty[0].innerHTML = propertyData.icons[propertyData.defaultValue]; + elProperty[0].style.display = "inline-block"; + elProperty[1].innerHTML = propertyData.defaultValue; + } + break; + } + } + + let showPropertyRules = properties[propertyName].showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToHide in showPropertyRules) { + showPropertyElement(propertyToHide, false); + } + } + } +} + +function showGroupsForType(type) { + if (type === "Box" || type === "Sphere") { + type = "Shape"; + } + + let typeGroups = GROUPS_PER_TYPE[type]; + + for (let groupKey in elGroups) { + let elGroup = elGroups[groupKey]; + if (typeGroups && typeGroups.indexOf(groupKey) > -1) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } + } +} + + + +// PROPERTY UPDATE FUNCTIONS + function updateProperty(propertyName, propertyValue) { - var properties = {}; + let propertyUpdate = {}; + // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times let splitPropertyName = propertyName.split('.'); if (splitPropertyName.length > 1) { let propertyGroupName = splitPropertyName[0]; let subPropertyName = splitPropertyName[1]; - properties[propertyGroupName] = {}; + propertyUpdate[propertyGroupName] = {}; if (splitPropertyName.length === 3) { let subSubPropertyName = splitPropertyName[2]; - properties[propertyGroupName][subPropertyName] = {}; - properties[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; + propertyUpdate[propertyGroupName][subPropertyName] = {}; + propertyUpdate[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; } else { - properties[propertyGroupName][subPropertyName] = propertyValue; + propertyUpdate[propertyGroupName][subPropertyName] = propertyValue; } } else { - properties[propertyName] = propertyValue; + propertyUpdate[propertyName] = propertyValue; } - updateProperties(properties); + updateProperties(propertyUpdate); } -function updateProperties(properties) { +function updateProperties(propertiesToUpdate) { EventBridge.emitWebEvent(JSON.stringify({ id: lastEntityID, type: "update", - properties: properties + properties: propertiesToUpdate })); } + +function createEmitTextPropertyUpdateFunction(propertyName) { + return function() { + updateProperty(propertyName, this.value); + }; +} + function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { return function() { updateProperty(propertyName, inverse ? !this.checked : this.checked); @@ -994,67 +1125,50 @@ function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { decimals = ((decimals === undefined) ? 4 : decimals); return function() { - var value = parseFloat(this.value).toFixed(decimals); + let value = parseFloat(this.value).toFixed(decimals); updateProperty(propertyName, value); }; } -function createImageURLUpdateFunction(propertyName) { - return function () { - var newTextures = JSON.stringify({ "tex.picture": this.value }); - updateProperty(propertyName, newTextures); - }; -} - -function createEmitTextPropertyUpdateFunction(propertyName) { - return function() { - updateProperty(propertyName, this.value); - }; -} - function createEmitVec2PropertyUpdateFunction(property, elX, elY) { return function () { - var properties = {}; - properties[property] = { + let newValue = { x: elX.value, y: elY.value }; - updateProperties(properties); + updateProperty(property, newValue); }; } function createEmitVec2PropertyUpdateFunctionWithMultiplier(property, elX, elY, multiplier) { return function () { - var properties = {}; - properties[property] = { + let newValue = { x: elX.value * multiplier, y: elY.value * multiplier }; - updateProperties(properties); + updateProperty(property, newValue); }; } function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { return function() { - var properties = {}; - properties[property] = { + let newValue = { x: elX.value, y: elY.value, z: elZ ? elZ.value : 0 }; - updateProperties(properties); + updateProperty(property, newValue); }; } function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { return function() { - var properties = {}; - properties[property] = { + let newValue = { x: elX.value * multiplier, y: elY.value * multiplier, z: elZ.value * multiplier }; - updateProperties(properties); + updateProperty(property, newValue); }; } @@ -1064,23 +1178,13 @@ function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) }; } -function emitColorPropertyUpdate(property, red, green, blue, group) { - var properties = {}; - if (group) { - properties[group] = {}; - properties[group][property] = { - red: red, - green: green, - blue: blue - }; - } else { - properties[property] = { - red: red, - green: green, - blue: blue - }; - } - updateProperties(properties); +function emitColorPropertyUpdate(property, red, green, blue) { + let newValue = { + red: red, + green: green, + blue: blue + }; + updateProperty(property, newValue); } function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { @@ -1095,8 +1199,440 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen updateProperty(propertyName, propertyValue); } +function createImageURLUpdateFunction(propertyName) { + return function () { + var newTextures = JSON.stringify({ "tex.picture": this.value }); + updateProperty(propertyName, newTextures); + }; +} + + + +// PROPERTY ELEMENT CREATION FUNCTIONS + +function createStringProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property text"); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("type", "text"); + if (propertyData.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, propertyElementID, propertyData.buttons, false); + } + + properties[propertyName].el = elInput; +} + +function createBoolProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property checkbox"); + + if (propertyData.glyph !== undefined) { + elLabel.innerText = " " + propertyData.label; + let elSpan = document.createElement('span'); + elSpan.innerHTML = propertyData.glyph; + elLabel.insertBefore(elSpan, elLabel.firstChild); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("type", "checkbox"); + + elProperty.appendChild(elInput); + elProperty.appendChild(elLabel); + + let subPropertyOf = propertyData.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.addEventListener('change', function() { + updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName); + }); + } else { + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse)); + } + + properties[propertyName].el = elInput; +} + +function createVec3Property(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property " + propertyData.vec3Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(propertyData.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[0], + propertyData.min, propertyData.max, propertyData.step); + let elInputY = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[1], + propertyData.min, propertyData.max, propertyData.step); + let elInputZ = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[2], + propertyData.min, propertyData.max, propertyData.step); + + let inputChangeFunction; + if (propertyData.multiplier !== undefined) { + inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, + elInputZ, propertyData.multiplier); + } else { + inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); + } + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + elInputZ.addEventListener('change', inputChangeFunction); + + properties[propertyName].el = [ elInputX, elInputY, elInputZ ]; +} + +function createVec2Property(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property " + propertyData.vec2Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(propertyData.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[0], + propertyData.min, propertyData.max, propertyData.step); + let elInputY = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[1], + propertyData.min, propertyData.max, propertyData.step); + + let inputChangeFunction; + if (propertyData.multiplier !== undefined) { + inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, + propertyData.multiplier); + } else { + inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY); + } + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + + properties[propertyName].el = [elInputX, elInputY]; +} + +function createColorProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property rgb fstuple"); + + let elColorPicker = document.createElement('div'); + elColorPicker.setAttribute("class", "color-picker"); + elColorPicker.setAttribute("id", propertyElementID); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputR = createTupleNumberInput(elTuple, propertyElementID, "red", 0, 255, 1); + let elInputG = createTupleNumberInput(elTuple, propertyElementID, "green", 0, 255, 1); + let elInputB = createTupleNumberInput(elTuple, propertyElementID, "blue", 0, 255, 1); + + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); + elInputR.addEventListener('change', inputChangeFunction); + elInputG.addEventListener('change', inputChangeFunction); + elInputB.addEventListener('change', inputChangeFunction); + + let colorPickerID = "#" + propertyElementID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + $(colorPickerID).attr('active', 'true'); + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elInputR.value, + "g": elInputG.value, + "b": elInputB.value + }); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + } + }); + + properties[propertyName].el = [elColorPicker, elInputR, elInputG, elInputB]; +} + +function createDropdownProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property dropdown"); + + let elInput = document.createElement('select'); + elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("propertyName", propertyName); + + for (let optionKey in propertyData.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = propertyData.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + properties[propertyName].el = elInput; +} + +function createNumberProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property number"); + + addUnit(propertyData.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("type", "number"); + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + } + + let defaultValue = propertyData.defaultValue; + if (defaultValue !== undefined) { + elInput.setAttribute("defaultValue", defaultValue); + elInput.value = defaultValue; + } + + let fixedDecimals = propertyData.fixedDecimals !== undefined ? propertyData.fixedDecimals : 0; + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, propertyElementID, propertyData.buttons, true); + } + + properties[propertyName].el = elInput; +} + +function createTextareaProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property textarea"); + + elProperty.appendChild(elLabel); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, propertyElementID, propertyData.buttons, true); + } + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", propertyElementID); + if (propertyData.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elInput); + + properties[propertyName].el = elInput; +} + +function createIconProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property value"); + + elLabel.setAttribute("id", propertyElementID); + elLabel.innerHTML = " " + propertyData.label; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", propertyElementID + "-icon"); + + elProperty.appendChild(elSpan); + elProperty.appendChild(elLabel); + + properties[propertyName].el = [ elSpan, elLabel ]; +} + +function createButtonsProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property text"); + + let hasLabel = propertyData.label !== undefined; + if (hasLabel) { + elProperty.appendChild(elLabel); + } + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, propertyElementID, propertyData.buttons, hasLabel); + } + + properties[propertyName].el = elProperty; +} + +function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { + let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; + elLabel.setAttribute("for", elementPropertyID); + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementPropertyID); + elInput.setAttribute("type", "number"); + elInput.setAttribute("class", subLabel); + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } + if (step !== undefined) { + elInput.setAttribute("step", step); + } + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elTuple.appendChild(elDiv); + return elInput; +} + +function addUnit(unit, elLabel) { + if (unit !== undefined) { + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "unit"); + elSpan.innerHTML = unit; + elLabel.appendChild(elSpan); + } +} + +function addButtons(elProperty, propertyID, buttons, newRow) { + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "row"); + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.setAttribute("type", "button"); + elButton.setAttribute("class", button.className); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } +} + + + +// BUTTON CALLBACKS + +function rescaleDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); +} + +function moveSelectionToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); +} + +function moveAllToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); +} + +function resetToNaturalDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); +} + +function reloadScripts() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); +} + +function reloadServerScripts() { + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); +} + +function copySkyboxURLToAmbientURL() { + let skyboxURL = getPropertyElement("skybox.url").value; + getPropertyElement("ambientLight.ambientURL").value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL); +} + + + +// USER DATA FUNCTIONS + function clearUserData() { - let elUserData = elPropertyElements["userData"]; + let elUserData = getPropertyElement("userData"); deleteJSONEditor(); elUserData.value = ""; showUserDataTextArea(); @@ -1148,7 +1684,7 @@ function setUserDataFromEditor(noUpdate) { } function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, removeKeys) { - var properties = {}; + var propertyUpdate = {}; var parsedData = {}; var keysToBeRemoved = removeKeys ? removeKeys : []; try { @@ -1192,14 +1728,14 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r delete parsedData[groupName]; } if (Object.keys(parsedData).length > 0) { - properties.userData = JSON.stringify(parsedData); + propertyUpdate.userData = JSON.stringify(parsedData); } else { - properties.userData = ''; + propertyUpdate.userData = ''; } - userDataElement.value = properties.userData; + userDataElement.value = propertyUpdate.userData; - updateProperties(properties); + updateProperties(propertyUpdate); } function userDataChanger(groupName, keyName, values, userDataElement, defaultValue, removeKeys) { var val = {}, def = {}; @@ -1313,8 +1849,12 @@ function saveJSONUserData(noUpdate) { }, EDITOR_TIMEOUT_DURATION); } + + +// MATERIAL DATA FUNCTIONS + function clearMaterialData() { - let elMaterialData = elPropertyElements["materialData"]; + let elMaterialData = getPropertyElement("materialData"); deleteJSONMaterialEditor(); elMaterialData.value = ""; showMaterialDataTextArea(); @@ -1479,7 +2019,8 @@ function bindAllNonJSONEditorElements() { // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { - if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { + if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || + e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { return; } else { if ($('#property-userData-editor').css('height') !== "0px") { @@ -1493,14 +2034,41 @@ function bindAllNonJSONEditorElements() { } } -function unbindAllInputs() { - var inputs = $('input'); - var i; - for (i = 0; i < inputs.length; i++) { - var input = inputs[i]; - var field = $(input); - field.unbind(); + + +// DROPDOWNS / TEXTAREAS / PARENT MATERIAL NAME FUNCTIONS + +function setDropdownText(dropdown) { + let lis = dropdown.parentNode.getElementsByTagName("li"); + let text = ""; + for (let i = 0; i < lis.length; i++) { + if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { + text = lis[i].textContent; + } } + dropdown.firstChild.textContent = text; +} + +function toggleDropdown(event) { + let element = event.target; + if (element.nodeName !== "DT") { + element = element.parentNode; + } + element = element.parentNode; + let isDropped = element.getAttribute("dropped"); + element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); +} + +function setDropdownValue(event) { + let dt = event.target.parentNode.parentNode.previousSibling; + dt.value = event.target.getAttribute("value"); + dt.firstChild.textContent = event.target.textContent; + + dt.parentNode.setAttribute("dropped", "false"); + + let evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", true, true); + dt.dispatchEvent(evt); } function setTextareaScrolling(element) { @@ -1520,183 +2088,7 @@ function showParentMaterialNameBox(number, elNumber, elString) { } } -function rescaleDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "rescaleDimensions", - percentage: parseFloat(document.getElementById("property-scale").value) - })); -} -function moveSelectionToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveSelectionToGrid" - })); -} - -function moveAllToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveAllToGrid" - })); -} - -function resetToNaturalDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "resetToNaturalDimensions" - })); -} - -function reloadScripts() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadClientScripts" - })); -} - -function reloadServerScripts() { - // invalidate the current status (so that same-same updates can still be observed visually) - document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadServerScripts" - })); -} - -function copySkyboxURLToAmbientURL() { - let skyboxURL = elPropertyElements["skybox.url"].value; - elPropertyElements["ambientLight.ambientURL"].value = skyboxURL; - updateProperty("ambientLight.ambientURL", skyboxURL); -} - -function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { - let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); - let elDiv = document.createElement('div'); - let elLabel = document.createElement('label'); - elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; - elLabel.setAttribute("for", elementPropertyID); - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementPropertyID); - elInput.setAttribute("type", "number"); - elInput.setAttribute("class", subLabel); - if (min !== undefined) { - elInput.setAttribute("min", min); - } - if (max !== undefined) { - elInput.setAttribute("max", max); - } - if (step !== undefined) { - elInput.setAttribute("step", step); - } - elDiv.appendChild(elInput); - elDiv.appendChild(elLabel); - elTuple.appendChild(elDiv); - return elInput; -} - -function addUnit(unit, elLabel) { - if (unit !== undefined) { - let elSpan = document.createElement('span'); - elSpan.setAttribute("class", "unit"); - elSpan.innerHTML = unit; - elLabel.appendChild(elSpan); - } -} - -function addButtons(elProperty, propertyID, buttons, newRow) { - let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "row"); - - buttons.forEach(function(button) { - let elButton = document.createElement('input'); - elButton.setAttribute("type", "button"); - elButton.setAttribute("class", button.className); - elButton.setAttribute("id", propertyID + "-button-" + button.id); - elButton.setAttribute("value", button.label); - elButton.addEventListener("click", button.onClick); - if (newRow) { - elDiv.appendChild(elButton); - } else { - elProperty.appendChild(elButton); - } - }); - - if (newRow) { - elProperty.appendChild(document.createElement('br')); - elProperty.appendChild(elDiv); - } -} - -function showGroupsForType(type) { - if (type === "Box" || type === "Sphere") { - type = "Shape"; - } - - let typeGroups = GROUPS_PER_TYPE[type]; - - for (let groupKey in elGroups) { - let elGroup = elGroups[groupKey]; - if (typeGroups && typeGroups.indexOf(groupKey) > -1) { - elGroup.style.display = "block"; - } else { - elGroup.style.display = "none"; - } - } -} - -function resetProperties() { - for (let propertyName in elPropertyElements) { - let elProperty = elPropertyElements[propertyName]; - if (elProperty instanceof Array) { - if (elProperty.length === 2 || elProperty.length === 3) { - // vec2/vec3 are array of 2/3 input numbers - elProperty[0].value = ""; - elProperty[1].value = ""; - if (elProperty[2] !== undefined) { - elProperty[2].value = ""; - } - } else if (elProperty.length === 4) { - // color is array of color picker and 3 input numbers - elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elProperty[1].value = ""; - elProperty[2].value = ""; - elProperty[3].value = ""; - } - } else if (elProperty.getAttribute("type") === "number") { - if (elProperty.getAttribute("defaultValue")) { - elProperty.value = elProperty.getAttribute("defaultValue"); - } else { - elProperty.value = ""; - } - } else if (elProperty.getAttribute("type") === "checkbox") { - elProperty.checked = false; - } else { - elProperty.value = ""; - } - } - - for (let showPropertyRule in showPropertyRules) { - let propertyShowRules = showPropertyRules[showPropertyRule]; - for (let propertyToHide in propertyShowRules) { - let elPropertyToHide = elPropertyElements[propertyToHide]; - if (elPropertyToHide) { - let nodeToHide = elPropertyToHide; - if (elPropertyToHide.nodeName !== "DIV") { - let parentNode = elPropertyToHide.parentNode; - if (parentNode === undefined && elPropertyToHide instanceof Array) { - parentNode = elPropertyToHide[0].parentNode; - } - if (parentNode !== undefined) { - nodeToHide = parentNode; - } - } - nodeToHide.style.display = "none"; - } - } - } -} function loaded() { openEventBridge(function() { @@ -1729,8 +2121,8 @@ function loaded() { group.properties.forEach(function(property) { let propertyType = property.type; let propertyName = property.propertyName; - let propertyID = "property-" + propertyName; - propertyID = propertyID.replace(".", "-"); + let propertyElementID = getPropertyElementID(propertyName); + propertyElementID = propertyElementID.replace(".", "-"); let elProperty; if (propertyType === "sub-header") { @@ -1739,7 +2131,7 @@ function loaded() { elProperty.setAttribute("class", "sub-section-header"); } else { elProperty = document.createElement('div'); - elProperty.setAttribute("id", "div-" + propertyID); + elProperty.setAttribute("id", "div-" + propertyElementID); } if (group.twoColumn && property.column !== undefined && property.column !== -1) { @@ -1766,294 +2158,58 @@ function loaded() { let elLabel = document.createElement('label'); elLabel.innerText = property.label; - elLabel.setAttribute("for", propertyID); + elLabel.setAttribute("for", propertyElementID); + + properties[propertyName] = { data: property }; switch (propertyType) { - case 'vec3': { - elProperty.setAttribute("class", "property " + property.vec3Type + " fstuple"); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - addUnit(property.unit, elLabel); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); - let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); - let elInputZ = createTupleNumberInput(elTuple, propertyID, property.subLabels[2], property.min, property.max, property.step); - - let inputChangeFunction; - let multiplier = 1; - if (property.multiplier !== undefined) { - multiplier = property.multiplier; - inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); - } else { - inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, elInputZ, property.multiplier); - } - elInputX.setAttribute("multiplier", multiplier); - elInputY.setAttribute("multiplier", multiplier); - elInputZ.setAttribute("multiplier", multiplier); - elInputX.addEventListener('change', inputChangeFunction); - elInputY.addEventListener('change', inputChangeFunction); - elInputZ.addEventListener('change', inputChangeFunction); - - elPropertyElements[propertyName] = [elInputX, elInputY, elInputZ]; - break; - } - case 'vec2': { - elProperty.setAttribute("class", "property " + property.vec2Type + " fstuple"); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - addUnit(property.unit, elLabel); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); - let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); - - let inputChangeFunction; - let multiplier = 1; - if (property.multiplier !== undefined) { - multiplier = property.multiplier; - inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY); - } else { - inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, property.multiplier); - } - elInputX.setAttribute("multiplier", multiplier); - elInputY.setAttribute("multiplier", multiplier); - elInputX.addEventListener('change', inputChangeFunction); - elInputY.addEventListener('change', inputChangeFunction); - - elPropertyElements[propertyName] = [elInputX, elInputY]; - break; - } - case 'color': { - elProperty.setAttribute("class", "property rgb fstuple"); - - let elColorPicker = document.createElement('div'); - elColorPicker.setAttribute("class", "color-picker"); - elColorPicker.setAttribute("id", propertyID); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - elProperty.appendChild(elColorPicker); - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputR = createTupleNumberInput(elTuple, propertyID, "red", 0, 255, 1); - let elInputG = createTupleNumberInput(elTuple, propertyID, "green", 0, 255, 1); - let elInputB = createTupleNumberInput(elTuple, propertyID, "blue", 0, 255, 1); - - let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); - elInputR.addEventListener('change', inputChangeFunction); - elInputG.addEventListener('change', inputChangeFunction); - elInputB.addEventListener('change', inputChangeFunction); - - let colorPickerID = "#" + propertyID; - colorPickers[colorPickerID] = $(colorPickerID).colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $(colorPickerID).attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers[colorPickerID].colpickSetColor({ - "r": elInputR.value, - "g": elInputG.value, - "b": elInputB.value - }); - }, - onHide: function(colpick) { - $(colorPickerID).attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elPropertyElements[propertyName] = [elColorPicker, elInputR, elInputG, elInputB]; - break; - } case 'string': { - elProperty.setAttribute("class", "property text"); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "text"); - if (property.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, false); - } - - elPropertyElements[propertyName] = elInput; + createStringProperty(elProperty, elLabel, property); break; } case 'bool': { - elProperty.setAttribute("class", "property checkbox"); - - if (property.glyph !== undefined) { - elLabel.innerText = " " + property.label; - let elSpan = document.createElement('span'); - elSpan.innerHTML = property.glyph; - elLabel.insertBefore(elSpan, elLabel.firstChild); - } - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "checkbox"); - - let inverse = property.inverse; - elInput.setAttribute("inverse", inverse ? "true" : "false"); - - elProperty.appendChild(elInput); - elProperty.appendChild(elLabel); - - let subPropertyOf = property.subPropertyOf; - if (subPropertyOf !== undefined) { - elInput.setAttribute("subPropertyOf", subPropertyOf); - elPropertyElements[propertyName] = elInput; - subProperties.push(propertyName); - elInput.addEventListener('change', function() { - updateCheckedSubProperty(subPropertyOf, properties[subPropertyOf], elInput, propertyName); - }); - } else { - elPropertyElements[propertyName] = elInput; - elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, inverse)); - } - break; - } - case 'dropdown': { - elProperty.setAttribute("class", "property dropdown"); - - let elInput = document.createElement('select'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("propertyName", propertyName); - - for (let optionKey in property.options) { - let option = document.createElement('option'); - option.value = optionKey; - option.text = property.options[optionKey]; - elInput.add(option); - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - elPropertyElements[propertyName] = elInput; + createBoolProperty(elProperty, elLabel, property); break; } case 'number': { - elProperty.setAttribute("class", "property number"); - - addUnit(property.unit, elLabel); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "number"); - if (property.min !== undefined) { - elInput.setAttribute("min", property.min); - } - if (property.max !== undefined) { - elInput.setAttribute("max", property.max); - } - if (property.step !== undefined) { - elInput.setAttribute("step", property.step); - } - - let fixedDecimals = property.fixedDecimals !== undefined ? property.fixedDecimals : 0; - elInput.setAttribute("fixedDecimals", fixedDecimals); - - let defaultValue = property.defaultValue; - if (defaultValue !== undefined) { - elInput.setAttribute("defaultValue", defaultValue); - elInput.value = defaultValue; - } - - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, true); - } - - elPropertyElements[propertyName] = elInput; + createNumberProperty(elProperty, elLabel, property); + break; + } + case 'vec3': { + createVec3Property(elProperty, elLabel, property); + break; + } + case 'vec2': { + createVec2Property(elProperty, elLabel, property); + break; + } + case 'color': { + createColorProperty(elProperty, elLabel, property); + break; + } + case 'dropdown': { + createDropdownProperty(elProperty, elLabel, property); break; } case 'textarea': { - elProperty.setAttribute("class", "property textarea"); - - elProperty.appendChild(elLabel); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, true); - } - - let elInput = document.createElement('textarea'); - elInput.setAttribute("id", propertyID); - if (property.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elInput); - elPropertyElements[propertyName] = elInput; + createTextareaProperty(elProperty, elLabel, property); break; } case 'icon': { - elProperty.setAttribute("class", "property value"); - - elLabel.setAttribute("id", propertyID); - elLabel.innerHTML = " " + property.label; - - let elSpan = document.createElement('span'); - elSpan.setAttribute("id", propertyID + "-icon"); - icons[propertyName] = property.icons; - - elProperty.appendChild(elSpan); - elProperty.appendChild(elLabel); - elPropertyElements[propertyName] = [ elSpan, elLabel ]; + createIconProperty(elProperty, elLabel, property); break; } case 'buttons': { - elProperty.setAttribute("class", "property text"); - - let hasLabel = property.label !== undefined; - if (hasLabel) { - elProperty.appendChild(elLabel); - } - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, hasLabel); - } - - elPropertyElements[propertyName] = elProperty; + createButtonsProperty(elProperty, elLabel, property); break; } case 'sub-header': { - if (propertyName !== undefined) { - elPropertyElements[propertyName] = elProperty; - } + properties[propertyName].el = elProperty; + break; + } + default: { + console.log("EntityProperties - Unknown property type " + + propertyType + " set to property " + propertyName); break; } } @@ -2062,10 +2218,13 @@ function loaded() { if (showPropertyRule !== undefined) { let dependentProperty = Object.keys(showPropertyRule)[0]; let dependentPropertyValue = showPropertyRule[dependentProperty]; - if (!showPropertyRules[dependentProperty]) { - showPropertyRules[dependentProperty] = []; + if (properties[dependentProperty] === undefined) { + properties[dependentProperty] = {}; } - showPropertyRules[dependentProperty][propertyName] = dependentPropertyValue; + if (properties[dependentProperty].showPropertyRules === undefined) { + properties[dependentProperty].showPropertyRules = {}; + } + properties[dependentProperty].showPropertyRules[propertyName] = dependentPropertyValue; } }); @@ -2073,7 +2232,6 @@ function loaded() { }); if (window.EventBridge !== undefined) { - var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type === "server_script_status") { @@ -2112,21 +2270,21 @@ function loaded() { } } - elPropertyElements["type"][0].style.display = "none"; - elPropertyElements["type"][1].innerHTML = NO_SELECTION; + resetProperties(); + + getPropertyElement("type")[0].style.display = "none"; + getPropertyElement("type")[1].innerHTML = NO_SELECTION; elPropertiesList.className = ''; showGroupsForType("None"); - - resetProperties(); deleteJSONEditor(); - elPropertyElements["userData"].value = ""; + getPropertyElement("userData").value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); deleteJSONMaterialEditor(); - elPropertyElements["materialData"].value = ""; + getPropertyElement("materialData").value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -2157,18 +2315,19 @@ function loaded() { type = selections[0].properties.type; } - elPropertyElements["type"][0].innerHTML = ICON_FOR_TYPE[type]; - elPropertyElements["type"][0].style.display = "inline-block"; - elPropertyElements["type"][1].innerHTML = type + " (" + data.selections.length + ")"; + resetProperties(); + + getPropertyElement("type")[0].innerHTML = ICON_FOR_TYPE[type]; + getPropertyElement("type")[0].style.display = "inline-block"; + getPropertyElement("type")[1].innerHTML = type + " (" + data.selections.length + ")"; elPropertiesList.className = ''; showGroupsForType(type); - resetProperties(); disableProperties(); } else { - properties = data.selections[0].properties; + selectedEntityProperties = data.selections[0].properties; - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { saveUserData(); } @@ -2177,132 +2336,144 @@ function loaded() { } } - let doSelectElement = lastEntityID === '"' + properties.id + '"'; + let doSelectElement = lastEntityID === '"' + selectedEntityProperties.id + '"'; // the event bridge and json parsing handle our avatar id string differently. - lastEntityID = '"' + properties.id + '"'; + lastEntityID = '"' + selectedEntityProperties.id + '"'; // HTML workaround since image is not yet a separate entity type let IMAGE_MODEL_NAME = 'default-image-model.fbx'; - if (properties.type === "Model") { - let urlParts = properties.modelURL.split('/'); + if (selectedEntityProperties.type === "Model") { + let urlParts = selectedEntityProperties.modelURL.split('/'); let propsFilename = urlParts[urlParts.length - 1]; if (propsFilename === IMAGE_MODEL_NAME) { - properties.type = "Image"; + selectedEntityProperties.type = "Image"; } } // Create class name for css ruleset filtering - elPropertiesList.className = properties.type + 'Menu'; - showGroupsForType(properties.type); + elPropertiesList.className = selectedEntityProperties.type + 'Menu'; + showGroupsForType(selectedEntityProperties.type); - for (let propertyName in elPropertyElements) { - let elProperty = elPropertyElements[propertyName]; + for (let propertyName in properties) { + let property = properties[propertyName]; + let elProperty = property.el; + let propertyData = property.data; + // if this is a compound property name (i.e. animation.running) + // then split it by . up to 3 times to find property value let propertyValue; let splitPropertyName = propertyName.split('.'); if (splitPropertyName.length > 1) { let propertyGroupName = splitPropertyName[0]; let subPropertyName = splitPropertyName[1]; - if (properties[propertyGroupName] === undefined || properties[propertyGroupName][subPropertyName] === undefined) { + let groupProperties = selectedEntityProperties[propertyGroupName]; + if (groupProperties === undefined || groupProperties[subPropertyName] === undefined) { continue; } if (splitPropertyName.length === 3) { let subSubPropertyName = splitPropertyName[2]; - propertyValue = properties[propertyGroupName][subPropertyName][subSubPropertyName]; + propertyValue = groupProperties[subPropertyName][subSubPropertyName]; } else { - propertyValue = properties[propertyGroupName][subPropertyName]; + propertyValue = groupProperties[subPropertyName]; } } else { - propertyValue = properties[propertyName]; + propertyValue = selectedEntityProperties[propertyName]; } // workaround for shape Color & Light Color property fields sharing same property value "color" if (propertyValue === undefined && propertyName === "lightColor") { - propertyValue = properties["color"] + propertyValue = selectedEntityProperties["color"] } - let isSubProperty = subProperties.indexOf(propertyName) > -1; + let isSubProperty = propertyData.subPropertyOf !== undefined; if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { continue; } - if (elProperty instanceof Array) { - if (elProperty[1].getAttribute("type") === "number") { // vectors - if (elProperty.length === 2 || elProperty.length === 3) { - // vec2/vec3 are array of 2/3 input numbers - elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); - elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); - if (elProperty[2] !== undefined) { - elProperty[2].value = (propertyValue.z * 1 / elProperty[2].getAttribute("multiplier")).toFixed(4); - } - } else if (elProperty.length === 4) { - // color is array of color picker and 3 input numbers - elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; - elProperty[1].value = propertyValue.red; - elProperty[2].value = propertyValue.green; - elProperty[3].value = propertyValue.blue; - } - } else if (elProperty[0].nodeName === "SPAN") { // icons - // icon is array of elSpan (icon glyph) and elLabel - elProperty[0].innerHTML = icons[propertyName][propertyValue]; - elProperty[0].style.display = "inline-block"; - elProperty[1].innerHTML = propertyValue; //+ " (" + data.selections.length + ")"; + switch (propertyData.type) { + case 'string': { + elProperty.value = propertyValue; + break; + } + case 'bool': { + let inverse = propertyData.inverse !== undefined ? propertyData.inverse : false; + if (isSubProperty) { + let propertyValue = selectedEntityProperties[propertyData.subPropertyOf]; + let subProperties = propertyValue.split(","); + let subPropertyValue = subProperties.indexOf(propertyName) > -1; + elProperty.checked = inverse ? !subPropertyValue : subPropertyValue; + } else { + elProperty.checked = inverse ? !propertyValue : propertyValue; + } + break; + } + case 'number': { + let fixedDecimals = propertyData.fixedDecimals !== undefined ? propertyData.fixedDecimals : 0; + elProperty.value = propertyValue.toFixed(fixedDecimals); + break; + } + case 'vec3': + case 'vec2': { + // vec2/vec3 are array of 2/3 elInput numbers + let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + elProperty[0].value = (propertyValue.x / multiplier).toFixed(4); + elProperty[1].value = (propertyValue.y / multiplier).toFixed(4); + if (elProperty[2] !== undefined) { + elProperty[2].value = (propertyValue.z / multiplier).toFixed(4); + } + break; + } + case 'color': { + // color is array of color picker and 3 elInput numbers + elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + + propertyValue.green + "," + + propertyValue.blue + ")"; + elProperty[1].value = propertyValue.red; + elProperty[2].value = propertyValue.green; + elProperty[3].value = propertyValue.blue; + break; + } + case 'dropdown': { + elProperty.value = propertyValue; + setDropdownText(elProperty); + break; + } + case 'textarea': { + elProperty.value = propertyValue; + setTextareaScrolling(elProperty); + break; + } + case 'icon': { + // icon is array of elSpan (icon glyph) and elLabel + elProperty[0].innerHTML = propertyData.icons[propertyValue]; + elProperty[0].style.display = "inline-block"; + elProperty[1].innerHTML = propertyValue; + break; } - } else if (elProperty.getAttribute("subPropertyOf")) { - let propertyValue = properties[elProperty.getAttribute("subPropertyOf")]; - let subProperties = propertyValue.split(","); - elProperty.checked = subProperties.indexOf(propertyName) > -1; - } else if (elProperty.getAttribute("type") === "number") { - let fixedDecimals = elProperty.getAttribute("fixedDecimals"); - elProperty.value = propertyValue.toFixed(fixedDecimals); - } else if (elProperty.getAttribute("type") === "checkbox") { - let inverse = elProperty.getAttribute("inverse") === "true"; - elProperty.checked = inverse ? !propertyValue : propertyValue; - } else if (elProperty.nodeName === "TEXTAREA") { - elProperty.value = propertyValue; - setTextareaScrolling(elProperty); - } else if (elProperty.nodeName === "DT") { // dropdown - elProperty.value = propertyValue; - setDropdownText(elProperty); - } else { - elProperty.value = propertyValue; } - let propertyShowRules = showPropertyRules[propertyName]; - if (propertyShowRules !== undefined) { - for (let propertyToShow in propertyShowRules) { - let showIfThisPropertyValue = propertyShowRules[propertyToShow]; + let showPropertyRules = property.showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToShow in showPropertyRules) { + let showIfThisPropertyValue = showPropertyRules[propertyToShow]; let show = String(propertyValue) === String(showIfThisPropertyValue); - let elPropertyToShow = elPropertyElements[propertyToShow]; - if (elPropertyToShow) { - let nodeToShow = elPropertyToShow; - if (elPropertyToShow.nodeName !== "DIV") { - let parentNode = elPropertyToShow.parentNode; - if (parentNode === undefined && elPropertyToShow instanceof Array) { - parentNode = elPropertyToShow[0].parentNode; - } - if (parentNode !== undefined) { - nodeToShow = parentNode; - } - } - nodeToShow.style.display = show ? "table" : "none"; - } + showPropertyElement(propertyToShow, show); } } } - let elGrabbable = elPropertyElements["grabbable"]; - let elTriggerable = elPropertyElements["triggerable"]; - let elIgnoreIK = elPropertyElements["ignoreIK"]; - elGrabbable.checked = elPropertyElements["dynamic"].checked; + let elGrabbable = getPropertyElement("grabbable"); + let elTriggerable = getPropertyElement("triggerable"); + let elIgnoreIK = getPropertyElement("ignoreIK"); + elGrabbable.checked = getPropertyElement("dynamic").checked; elTriggerable.checked = false; elIgnoreIK.checked = true; let grabbablesSet = false; let parsedUserData = {}; try { - parsedUserData = JSON.parse(properties.userData); + parsedUserData = JSON.parse(selectedEntityProperties.userData); if ("grabbableKey" in parsedUserData) { grabbablesSet = true; let grabbableData = parsedUserData.grabbableKey; @@ -2333,19 +2504,20 @@ function loaded() { elIgnoreIK.checked = true; } - if (properties.type === "Image") { - var imageLink = JSON.parse(properties.textures)["tex.picture"]; - elPropertyElements["image"].value = imageLink; - } else if (properties.type === "Material") { - let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; - let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; - let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; - if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { - elParentMaterialNameString.value = properties.parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); + if (selectedEntityProperties.type === "Image") { + let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; + getPropertyElement("image").value = imageLink; + } else if (selectedEntityProperties.type === "Material") { + let elParentMaterialNameString = getPropertyElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyElement("selectSubmesh"); + let parentMaterialName = selectedEntityProperties.parentMaterialName; + if (parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { + elParentMaterialNameString.value = parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = false; } else { - elParentMaterialNameNumber.value = parseInt(properties.parentMaterialName); + elParentMaterialNameNumber.value = parseInt(parentMaterialName); showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = true; } @@ -2353,11 +2525,11 @@ function loaded() { let json = null; try { - json = JSON.parse(properties.userData); + json = JSON.parse(selectedEntityProperties.userData); } catch (e) { // normal text deleteJSONEditor(); - elPropertyElements["userData"].value = properties.userData; + getPropertyElement("userData").value = selectedEntityProperties.userData; showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); @@ -2376,11 +2548,11 @@ function loaded() { let materialJson = null; try { - materialJson = JSON.parse(properties.materialData); + materialJson = JSON.parse(selectedEntityProperties.materialData); } catch (e) { // normal text deleteJSONMaterialEditor(); - elPropertyElements["materialData"].value = properties.materialData; + getPropertyElement("materialData").value = selectedEntityProperties.materialData; showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); @@ -2397,16 +2569,16 @@ function loaded() { hideMaterialDataSaved(); } - if (properties.locked) { + if (selectedEntityProperties.locked) { disableProperties(); - elPropertyElements["locked"].removeAttribute('disabled'); + getPropertyElement("locked").removeAttribute('disabled'); } else { enableProperties(); disableSaveUserDataButton(); disableSaveMaterialDataButton() } - var activeElement = document.activeElement; + let activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); } @@ -2416,14 +2588,15 @@ function loaded() { } // Server Script Status - let elServerScript = elPropertyElements["serverScripts"]; + let elServerScript = getPropertyElement("serverScripts"); + let serverScriptElementID = getPropertyElementID("serverScripts"); let elDiv = document.createElement('div'); elDiv.setAttribute("class", "property"); let elLabel = document.createElement('label'); - elLabel.setAttribute("for", "property-serverScripts-status"); + elLabel.setAttribute("for", serverScriptElementID + "-status"); elLabel.innerText = "Server Script Status"; let elServerScriptStatus = document.createElement('span'); - elServerScriptStatus.setAttribute("id", "property-serverScripts-status"); + elServerScriptStatus.setAttribute("id", serverScriptElementID + "-status"); elDiv.appendChild(elLabel); elDiv.appendChild(elServerScriptStatus); elServerScript.parentNode.appendChild(elDiv); @@ -2432,46 +2605,48 @@ function loaded() { elDiv = document.createElement('div'); elDiv.setAttribute("class", "property"); let elServerScriptError = document.createElement('textarea'); - elServerScriptError.setAttribute("id", "property-serverScripts-error"); + elServerScriptError.setAttribute("id", serverScriptElementID + "-error"); elDiv.appendChild(elServerScriptError); elServerScript.parentNode.appendChild(elDiv); - let elScript = elPropertyElements["script"]; + let elScript = getPropertyElement("script"); elScript.parentNode.setAttribute("class", "property url refresh"); elServerScript.parentNode.setAttribute("class", "property url refresh"); // User Data - let elUserData = elPropertyElements["userData"]; + let elUserData = getPropertyElement("userData"); + let userDataElementID = getPropertyElementID("userData"); elDiv = elUserData.parentNode; let elStaticUserData = document.createElement('div'); - elStaticUserData.setAttribute("id", "property-userData-static"); + elStaticUserData.setAttribute("id", userDataElementID + "-static"); let elUserDataEditor = document.createElement('div'); - elUserDataEditor.setAttribute("id", "property-userData-editor"); + elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); let elUserDataSaved = document.createElement('span'); - elUserDataSaved.setAttribute("id", "property-userData-saved"); + elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); elUserDataSaved.innerText = "Saved!"; elDiv.childNodes[2].appendChild(elUserDataSaved); elDiv.insertBefore(elStaticUserData, elUserData); elDiv.insertBefore(elUserDataEditor, elUserData); // Material Data - let elMaterialData = elPropertyElements["materialData"]; + let elMaterialData = getPropertyElement("materialData"); + let materialDataElementID = getPropertyElementID("materialData"); elDiv = elMaterialData.parentNode; let elStaticMaterialData = document.createElement('div'); - elStaticMaterialData.setAttribute("id", "property-materialData-static"); + elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); let elMaterialDataEditor = document.createElement('div'); - elMaterialDataEditor.setAttribute("id", "property-materialData-editor"); + elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); let elMaterialDataSaved = document.createElement('span'); - elMaterialDataSaved.setAttribute("id", "property-materialData-saved"); + elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); elMaterialDataSaved.innerText = "Saved!"; elDiv.childNodes[2].appendChild(elMaterialDataSaved); elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData); // User Data Fields - let elGrabbable = elPropertyElements["grabbable"]; - let elTriggerable = elPropertyElements["triggerable"]; - let elIgnoreIK = elPropertyElements["ignoreIK"]; + let elGrabbable = getPropertyElement("grabbable"); + let elTriggerable = getPropertyElement("triggerable"); + let elIgnoreIK = getPropertyElement("ignoreIK"); elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); @@ -2483,11 +2658,15 @@ function loaded() { }); // Special Property Callbacks - let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; - let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; - let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; - elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); - elParentMaterialNameNumber.addEventListener('change', function () { updateProperty("parentMaterialName", this.value); }); + let elParentMaterialNameString = getPropertyElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyElement("selectSubmesh"); + elParentMaterialNameString.addEventListener('change', function () { + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); + }); + elParentMaterialNameNumber.addEventListener('change', function () { + updateProperty("parentMaterialName", this.value); + }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { updateProperty("parentMaterialName", elParentMaterialNameNumber.value); @@ -2498,7 +2677,7 @@ function loaded() { } }); - elPropertyElements["image"].addEventListener('change', createImageURLUpdateFunction('textures')); + getPropertyElement("image").addEventListener('change', createImageURLUpdateFunction('textures')); // Collapsible sections let elCollapsible = document.getElementsByClassName("section-header"); @@ -2544,40 +2723,7 @@ function loaded() { //
    • ...
    • //
    //
    - //
    - function setDropdownText(dropdown) { - let lis = dropdown.parentNode.getElementsByTagName("li"); - let text = ""; - for (let i = 0; i < lis.length; i++) { - if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { - text = lis[i].textContent; - } - } - dropdown.firstChild.textContent = text; - } - - function toggleDropdown(event) { - let element = event.target; - if (element.nodeName !== "DT") { - element = element.parentNode; - } - element = element.parentNode; - let isDropped = element.getAttribute("dropped"); - element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); - } - - function setDropdownValue(event) { - let dt = event.target.parentNode.parentNode.previousSibling; - dt.value = event.target.getAttribute("value"); - dt.firstChild.textContent = event.target.textContent; - - dt.parentNode.setAttribute("dropped", "false"); - - let evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", true, true); - dt.dispatchEvent(evt); - } - + //
let elDropdowns = document.getElementsByTagName("select"); for (let dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { let options = elDropdowns[dropDownIndex].getElementsByTagName("option"); @@ -2623,7 +2769,7 @@ function loaded() { } let propertyName = elDropdowns[dropDownIndex].getAttribute("propertyName"); - elPropertyElements[propertyName] = dt; + properties[propertyName].el = dt; dt.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); } From ac0e999629031c696c96e28b3ff6d2a89ebf891f Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 10 Oct 2018 17:31:06 -0700 Subject: [PATCH 041/131] fix colors --- scripts/system/html/js/entityProperties.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 2841414d87..a58b5ca8cd 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -928,7 +928,9 @@ function getPropertyElement(propertyName) { } function getPropertyElementID(propertyName) { - return "property-" + propertyName; + let propertyElementID = "property-" + propertyName; + propertyElementID = propertyElementID.replace(".", "-"); + return propertyElementID; } function enableChildren(el, selector) { @@ -1077,7 +1079,6 @@ function showGroupsForType(type) { } - // PROPERTY UPDATE FUNCTIONS function updateProperty(propertyName, propertyValue) { @@ -1207,7 +1208,6 @@ function createImageURLUpdateFunction(propertyName) { } - // PROPERTY ELEMENT CREATION FUNCTIONS function createStringProperty(elProperty, elLabel, propertyData) { @@ -1381,7 +1381,7 @@ function createColorProperty(elProperty, elLabel, propertyData) { }, onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b); } }); @@ -1573,7 +1573,6 @@ function addButtons(elProperty, propertyID, buttons, newRow) { } - // BUTTON CALLBACKS function rescaleDimensions() { @@ -1628,7 +1627,6 @@ function copySkyboxURLToAmbientURL() { } - // USER DATA FUNCTIONS function clearUserData() { @@ -1850,7 +1848,6 @@ function saveJSONUserData(noUpdate) { } - // MATERIAL DATA FUNCTIONS function clearMaterialData() { @@ -2035,7 +2032,6 @@ function bindAllNonJSONEditorElements() { } - // DROPDOWNS / TEXTAREAS / PARENT MATERIAL NAME FUNCTIONS function setDropdownText(dropdown) { @@ -2122,7 +2118,6 @@ function loaded() { let propertyType = property.type; let propertyName = property.propertyName; let propertyElementID = getPropertyElementID(propertyName); - propertyElementID = propertyElementID.replace(".", "-"); let elProperty; if (propertyType === "sub-header") { From a3a87ef48b1960b929eafef3adde9ae9c82f7af5 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 10 Oct 2018 17:52:26 -0700 Subject: [PATCH 042/131] Debug rendering of tracked objects, remove filters from body. --- interface/resources/controllers/vive.json | 29 ++++---------- interface/src/Application.cpp | 38 +++++++++++++++++++ .../filters/ExponentialSmoothingFilter.cpp | 2 +- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 8a7744efb3..42be6f3f04 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -51,30 +51,15 @@ { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, - { "from": "Vive.LeftHand", "to": "Standard.LeftHand"}, - { "from": "Vive.RightHand", "to": "Standard.RightHand"}, + { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Vive.RightHand", "to": "Standard.RightHand" }, - { - "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] - }, + { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot" }, + { "from": "Vive.RightFoot", "to" : "Standard.RightFoot" }, + { "from": "Vive.Hips", "to" : "Standard.Hips" }, + { "from": "Vive.Spine2", "to" : "Standard.Spine2" }, - { - "from": "Vive.RightFoot", "to" : "Standard.RightFoot", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] - }, - - { - "from": "Vive.Hips", "to" : "Standard.Hips", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] - }, - - { - "from": "Vive.Spine2", "to" : "Standard.Spine2", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] - }, - - { "from": "Vive.Head", "to" : "Standard.Head"}, + { "from": "Vive.Head", "to" : "Standard.Head" }, { "from": "Vive.RightArm", "to" : "Standard.RightArm" }, { "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index aa2b382c58..7457afe091 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5801,6 +5801,44 @@ void Application::update(float deltaTime) { controller::Pose pose = userInputMapper->getPoseState(action); myAvatar->setControllerPoseInSensorFrame(action, pose.transform(avatarToSensorMatrix)); } + + // AJT: TODO put a nice menu around this. + // Make sure to remove all markers when menu is turned off. + { + static const std::vector trackedObjectActions = { + controller::Action::TRACKED_OBJECT_00, + controller::Action::TRACKED_OBJECT_01, + controller::Action::TRACKED_OBJECT_02, + controller::Action::TRACKED_OBJECT_03, + controller::Action::TRACKED_OBJECT_04, + controller::Action::TRACKED_OBJECT_05, + controller::Action::TRACKED_OBJECT_06, + controller::Action::TRACKED_OBJECT_07, + controller::Action::TRACKED_OBJECT_08, + controller::Action::TRACKED_OBJECT_09, + controller::Action::TRACKED_OBJECT_10, + controller::Action::TRACKED_OBJECT_11, + controller::Action::TRACKED_OBJECT_12, + controller::Action::TRACKED_OBJECT_13, + controller::Action::TRACKED_OBJECT_14, + controller::Action::TRACKED_OBJECT_15 + }; + + int i = 0; + glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + for (auto& action : trackedObjectActions) { + QString key = QString("_TrackedObject%1").arg(i); + controller::Pose pose = userInputMapper->getPoseState(action); + if (pose.valid) { + glm::vec3 pos = transformPoint(myAvatarMatrix, pose.translation); + glm::quat rot = glmExtractRotation(myAvatarMatrix) * pose.rotation; + DebugDraw::getInstance().addMarker(key, rot, pos, BLUE); + } else { + DebugDraw::getInstance().removeMarker(key); + } + i++; + } + } } updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp index 9cf2673d55..0f204ce15f 100644 --- a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp @@ -35,7 +35,7 @@ namespace controller { if (_prevSensorValue.isValid()) { // exponential smoothing filter sensorValue.translation = _translationConstant * sensorValue.getTranslation() + (1.0f - _translationConstant) * _prevSensorValue.getTranslation(); - sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), _rotationConstant); + sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), (1.0f - _rotationConstant)); // remember previous sensor space value. _prevSensorValue = sensorValue; From e5c2605ac29097349bd4fb1e1ce792736ab98b38 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 10 Oct 2018 17:53:14 -0700 Subject: [PATCH 043/131] Added filtered-puck-attach app to test filters on tracked objects. --- .../developer/tests/filtered-puck-attach.js | 425 ++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 scripts/developer/tests/filtered-puck-attach.js diff --git a/scripts/developer/tests/filtered-puck-attach.js b/scripts/developer/tests/filtered-puck-attach.js new file mode 100644 index 0000000000..23217886d5 --- /dev/null +++ b/scripts/developer/tests/filtered-puck-attach.js @@ -0,0 +1,425 @@ +// +// Created by Anthony J. Thibault on 2017/06/20 +// Modified by Robbie Uvanni to support multiple pucks and easier placement of pucks on entities, on 2017/08/01 +// Copyright 2017 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 +// +// When this script is running, a new app button, named "PUCKTACH", will be added to the toolbar/tablet. +// Click this app to bring up the puck attachment panel. +// + +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +/* global Xform */ +Script.include("/~/system/libraries/Xform.js"); + +(function() { // BEGIN LOCAL_SCOPE + +var TABLET_BUTTON_NAME = "PUCKATTACH"; +var TABLET_APP_URL = "https://s3.amazonaws.com/hifi-public/tony/html/filtered-puck-attach.html?2"; +var NUM_TRACKED_OBJECTS = 16; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-a.svg" +}); + +var shown = false; +function onScreenChanged(type, url) { + if (type === "Web" && url === TABLET_APP_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} +tablet.screenChanged.connect(onScreenChanged); + +function pad(num, size) { + var tempString = "000000000" + num; + return tempString.substr(tempString.length - size); +} +function indexToTrackedObjectName(index) { + return "TrackedObject" + pad(index, 2); +} +function getAvailableTrackedObjects() { + var available = []; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + var key = indexToTrackedObjectName(i); + var pose = Controller.getPoseValue(Controller.Standard[key]); + if (pose && pose.valid) { + available.push(i); + } + } + return available; +} +function sendAvailableTrackedObjects() { + tablet.emitScriptEvent(JSON.stringify({ + pucks: getAvailableTrackedObjects(), + selectedPuck: ((lastPuck === undefined) ? -1 : lastPuck.name) + })); +} + +function getRelativePosition(origin, rotation, offset) { + var relativeOffset = Vec3.multiplyQbyV(rotation, offset); + var worldPosition = Vec3.sum(origin, relativeOffset); + return worldPosition; +} +function getPropertyForEntity(entityID, propertyName) { + return Entities.getEntityProperties(entityID, [propertyName])[propertyName]; +} +function entityExists(entityID) { + return Object.keys(Entities.getEntityProperties(entityID)).length > 0; +} + +var VIVE_PUCK_MODEL = "https://s3.amazonaws.com/hifi-public/tony/vive_tracker_puck_y180z180.obj"; +var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model +var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres +var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres +var VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE = 10.0; // metres +var VIVE_PUCK_NAME = "Tracked Puck"; + +var trackedPucks = { }; +var lastPuck; + +var DEFAULT_ROTATION_SMOOTHING_CONSTANT = 1.0; // no smoothing +var DEFAULT_TRANSLATION_SMOOTHING_CONSTANT = 1.0; // no smoothing +var DEFAULT_TRANSLATION_ACCELERATION_LIMIT = 1000; // only extreme accelerations are smoothed +var DEFAULT_ROTATION_ACCELERATION_LIMIT = 10000; // only extreme accelerations are smoothed + +function buildMappingJson() { + var obj = {name: "com.highfidelity.testing.filteredPuckAttach", channels: []}; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + obj.channels.push({ + from: "Vive." + indexToTrackedObjectName(i), + to: "Standard." + indexToTrackedObjectName(i), + filters: [ + { + type: "accelerationLimiter", + translationAccelerationLimit: DEFAULT_TRANSLATION_ACCELERATION_LIMIT, + rotationAccelerationLimit: DEFAULT_ROTATION_ACCELERATION_LIMIT + }, + { + type: "exponentialSmoothing", + translation: DEFAULT_TRANSLATION_SMOOTHING_CONSTANT, + rotation: DEFAULT_ROTATION_SMOOTHING_CONSTANT + } + ] + }); + } + return obj; +} + +var mappingJson = buildMappingJson(); + +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +function setTranslationAccelerationLimit(value) { + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationAccelerationLimit = value; + } + mappingChanged(); +} + +function setTranslationSnapThreshold(value) { + // TODO: convert from mm +} + +function setRotationAccelerationLimit(value) { + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; + } + mappingChanged(); +} + +function setRotationSnapThreshold(value) { + // TODO: convert from degrees +} + +function setTranslationSmoothingConstant(value) { + print("AJT: setting translation smoothing constant = " + value); + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[1].translation = value; + } + mappingChanged(); + print("AJT: done, value = " + value); +} + +function setRotationSmoothingConstant(value) { + print("AJT: setRotationSmoothingConstant =" + value); + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[1].rotation = value; + } + mappingChanged(); +} + + +function createPuck(puck) { + // create a puck entity and add it to our list of pucks + var action = indexToTrackedObjectName(puck.puckno); + var pose = Controller.getPoseValue(Controller.Standard[action]); + + if (pose && pose.valid) { + var spawnOffset = Vec3.multiply(Vec3.FRONT, VIVE_PUCK_SPAWN_DISTANCE); + var spawnPosition = getRelativePosition(MyAvatar.position, MyAvatar.orientation, spawnOffset); + + // should be an overlay + var puckEntityProperties = { + name: "Tracked Puck", + type: "Model", + modelURL: VIVE_PUCK_MODEL, + dimensions: VIVE_PUCK_DIMENSIONS, + position: spawnPosition, + userData: '{ "grabbableKey": { "grabbable": true, "kinematic": false } }' + }; + + var puckEntityID = Entities.addEntity(puckEntityProperties); + + // if we've already created this puck, destroy it + if (trackedPucks.hasOwnProperty(puck.puckno)) { + destroyPuck(puck.puckno); + } + // if we had an unfinalized puck, destroy it + if (lastPuck !== undefined) { + destroyPuck(lastPuck.name); + } + // create our new unfinalized puck + trackedPucks[puck.puckno] = { + puckEntityID: puckEntityID, + trackedEntityID: "" + }; + lastPuck = trackedPucks[puck.puckno]; + lastPuck.name = Number(puck.puckno); + } +} +function finalizePuck(puckName) { + // find nearest entity and change its parent to the puck + + if (!trackedPucks.hasOwnProperty(puckName)) { + print('2'); + return; + } + if (lastPuck === undefined) { + print('3'); + return; + } + if (lastPuck.name !== Number(puckName)) { + print('1'); + return; + } + + var puckPosition = getPropertyForEntity(lastPuck.puckEntityID, "position"); + var foundEntities = Entities.findEntities(puckPosition, VIVE_PUCK_SEARCH_DISTANCE); + + var foundEntity; + var leastDistance = Number.MAX_VALUE; + + for (var i = 0; i < foundEntities.length; i++) { + var entity = foundEntities[i]; + + if (getPropertyForEntity(entity, "name") !== VIVE_PUCK_NAME) { + var entityPosition = getPropertyForEntity(entity, "position"); + var d = Vec3.distance(entityPosition, puckPosition); + + if (d < leastDistance) { + leastDistance = d; + foundEntity = entity; + } + } + } + + if (foundEntity) { + lastPuck.trackedEntityID = foundEntity; + // remember the userdata and collisionless flag for the tracked entity since + // we're about to remove it and make it ungrabbable and collisionless + lastPuck.trackedEntityUserData = getPropertyForEntity(foundEntity, "userData"); + lastPuck.trackedEntityCollisionFlag = getPropertyForEntity(foundEntity, "collisionless"); + // update properties of the tracked entity + Entities.editEntity(lastPuck.trackedEntityID, { + "parentID": lastPuck.puckEntityID, + "userData": '{ "grabbableKey": { "grabbable": false } }', + "collisionless": 1 + }); + // remove reference to puck since it is now calibrated and finalized + lastPuck = undefined; + } +} +function updatePucks() { + // for each puck, update its position and orientation + for (var puckName in trackedPucks) { + if (!trackedPucks.hasOwnProperty(puckName)) { + continue; + } + var action = indexToTrackedObjectName(puckName); + var pose = Controller.getPoseValue(Controller.Standard[action]); + if (pose && pose.valid) { + var puck = trackedPucks[puckName]; + if (puck.trackedEntityID) { + if (entityExists(puck.trackedEntityID)) { + var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position); + var puckXform = new Xform(pose.rotation, pose.translation); + var finalXform = Xform.mul(avatarXform, puckXform); + + var d = Vec3.distance(MyAvatar.position, finalXform.pos); + if (d > VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE) { + print('tried to move tracked object too far away: ' + d); + return; + } + + Entities.editEntity(puck.puckEntityID, { + position: finalXform.pos, + rotation: finalXform.rot + }); + + // in case someone grabbed both entities and destroyed the + // child/parent relationship + Entities.editEntity(puck.trackedEntityID, { + parentID: puck.puckEntityID + }); + } else { + destroyPuck(puckName); + } + } + } + } +} +function destroyPuck(puckName) { + // unparent entity and delete its parent + if (!trackedPucks.hasOwnProperty(puckName)) { + return; + } + + var puck = trackedPucks[puckName]; + var puckEntityID = puck.puckEntityID; + var trackedEntityID = puck.trackedEntityID; + + // remove the puck as a parent entity and restore the tracked entities + // former userdata and collision flag + Entities.editEntity(trackedEntityID, { + "parentID": "{00000000-0000-0000-0000-000000000000}", + "userData": puck.trackedEntityUserData, + "collisionless": puck.trackedEntityCollisionFlag + }); + + delete trackedPucks[puckName]; + + // in some cases, the entity deletion may occur before the parent change + // has been processed, resulting in both the puck and the tracked entity + // to be deleted so we wait 100ms before deleting the puck, assuming + // that the parent change has occured + var DELETE_TIMEOUT = 100; // ms + Script.setTimeout(function() { + // delete the puck + Entities.deleteEntity(puckEntityID); + }, DELETE_TIMEOUT); +} +function destroyPucks() { + // remove all pucks and unparent entities + for (var puckName in trackedPucks) { + if (trackedPucks.hasOwnProperty(puckName)) { + destroyPuck(puckName); + } + } +} + +function onWebEventReceived(msg) { + var obj = {}; + + try { + obj = JSON.parse(msg); + } catch (err) { + return; + } + + print("AJT: onWebEventReceived = " + msg); + + switch (obj.cmd) { + case "ready": + sendAvailableTrackedObjects(); + break; + case "create": + createPuck(obj); + break; + case "finalize": + finalizePuck(obj.puckno); + break; + case "destroy": + destroyPuck(obj.puckno); + break; + case "translation-acceleration-limit": + setTranslationAccelerationLimit(Number(obj.val)); + break; + case "translation-snap-threshold": + setTranslationSnapThreshold(Number(obj.val)); + break; + case "rotation-acceleration-limit": + setRotationAccelerationLimit(Number(obj.val)); + break; + case "rotation-snap-threshold": + setRotationSnapThreshold(Number(obj.val)); + break; + case "translation-smoothing-constant": + setTranslationSmoothingConstant(Number(obj.val)); + break; + case "rotation-smoothing-constant": + setRotationSmoothingConstant(Number(obj.val)); + break; + } +} + +Script.update.connect(updatePucks); +Script.scriptEnding.connect(function () { + tablet.removeButton(tabletButton); + destroyPucks(); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); + if (mapping) { + mapping.disable(); + } +}); +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(TABLET_APP_URL); + } +}); +}()); // END LOCAL_SCOPE From 717a5ed31b0a2031cda2f4f9b032af6422dca0b7 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 11 Oct 2018 09:22:12 -0700 Subject: [PATCH 044/131] Added snap threshold to AccelerationLimiterFilter --- .../filters/AccelerationLimiterFilter.cpp | 23 +++++++++------ .../impl/filters/AccelerationLimiterFilter.h | 4 +-- .../developer/tests/filtered-puck-attach.js | 29 ++++++++++++++----- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp index aacbdd2cea..3db1a9fba6 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -18,9 +18,9 @@ #include static const QString JSON_ROTATION_ACCELERATION_LIMIT = QStringLiteral("rotationAccelerationLimit"); -static const QString JSON_ROTATION_DECELERATION_LIMIT = QStringLiteral("rotationDecelerationLimit"); static const QString JSON_TRANSLATION_ACCELERATION_LIMIT = QStringLiteral("translationAccelerationLimit"); -static const QString JSON_TRANSLATION_DECELERATION_LIMIT = QStringLiteral("translationDecelerationLimit"); +static const QString JSON_TRANSLATION_SNAP_THRESHOLD = QStringLiteral("translationSnapThreshold"); +static const QString JSON_ROTATION_SNAP_THRESHOLD = QStringLiteral("rotationSnapThreshold"); static glm::vec3 angularVelFromDeltaRot(const glm::quat& deltaQ, float dt) { // Measure the angular velocity of a delta rotation quaternion by using quaternion logarithm. @@ -39,7 +39,7 @@ static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { } static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, - float dt, const float accLimit) { + float dt, float accLimit, float snapThreshold) { // measure the linear velocities of this step and the previoius step glm::vec3 v1 = (x3 - x1) / (2.0f * dt); @@ -52,7 +52,8 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con float aLen = glm::length(a); // pick limit based on if we are moving faster then our target - if (aLen > accLimit) { + float distToTarget = glm::length(x3 - x2); + if (aLen > accLimit && distToTarget > snapThreshold) { // Solve for a new `v1`, such that `a` does not exceed `aLimit` // This combines two steps: // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: @@ -71,7 +72,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con } static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, - float dt, const float accLimit) { + float dt, float accLimit, float snapThreshold) { // ensure quaternions have the same polarity glm::quat q0 = q0In; @@ -87,7 +88,8 @@ static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, co float aLen = glm::length(a); // clamp the acceleration if it is over the limit - if (aLen > accLimit) { + float angleToTarget = glm::angle(q3 * glm::inverse(q2)); + if (aLen > accLimit && angleToTarget > snapThreshold) { // solve for a new w1, such that a does not exceed the accLimit w1 = a * ((accLimit * dt) / aLen) + w0; @@ -120,10 +122,10 @@ namespace controller { glm::vec3 unfilteredTranslation = sensorValue.translation; sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, - DELTA_TIME, _translationAccelerationLimit); + DELTA_TIME, _translationAccelerationLimit, _translationSnapThreshold); glm::quat unfilteredRot = sensorValue.rotation; sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, - DELTA_TIME, _rotationAccelerationLimit); + DELTA_TIME, _rotationAccelerationLimit, _rotationSnapThreshold); // remember previous values. _prevPos[0] = _prevPos[1]; @@ -175,9 +177,12 @@ namespace controller { bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { if (parameters.isObject()) { auto obj = parameters.toObject(); - if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT)) { + if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT) && + obj.contains(JSON_ROTATION_SNAP_THRESHOLD) && obj.contains(JSON_TRANSLATION_SNAP_THRESHOLD)) { _rotationAccelerationLimit = (float)obj[JSON_ROTATION_ACCELERATION_LIMIT].toDouble(); _translationAccelerationLimit = (float)obj[JSON_TRANSLATION_ACCELERATION_LIMIT].toDouble(); + _rotationSnapThreshold = (float)obj[JSON_ROTATION_SNAP_THRESHOLD].toDouble(); + _translationSnapThreshold = (float)obj[JSON_TRANSLATION_SNAP_THRESHOLD].toDouble(); return true; } } diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h index 06eeef1579..269fd54102 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -25,9 +25,9 @@ namespace controller { private: float _rotationAccelerationLimit { FLT_MAX }; - float _rotationDecelerationLimit { FLT_MAX }; float _translationAccelerationLimit { FLT_MAX }; - float _translationDecelerationLimit { FLT_MAX }; + float _rotationSnapThreshold { 0.0f }; + float _translationSnapThreshold { 0.0f }; mutable glm::vec3 _prevPos[3]; // sensor space mutable glm::quat _prevRot[3]; // sensor space diff --git a/scripts/developer/tests/filtered-puck-attach.js b/scripts/developer/tests/filtered-puck-attach.js index 23217886d5..ad9b17a0e4 100644 --- a/scripts/developer/tests/filtered-puck-attach.js +++ b/scripts/developer/tests/filtered-puck-attach.js @@ -101,6 +101,8 @@ var DEFAULT_ROTATION_SMOOTHING_CONSTANT = 1.0; // no smoothing var DEFAULT_TRANSLATION_SMOOTHING_CONSTANT = 1.0; // no smoothing var DEFAULT_TRANSLATION_ACCELERATION_LIMIT = 1000; // only extreme accelerations are smoothed var DEFAULT_ROTATION_ACCELERATION_LIMIT = 10000; // only extreme accelerations are smoothed +var DEFAULT_TRANSLATION_SNAP_THRESHOLD = 0; // no snapping +var DEFAULT_ROTATION_SNAP_THRESHOLD = 0; // no snapping function buildMappingJson() { var obj = {name: "com.highfidelity.testing.filteredPuckAttach", channels: []}; @@ -113,7 +115,9 @@ function buildMappingJson() { { type: "accelerationLimiter", translationAccelerationLimit: DEFAULT_TRANSLATION_ACCELERATION_LIMIT, - rotationAccelerationLimit: DEFAULT_ROTATION_ACCELERATION_LIMIT + rotationAccelerationLimit: DEFAULT_ROTATION_ACCELERATION_LIMIT, + translationSnapThreshold: DEFAULT_TRANSLATION_SNAP_THRESHOLD, + rotationSnapThreshold: DEFAULT_ROTATION_SNAP_THRESHOLD, }, { type: "exponentialSmoothing", @@ -154,7 +158,14 @@ function setTranslationAccelerationLimit(value) { } function setTranslationSnapThreshold(value) { - // TODO: convert from mm + // convert from mm + var MM_PER_M = 1000; + var meters = value / MM_PER_M; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationSnapThreshold = meters; + } + mappingChanged(); } function setRotationAccelerationLimit(value) { @@ -166,21 +177,25 @@ function setRotationAccelerationLimit(value) { } function setRotationSnapThreshold(value) { - // TODO: convert from degrees + // convert from degrees + var PI_IN_DEGREES = 180; + var radians = value * (Math.pi / PI_IN_DEGREES); + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationSnapThreshold = radians; + } + mappingChanged(); } function setTranslationSmoothingConstant(value) { - print("AJT: setting translation smoothing constant = " + value); var i; for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { mappingJson.channels[i].filters[1].translation = value; } mappingChanged(); - print("AJT: done, value = " + value); } function setRotationSmoothingConstant(value) { - print("AJT: setRotationSmoothingConstant =" + value); var i; for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { mappingJson.channels[i].filters[1].rotation = value; @@ -366,8 +381,6 @@ function onWebEventReceived(msg) { return; } - print("AJT: onWebEventReceived = " + msg); - switch (obj.cmd) { case "ready": sendAvailableTrackedObjects(); From 1adac78828403d0d26eb8e9ad7d0e8b76ee29b2c Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 11 Oct 2018 10:45:35 -0700 Subject: [PATCH 045/131] fix errors and set new anim parameters --- interface/src/avatar/AvatarManager.cpp | 19 +++++------- .../src/avatars-renderer/Avatar.cpp | 31 +++++++++++++------ .../src/avatars-renderer/Avatar.h | 5 ++- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1f2c9e462d..0bb1bc2e0a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -81,7 +81,7 @@ AvatarManager::AvatarManager(QObject* parent) : const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f; const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing - const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing + const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; // Based on testing const QString START_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; const QString MIDDLE_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; @@ -92,9 +92,9 @@ AvatarManager::AvatarManager(QObject* parent) : _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; _transitConfig._isDistanceBased = true; - _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 14); + _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 10); _transitConfig._middleTransitAnimation = AvatarTransit::TransitAnimation(MIDDLE_ANIMATION_URL, 15, 0); - _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 16, 38); + _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 22, 49); } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -180,8 +180,9 @@ void AvatarManager::updateMyAvatar(float deltaTime) { PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); - bool sendFirstTransitPackage = (status == AvatarTransit::Status::START_TRANSIT); - bool blockTransitData = (status == AvatarTransit::Status::TRANSITING); + if (status != AvatarTransit::Status::IDLE) { + playTransitAnimations(status); + } _myAvatar->update(deltaTime); render::Transaction transaction; @@ -191,13 +192,9 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - - if (sendFirstTransitPackage || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused && !blockTransitData)) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server - PerformanceTimer perfTimer("send"); - if (sendFirstTransitPackage) { - _myAvatar->overrideNextPackagePositionData(_myAvatar->getTransit()->getEndPosition()); - } + PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index f154746707..6ac808ec66 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -144,9 +144,12 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _totalDistance = glm::length(_transitLine); _easeType = config._easeType; const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - + _preTime = (float)config._startTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + _postTime = (float)config._endTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; - _totalTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; + _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; + _totalTime = _transitTime + _preTime + _postTime; _currentTime = 0.0f; _isTransiting = true; } @@ -173,19 +176,27 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { Status status = Status::IDLE; if (_isTransiting) { float nextTime = _currentTime + deltaTime; - glm::vec3 newPosition; - if (nextTime >= _totalTime) { - _currentPosition = _endPosition; - _isTransiting = false; - status = Status::END_TRANSIT; - } else { + if (nextTime < _preTime) { + _currentPosition = _startPosition; if (_currentTime == 0) { + status = Status::START_FRAME; + } + } else if (nextTime < _totalTime - _postTime){ + if (_currentTime <= _preTime) { status = Status::START_TRANSIT; } else { + float percentageIntoTransit = (nextTime - _preTime) / _transitTime; + _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; status = Status::TRANSITING; } - float percentageIntoTransit = nextTime / _totalTime; - _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; + } else { + _currentPosition = _endPosition; + if (nextTime >= _totalTime) { + _isTransiting = false; + status = Status::END_FRAME; + } else if (_currentTime < _totalTime - _postTime) { + status = Status::END_TRANSIT; + } } _currentTime = nextTime; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 21e359051f..ffe90ecaa2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -81,7 +81,7 @@ public: struct TransitConfig { TransitConfig() {}; int _totalFrames { 0 }; - int _framesPerMeter { 0 }; + float _framesPerMeter { 0.0f }; bool _isDistanceBased { false }; float _triggerDistance { 0 }; EaseType _easeType { EaseType::EASE_OUT }; @@ -115,7 +115,10 @@ private: glm::vec3 _transitLine; float _totalDistance { 0.0f }; + float _preTime { 0.0f }; float _totalTime { 0.0f }; + float _transitTime { 0.0f }; + float _postTime { 0.0f }; float _currentTime { 0.0f }; EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; From 2f10327ad36cc5bcd81d55efcb832e2e29a5765c Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 11 Oct 2018 10:57:39 -0700 Subject: [PATCH 046/131] Fix last anim value --- interface/src/avatar/AvatarManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 0bb1bc2e0a..87c0bc471b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -94,7 +94,7 @@ AvatarManager::AvatarManager(QObject* parent) : _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 10); _transitConfig._middleTransitAnimation = AvatarTransit::TransitAnimation(MIDDLE_ANIMATION_URL, 15, 0); - _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 22, 49); + _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 22, 27); } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { From de1b6e717fc8f8acb46e61697e9c14431feed210 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 11 Oct 2018 11:35:56 -0700 Subject: [PATCH 047/131] Cancel animation on chained trasits --- interface/src/avatar/AvatarManager.cpp | 2 +- .../src/avatars-renderer/Avatar.cpp | 15 +++++++-------- libraries/avatars/src/AvatarData.cpp | 2 +- libraries/avatars/src/AvatarData.h | 1 + 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 87c0bc471b..80e4e4ba66 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -313,7 +313,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } - auto transitStatus = avatar->_transit.update(deltaTime, avatar->_globalPosition, _transitConfig); + auto transitStatus = avatar->_transit.update(deltaTime, avatar->_lastPosition, _transitConfig); if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { avatar->_transit.reset(); avatar->setIsNewAvatar(false); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 6ac808ec66..53c2019138 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -114,19 +114,18 @@ void Avatar::setShowNamesAboveHeads(bool show) { } AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { - glm::vec3 currentPosition = _isTransiting ? _currentPosition : avatarPosition; - float oneFrameDistance = glm::length(currentPosition - _lastPosition); + float oneFrameDistance = _isTransiting ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); const float MAX_TRANSIT_DISTANCE = 30.0f; float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance && !_isTransiting) { + if (oneFrameDistance > config._triggerDistance) { if (oneFrameDistance < scaledMaxTransitDistance) { - start(deltaTime, _lastPosition, currentPosition, config); + start(deltaTime, _lastPosition, avatarPosition, config); } else { - _lastPosition = currentPosition; - return Status::ABORT_TRANSIT; + _lastPosition = avatarPosition; + _status = Status::ABORT_TRANSIT; } } - _lastPosition = currentPosition; + _lastPosition = avatarPosition; _status = updatePosition(deltaTime); return _status; } @@ -150,7 +149,7 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; _totalTime = _transitTime + _preTime + _postTime; - _currentTime = 0.0f; + _currentTime = _isTransiting ? _preTime : 0.0f; _isTransiting = true; } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d78d83bc09..a22cc4a1d3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -896,7 +896,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; if (_globalPosition != newValue) { - _globalPosition = newValue; + _lastPosition = _globalPosition = newValue; _globalPositionChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 46489451f7..dcdaa70ad7 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1378,6 +1378,7 @@ protected: // where Entities are located. This is currently only used by the mixer to decide how often to send // updates about one avatar to another. glm::vec3 _globalPosition { 0, 0, 0 }; + glm::vec3 _lastPosition { 0, 0, 0 }; glm::vec3 _globalPositionOverride { 0, 0, 0 }; bool _overrideGlobalPosition { false }; From 5528050898de75444b1caff4d6b3420beee488f8 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Oct 2018 12:44:07 -0700 Subject: [PATCH 048/131] CR changes - propertyName vs propertyID, store name/elementID, return property element --- scripts/system/html/js/entityProperties.js | 527 ++++++++++----------- 1 file changed, 257 insertions(+), 270 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a58b5ca8cd..fcd9251485 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -40,36 +40,35 @@ const GROUPS = [ label: NO_SELECTION, type: "icon", icons: ICON_FOR_TYPE, - defaultValue: "Box", - propertyName: "type", + propertyID: "type", }, { label: "Name", type: "string", - propertyName: "name", + propertyID: "name", }, { label: "ID", type: "string", - propertyName: "id", + propertyID: "id", readOnly: true, }, { label: "Parent", type: "string", - propertyName: "parentID", + propertyID: "parentID", }, { label: "Locked", glyph: "", type: "bool", - propertyName: "locked", + propertyID: "locked", }, { label: "Visible", glyph: "", type: "bool", - propertyName: "visible", + propertyID: "visible", }, ] }, @@ -84,12 +83,12 @@ const GROUPS = [ Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, - propertyName: "shape", + propertyID: "shape", }, { label: "Color", type: "color", - propertyName: "color", + propertyID: "color", }, ] }, @@ -100,17 +99,17 @@ const GROUPS = [ { label: "Text", type: "string", - propertyName: "text", + propertyID: "text", }, { label: "Text Color", type: "color", - propertyName: "textColor", + propertyID: "textColor", }, { label: "Background Color", type: "color", - propertyName: "backgroundColor", + propertyID: "backgroundColor", }, { label: "Line Height", @@ -119,12 +118,12 @@ const GROUPS = [ step: 0.005, fixedDecimals: 4, unit: "m", - propertyName: "lineHeight" + propertyID: "lineHeight" }, { label: "Face Camera", type: "bool", - propertyName: "faceCamera" + propertyID: "faceCamera" }, ] }, @@ -135,29 +134,29 @@ const GROUPS = [ { label: "Flying Allowed", type: "bool", - propertyName: "flyingAllowed", + propertyID: "flyingAllowed", }, { label: "Ghosting Allowed", type: "bool", - propertyName: "ghostingAllowed", + propertyID: "ghostingAllowed", }, { label: "Filter", type: "string", - propertyName: "filterURL", + propertyID: "filterURL", }, { label: "Key Light", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "keyLightMode", + propertyID: "keyLightMode", }, { label: "Key Light Color", type: "color", - propertyName: "keyLight.color", + propertyID: "keyLight.color", showPropertyRule: { "keyLightMode": "enabled" }, }, { @@ -167,7 +166,7 @@ const GROUPS = [ max: 10, step: 0.1, fixedDecimals: 2, - propertyName: "keyLight.intensity", + propertyID: "keyLight.intensity", showPropertyRule: { "keyLightMode": "enabled" }, }, { @@ -175,7 +174,7 @@ const GROUPS = [ type: "number", fixedDecimals: 2, unit: "deg", - propertyName: "keyLight.direction.y", + propertyID: "keyLight.direction.y", showPropertyRule: { "keyLightMode": "enabled" }, }, { @@ -183,45 +182,45 @@ const GROUPS = [ type: "number", fixedDecimals: 2, unit: "deg", - propertyName: "keyLight.direction.x", + propertyID: "keyLight.direction.x", showPropertyRule: { "keyLightMode": "enabled" }, }, { label: "Cast Shadows", type: "bool", - propertyName: "keyLight.castShadows", + propertyID: "keyLight.castShadows", showPropertyRule: { "keyLightMode": "enabled" }, }, { label: "Skybox", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "skyboxMode", + propertyID: "skyboxMode", }, { label: "Skybox Color", type: "color", - propertyName: "skybox.color", + propertyID: "skybox.color", showPropertyRule: { "skyboxMode": "enabled" }, }, { label: "Skybox URL", type: "string", - propertyName: "skybox.url", + propertyID: "skybox.url", showPropertyRule: { "skyboxMode": "enabled" }, }, { type: "buttons", buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], - propertyName: "copyURLToAmbient", + propertyID: "copyURLToAmbient", showPropertyRule: { "skyboxMode": "enabled" }, }, { label: "Ambient Light", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "ambientLightMode", + propertyID: "ambientLightMode", }, { label: "Ambient Intensity", @@ -230,20 +229,20 @@ const GROUPS = [ max: 10, step: 0.1, fixedDecimals: 2, - propertyName: "ambientLight.ambientIntensity", + propertyID: "ambientLight.ambientIntensity", showPropertyRule: { "ambientLightMode": "enabled" }, }, { label: "Ambient URL", type: "string", - propertyName: "ambientLight.ambientURL", + propertyID: "ambientLight.ambientURL", showPropertyRule: { "ambientLightMode": "enabled" }, }, { label: "Haze", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "hazeMode", + propertyID: "hazeMode", }, { label: "Range", @@ -253,13 +252,13 @@ const GROUPS = [ step: 5, fixedDecimals: 0, unit: "m", - propertyName: "haze.hazeRange", + propertyID: "haze.hazeRange", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Use Altitude", type: "bool", - propertyName: "haze.hazeAltitudeEffect", + propertyID: "haze.hazeAltitudeEffect", showPropertyRule: { "hazeMode": "enabled" }, }, { @@ -270,7 +269,7 @@ const GROUPS = [ step: 10, fixedDecimals: 0, unit: "m", - propertyName: "haze.hazeBaseRef", + propertyID: "haze.hazeBaseRef", showPropertyRule: { "hazeMode": "enabled" }, }, { @@ -281,13 +280,13 @@ const GROUPS = [ step: 10, fixedDecimals: 0, unit: "m", - propertyName: "haze.hazeCeiling", + propertyID: "haze.hazeCeiling", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Haze Color", type: "color", - propertyName: "haze.hazeColor", + propertyID: "haze.hazeColor", showPropertyRule: { "hazeMode": "enabled" }, }, { @@ -297,19 +296,19 @@ const GROUPS = [ max: 1.0, step: 0.01, fixedDecimals: 2, - propertyName: "haze.hazeBackgroundBlend", + propertyID: "haze.hazeBackgroundBlend", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Enable Glare", type: "bool", - propertyName: "haze.hazeEnableGlare", + propertyID: "haze.hazeEnableGlare", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Glare Color", type: "color", - propertyName: "haze.hazeGlareColor", + propertyID: "haze.hazeGlareColor", showPropertyRule: { "hazeMode": "enabled" }, }, { @@ -319,14 +318,14 @@ const GROUPS = [ max: 180, step: 1, fixedDecimals: 0, - propertyName: "haze.hazeGlareAngle", + propertyID: "haze.hazeGlareAngle", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Bloom", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "bloomMode", + propertyID: "bloomMode", }, { label: "Bloom Intensity", @@ -335,7 +334,7 @@ const GROUPS = [ max: 1, step: 0.01, fixedDecimals: 2, - propertyName: "bloom.bloomIntensity", + propertyID: "bloom.bloomIntensity", showPropertyRule: { "bloomMode": "enabled" }, }, { @@ -345,7 +344,7 @@ const GROUPS = [ min: 1, step: 0.01, fixedDecimals: 2, - propertyName: "bloom.bloomThreshold", + propertyID: "bloom.bloomThreshold", showPropertyRule: { "bloomMode": "enabled" }, }, { @@ -355,7 +354,7 @@ const GROUPS = [ min: 2, step: 0.01, fixedDecimals: 2, - propertyName: "bloom.bloomSize", + propertyID: "bloom.bloomSize", showPropertyRule: { "bloomMode": "enabled" }, }, ] @@ -367,7 +366,7 @@ const GROUPS = [ { label: "Model", type: "string", - propertyName: "modelURL", + propertyID: "modelURL", }, { label: "Collision Shape", @@ -375,67 +374,67 @@ const GROUPS = [ options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, - propertyName: "shapeType", + propertyID: "shapeType", }, { label: "Compound Shape", type: "string", - propertyName: "compoundShapeURL", + propertyID: "compoundShapeURL", }, { label: "Animation", type: "string", - propertyName: "animation.url", + propertyID: "animation.url", }, { label: "Play Automatically", type: "bool", - propertyName: "animation.running", + propertyID: "animation.running", }, { label: "Allow Transition", type: "bool", - propertyName: "animation.allowTranslation", + propertyID: "animation.allowTranslation", }, { label: "Loop", type: "bool", - propertyName: "animation.loop", + propertyID: "animation.loop", }, { label: "Hold", type: "bool", - propertyName: "animation.hold", + propertyID: "animation.hold", }, { label: "Animation Frame", type: "number", - propertyName: "animation.currentFrame", + propertyID: "animation.currentFrame", }, { label: "First Frame", type: "number", - propertyName: "animation.firstFrame", + propertyID: "animation.firstFrame", }, { label: "Last Frame", type: "number", - propertyName: "animation.lastFrame", + propertyID: "animation.lastFrame", }, { label: "Animation FPS", type: "number", - propertyName: "animation.fps", + propertyID: "animation.fps", }, { label: "Texture", type: "textarea", - propertyName: "textures", + propertyID: "textures", }, { label: "Original Texture", type: "textarea", - propertyName: "originalTextures", + propertyID: "originalTextures", readOnly: true, }, ] @@ -447,7 +446,7 @@ const GROUPS = [ { label: "Image", type: "string", - propertyName: "image", + propertyID: "image", }, ] }, @@ -458,24 +457,26 @@ const GROUPS = [ { label: "Source", type: "string", - propertyName: "sourceUrl", + propertyID: "sourceUrl", }, { label: "Source Resolution", type: "number", - propertyName: "dpi", + propertyID: "dpi", }, ] }, { id: "light", addToGroup: "base", + addToGroup: "base", properties: [ { label: "Light Color", type: "color", - propertyName: "lightColor", // this actually shares "color" property with shape Color but + propertyID: "lightColor", // this actually shares "color" property with shape Color but // separating naming here to distinguish property element/data + propertyName: "color", }, { label: "Intensity", @@ -483,7 +484,7 @@ const GROUPS = [ min: 0, step: 0.1, fixedDecimals: 1, - propertyName: "intensity", + propertyID: "intensity", }, { label: "Fall-Off Radius", @@ -492,26 +493,26 @@ const GROUPS = [ step: 0.1, fixedDecimals: 1, unit: "m", - propertyName: "falloffRadius", + propertyID: "falloffRadius", }, { label: "Spotlight", type: "bool", - propertyName: "isSpotlight", + propertyID: "isSpotlight", }, { label: "Spotlight Exponent", type: "number", step: 0.01, fixedDecimals: 2, - propertyName: "exponent", + propertyID: "exponent", }, { label: "Spotlight Cut-Off", type: "number", step: 0.01, fixedDecimals: 2, - propertyName: "cutoff", + propertyID: "cutoff", }, ] }, @@ -522,7 +523,7 @@ const GROUPS = [ { label: "Material URL", type: "string", - propertyName: "materialURL", + propertyID: "materialURL", }, { label: "Material Data", @@ -530,30 +531,30 @@ const GROUPS = [ buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], - propertyName: "materialData", + propertyID: "materialData", }, { label: "Submesh to Replace", type: "number", min: 0, step: 1, - propertyName: "submeshToReplace", + propertyID: "submeshToReplace", }, { label: "Material Name to Replace", type: "string", - propertyName: "materialNameToReplace", + propertyID: "materialNameToReplace", }, { label: "Select Submesh", type: "bool", - propertyName: "selectSubmesh", + propertyID: "selectSubmesh", }, { label: "Priority", type: "number", min: 0, - propertyName: "priority", + propertyID: "priority", }, { label: "Material Position", @@ -563,7 +564,7 @@ const GROUPS = [ step: 0.1, vec2Type: "xy", subLabels: [ "x", "y" ], - propertyName: "materialMappingPos", + propertyID: "materialMappingPos", }, { label: "Material Scale", @@ -572,7 +573,7 @@ const GROUPS = [ step: 0.1, vec2Type: "wh", subLabels: [ "width", "height" ], - propertyName: "materialMappingScale", + propertyID: "materialMappingScale", }, { label: "Material Rotation", @@ -580,7 +581,7 @@ const GROUPS = [ step: 0.1, fixedDecimals: 2, unit: "deg", - propertyName: "materialMappingRot", + propertyID: "materialMappingRot", }, ] }, @@ -594,7 +595,7 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m", - propertyName: "position", + propertyID: "position", }, { label: "Rotation", @@ -603,7 +604,7 @@ const GROUPS = [ vec3Type: "pyr", subLabels: [ "pitch", "yaw", "roll" ], unit: "deg", - propertyName: "rotation", + propertyID: "rotation", }, { label: "Dimension", @@ -612,7 +613,7 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m", - propertyName: "dimensions", + propertyID: "dimensions", }, { label: "Scale", @@ -621,7 +622,7 @@ const GROUPS = [ unit: "%", buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], - propertyName: "scale", + propertyID: "scale", }, { label: "Pivot", @@ -630,14 +631,14 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "(ratio of dimension)", - propertyName: "registrationPoint", + propertyID: "registrationPoint", }, { label: "Align", type: "buttons", buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], - propertyName: "alignToGrid", + propertyID: "alignToGrid", }, ] }, @@ -650,33 +651,33 @@ const GROUPS = [ label: "Collides", type: "bool", inverse: true, - propertyName: "collisionless", + propertyID: "collisionless", column: -1, // before two columns div }, { label: "Dynamic", type: "bool", - propertyName: "dynamic", + propertyID: "dynamic", column: -1, // before two columns div }, { label: "Collides With", type: "sub-header", - propertyName: "collidesWithHeader", // not actually a property but used for naming/storing this element + propertyID: "collidesWithHeader", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, column: 1, }, { label: "", type: "sub-header", - propertyName: "collidesWithHeaderHelper", // not actually a property but used for naming/storing this element + propertyID: "collidesWithHeaderHelper", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, column: 2, }, { label: "Static Entities", type: "bool", - propertyName: "static", + propertyID: "static", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 1, @@ -684,7 +685,7 @@ const GROUPS = [ { label: "Dynamic Entities", type: "bool", - propertyName: "dynamic", + propertyID: "dynamic", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 2, @@ -692,7 +693,7 @@ const GROUPS = [ { label: "Kinematic Entities", type: "bool", - propertyName: "kinematic", + propertyID: "kinematic", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 1, @@ -700,7 +701,7 @@ const GROUPS = [ { label: "My Avatar", type: "bool", - propertyName: "myAvatar", + propertyID: "myAvatar", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 2, @@ -708,7 +709,7 @@ const GROUPS = [ { label: "Other Avatars", type: "bool", - propertyName: "otherAvatar", + propertyID: "otherAvatar", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 1, @@ -716,7 +717,7 @@ const GROUPS = [ { label: "Collision sound URL", type: "string", - propertyName: "collisionSoundURL", + propertyID: "collisionSoundURL", showPropertyRule: { "collisionless": "false" }, // having no column number means place this after two columns div }, @@ -730,78 +731,78 @@ const GROUPS = [ { label: "Grabbable", type: "bool", - propertyName: "grabbable", + propertyID: "grabbable", column: 1, }, { label: "Triggerable", type: "bool", - propertyName: "triggerable", + propertyID: "triggerable", column: 2, }, { label: "Cloneable", type: "bool", - propertyName: "cloneable", + propertyID: "cloneable", column: 1, }, { label: "Ignore inverse kinematics", type: "bool", - propertyName: "ignoreIK", + propertyID: "ignoreIK", column: 2, }, { label: "Clone Lifetime", type: "number", unit: "s", - propertyName: "cloneLifetime", + propertyID: "cloneLifetime", showPropertyRule: { "cloneable": "true" }, column: 1, }, { label: "Clone Limit", type: "number", - propertyName: "cloneLimit", + propertyID: "cloneLimit", showPropertyRule: { "cloneable": "true" }, column: 1, }, { label: "Clone Dynamic", type: "bool", - propertyName: "cloneDynamic", + propertyID: "cloneDynamic", showPropertyRule: { "cloneable": "true" }, column: 1, }, { label: "Clone Avatar Entity", type: "bool", - propertyName: "cloneAvatarEntity", + propertyID: "cloneAvatarEntity", showPropertyRule: { "cloneable": "true" }, column: 1, }, { // below properties having no column number means place them after two columns div label: "Can cast shadow", type: "bool", - propertyName: "canCastShadow", + propertyID: "canCastShadow", }, { label: "Script", type: "string", buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], - propertyName: "script", + propertyID: "script", }, { label: "Server Script", type: "string", buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], - propertyName: "serverScripts", + propertyID: "serverScripts", }, { label: "Lifetime", type: "number", unit: "s", - propertyName: "lifetime", + propertyID: "lifetime", }, { label: "User Data", @@ -809,7 +810,7 @@ const GROUPS = [ buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], - propertyName: "userData", + propertyID: "userData", }, ] }, @@ -823,13 +824,13 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m/s", - propertyName: "velocity", + propertyID: "velocity", }, { label: "Linear Damping", type: "number", fixedDecimals: 2, - propertyName: "damping", + propertyID: "damping", }, { label: "Angular Velocity", @@ -838,31 +839,31 @@ const GROUPS = [ vec3Type: "pyr", subLabels: [ "pitch", "yaw", "roll" ], unit: "deg/s", - propertyName: "angularVelocity", + propertyID: "angularVelocity", }, { label: "Angular Damping", type: "number", fixedDecimals: 4, - propertyName: "angularDamping", + propertyID: "angularDamping", }, { label: "Bounciness", type: "number", fixedDecimals: 4, - propertyName: "restitution", + propertyID: "restitution", }, { label: "Friction", type: "number", fixedDecimals: 4, - propertyName: "friction", + propertyID: "friction", }, { label: "Density", type: "number", fixedDecimals: 4, - propertyName: "density", + propertyID: "density", }, { label: "Gravity", @@ -870,7 +871,7 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m/s2", - propertyName: "gravity", + propertyID: "gravity", }, { label: "Acceleration", @@ -878,7 +879,7 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m/s2", - propertyName: "acceleration", + propertyID: "acceleration", }, ] }, @@ -923,14 +924,8 @@ function debugPrint(message) { // GENERAL PROPERTY/GROUP FUNCTIONS -function getPropertyElement(propertyName) { - return properties[propertyName].el; -} - -function getPropertyElementID(propertyName) { - let propertyElementID = "property-" + propertyName; - propertyElementID = propertyElementID.replace(".", "-"); - return propertyElementID; +function getPropertyElement(propertyID) { + return properties[propertyID].el; } function enableChildren(el, selector) { @@ -976,8 +971,8 @@ function disableProperties() { } } -function showPropertyElement(propertyName, show) { - let elProperty = properties[propertyName].el; +function showPropertyElement(propertyID, show) { + let elProperty = properties[propertyID].el; let elNode = elProperty; if (elNode.nodeName !== "DIV") { let elParent = elProperty.parentNode; @@ -992,9 +987,9 @@ function showPropertyElement(propertyName, show) { } function resetProperties() { - for (let propertyName in properties) { - let elProperty = properties[propertyName].el; - let propertyData = properties[propertyName].data; + for (let propertyID in properties) { + let elProperty = properties[propertyID].el; + let propertyData = properties[propertyID].data; switch (propertyData.type) { case 'string': { @@ -1043,16 +1038,13 @@ function resetProperties() { } case 'icon': { // icon is array of elSpan (icon glyph) and elLabel - if (propertyData.defaultValue !== undefined) { - elProperty[0].innerHTML = propertyData.icons[propertyData.defaultValue]; - elProperty[0].style.display = "inline-block"; - elProperty[1].innerHTML = propertyData.defaultValue; - } + elProperty[0].style.display = "none"; + elProperty[1].innerHTML = propertyData.label; break; } } - let showPropertyRules = properties[propertyName].showPropertyRules; + let showPropertyRules = properties[propertyID].showPropertyRules; if (showPropertyRules !== undefined) { for (let propertyToHide in showPropertyRules) { showPropertyElement(propertyToHide, false); @@ -1131,61 +1123,61 @@ function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { }; } -function createEmitVec2PropertyUpdateFunction(property, elX, elY) { +function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY) { return function () { let newValue = { x: elX.value, y: elY.value }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); }; } -function createEmitVec2PropertyUpdateFunctionWithMultiplier(property, elX, elY, multiplier) { +function createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elX, elY, multiplier) { return function () { let newValue = { x: elX.value * multiplier, y: elY.value * multiplier }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); }; } -function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { +function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ) { return function() { let newValue = { x: elX.value, y: elY.value, z: elZ ? elZ.value : 0 }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); }; } -function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { +function createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elX, elY, elZ, multiplier) { return function() { let newValue = { x: elX.value * multiplier, y: elY.value * multiplier, z: elZ.value * multiplier }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); }; } -function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) { +function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue) { return function() { - emitColorPropertyUpdate(property, elRed.value, elGreen.value, elBlue.value); + emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value); }; } -function emitColorPropertyUpdate(property, red, green, blue) { +function emitColorPropertyUpdate(propertyName, red, green, blue) { let newValue = { red: red, green: green, blue: blue }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); } function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { @@ -1210,14 +1202,15 @@ function createImageURLUpdateFunction(propertyName) { // PROPERTY ELEMENT CREATION FUNCTIONS -function createStringProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createStringProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property text"); let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("id", elementID); elInput.setAttribute("type", "text"); if (propertyData.readOnly) { elInput.readOnly = true; @@ -1229,15 +1222,16 @@ function createStringProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elInput); if (propertyData.buttons !== undefined) { - addButtons(elProperty, propertyElementID, propertyData.buttons, false); + addButtons(elProperty, elementID, propertyData.buttons, false); } - properties[propertyName].el = elInput; + return elInput; } -function createBoolProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createBoolProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property checkbox"); @@ -1249,7 +1243,7 @@ function createBoolProperty(elProperty, elLabel, propertyData) { } let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("id", elementID); elInput.setAttribute("type", "checkbox"); elProperty.appendChild(elInput); @@ -1264,12 +1258,13 @@ function createBoolProperty(elProperty, elLabel, propertyData) { elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse)); } - properties[propertyName].el = elInput; + return elInput; } -function createVec3Property(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createVec3Property(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property " + propertyData.vec3Type + " fstuple"); @@ -1281,11 +1276,11 @@ function createVec3Property(elProperty, elLabel, propertyData) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputX = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[0], + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[0], propertyData.min, propertyData.max, propertyData.step); - let elInputY = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[1], + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], propertyData.min, propertyData.max, propertyData.step); - let elInputZ = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[2], + let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[2], propertyData.min, propertyData.max, propertyData.step); let inputChangeFunction; @@ -1299,12 +1294,13 @@ function createVec3Property(elProperty, elLabel, propertyData) { elInputY.addEventListener('change', inputChangeFunction); elInputZ.addEventListener('change', inputChangeFunction); - properties[propertyName].el = [ elInputX, elInputY, elInputZ ]; + return [ elInputX, elInputY, elInputZ ]; } -function createVec2Property(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createVec2Property(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property " + propertyData.vec2Type + " fstuple"); @@ -1316,9 +1312,9 @@ function createVec2Property(elProperty, elLabel, propertyData) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputX = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[0], + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[0], propertyData.min, propertyData.max, propertyData.step); - let elInputY = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[1], + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], propertyData.min, propertyData.max, propertyData.step); let inputChangeFunction; @@ -1331,18 +1327,18 @@ function createVec2Property(elProperty, elLabel, propertyData) { elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); - properties[propertyName].el = [elInputX, elInputY]; + return [elInputX, elInputY]; } -function createColorProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createColorProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; elProperty.setAttribute("class", "property rgb fstuple"); let elColorPicker = document.createElement('div'); elColorPicker.setAttribute("class", "color-picker"); - elColorPicker.setAttribute("id", propertyElementID); + elColorPicker.setAttribute("id", elementID); let elTuple = document.createElement('div'); elTuple.setAttribute("class", "tuple"); @@ -1351,16 +1347,16 @@ function createColorProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputR = createTupleNumberInput(elTuple, propertyElementID, "red", 0, 255, 1); - let elInputG = createTupleNumberInput(elTuple, propertyElementID, "green", 0, 255, 1); - let elInputB = createTupleNumberInput(elTuple, propertyElementID, "blue", 0, 255, 1); + let elInputR = createTupleNumberInput(elTuple, elementID, "red", 0, 255, 1); + let elInputG = createTupleNumberInput(elTuple, elementID, "green", 0, 255, 1); + let elInputB = createTupleNumberInput(elTuple, elementID, "blue", 0, 255, 1); let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); elInputR.addEventListener('change', inputChangeFunction); elInputG.addEventListener('change', inputChangeFunction); elInputB.addEventListener('change', inputChangeFunction); - let colorPickerID = "#" + propertyElementID; + let colorPickerID = "#" + elementID; colorPickers[colorPickerID] = $(colorPickerID).colpick({ colorScheme: 'dark', layout: 'hex', @@ -1385,18 +1381,19 @@ function createColorProperty(elProperty, elLabel, propertyData) { } }); - properties[propertyName].el = [elColorPicker, elInputR, elInputG, elInputB]; + return [elColorPicker, elInputR, elInputG, elInputB]; } -function createDropdownProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createDropdownProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property dropdown"); let elInput = document.createElement('select'); - elInput.setAttribute("id", propertyElementID); - elInput.setAttribute("propertyName", propertyName); + elInput.setAttribute("id", elementID); + elInput.setAttribute("propertyID", propertyData.propertyID); for (let optionKey in propertyData.options) { let option = document.createElement('option'); @@ -1410,33 +1407,24 @@ function createDropdownProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elLabel); elProperty.appendChild(elInput); - properties[propertyName].el = elInput; + return elInput; } -function createNumberProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createNumberProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property number"); addUnit(propertyData.unit, elLabel); let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("id", elementID); elInput.setAttribute("type", "number"); - if (propertyData.min !== undefined) { - elInput.setAttribute("min", propertyData.min); - } - if (propertyData.max !== undefined) { - elInput.setAttribute("max", propertyData.max); - } - if (propertyData.step !== undefined) { - elInput.setAttribute("step", propertyData.step); - } let defaultValue = propertyData.defaultValue; if (defaultValue !== undefined) { - elInput.setAttribute("defaultValue", defaultValue); elInput.value = defaultValue; } @@ -1447,26 +1435,27 @@ function createNumberProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elInput); if (propertyData.buttons !== undefined) { - addButtons(elProperty, propertyElementID, propertyData.buttons, true); + addButtons(elProperty, elementID, propertyData.buttons, true); } - properties[propertyName].el = elInput; + return elInput; } -function createTextareaProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createTextareaProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property textarea"); elProperty.appendChild(elLabel); if (propertyData.buttons !== undefined) { - addButtons(elProperty, propertyElementID, propertyData.buttons, true); + addButtons(elProperty, elementID, propertyData.buttons, true); } let elInput = document.createElement('textarea'); - elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("id", elementID); if (propertyData.readOnly) { elInput.readOnly = true; } @@ -1475,30 +1464,30 @@ function createTextareaProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elInput); - properties[propertyName].el = elInput; + return elInput; } -function createIconProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createIconProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property value"); - elLabel.setAttribute("id", propertyElementID); + elLabel.setAttribute("id", elementID); elLabel.innerHTML = " " + propertyData.label; let elSpan = document.createElement('span'); - elSpan.setAttribute("id", propertyElementID + "-icon"); + elSpan.setAttribute("id", elementID + "-icon"); elProperty.appendChild(elSpan); elProperty.appendChild(elLabel); - properties[propertyName].el = [ elSpan, elLabel ]; + return [ elSpan, elLabel ]; } -function createButtonsProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createButtonsProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property text"); @@ -1508,34 +1497,29 @@ function createButtonsProperty(elProperty, elLabel, propertyData) { } if (propertyData.buttons !== undefined) { - addButtons(elProperty, propertyElementID, propertyData.buttons, hasLabel); + addButtons(elProperty, elementID, propertyData.buttons, hasLabel); } - properties[propertyName].el = elProperty; + return elProperty; } -function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { - let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); +function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, step) { + let elementID = propertyElementID + "-" + subLabel.toLowerCase(); + let elDiv = document.createElement('div'); let elLabel = document.createElement('label'); elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; - elLabel.setAttribute("for", elementPropertyID); + elLabel.setAttribute("for", elementID); + let elInput = document.createElement('input'); - elInput.setAttribute("id", elementPropertyID); + elInput.setAttribute("id", elementID); elInput.setAttribute("type", "number"); elInput.setAttribute("class", subLabel); - if (min !== undefined) { - elInput.setAttribute("min", min); - } - if (max !== undefined) { - elInput.setAttribute("max", max); - } - if (step !== undefined) { - elInput.setAttribute("step", step); - } + elDiv.appendChild(elInput); elDiv.appendChild(elLabel); elTuple.appendChild(elDiv); + return elInput; } @@ -2114,23 +2098,24 @@ function loaded() { elGroup.appendChild(elLegend); } - group.properties.forEach(function(property) { - let propertyType = property.type; - let propertyName = property.propertyName; - let propertyElementID = getPropertyElementID(propertyName); + group.properties.forEach(function(propertyData) { + let propertyType = propertyData.type; + let propertyID = propertyData.propertyID; + let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; + let propertyElementID = "property-" + propertyID; let elProperty; if (propertyType === "sub-header") { elProperty = document.createElement('legend'); - elProperty.innerText = property.label; + elProperty.innerText = propertyData.label; elProperty.setAttribute("class", "sub-section-header"); } else { elProperty = document.createElement('div'); elProperty.setAttribute("id", "div-" + propertyElementID); } - if (group.twoColumn && property.column !== undefined && property.column !== -1) { - let columnName = group.id + "column" + property.column; + if (group.twoColumn && propertyData.column !== undefined && propertyData.column !== -1) { + let columnName = group.id + "column" + propertyData.column; let elColumn = document.getElementById(columnName); if (!elColumn) { let columnDivName = group.id + "columnDiv"; @@ -2152,64 +2137,66 @@ function loaded() { } let elLabel = document.createElement('label'); - elLabel.innerText = property.label; + elLabel.innerText = propertyData.label; elLabel.setAttribute("for", propertyElementID); - properties[propertyName] = { data: property }; + properties[propertyID] = { data: propertyData, elementID: propertyElementID, name: propertyName }; + + let property = properties[propertyID]; switch (propertyType) { case 'string': { - createStringProperty(elProperty, elLabel, property); + properties[propertyID].el = createStringProperty(property, elProperty, elLabel); break; } case 'bool': { - createBoolProperty(elProperty, elLabel, property); + properties[propertyID].el = createBoolProperty(property, elProperty, elLabel); break; } case 'number': { - createNumberProperty(elProperty, elLabel, property); + properties[propertyID].el = createNumberProperty(property, elProperty, elLabel); break; } case 'vec3': { - createVec3Property(elProperty, elLabel, property); + properties[propertyID].el = createVec3Property(property, elProperty, elLabel); break; } case 'vec2': { - createVec2Property(elProperty, elLabel, property); + properties[propertyID].el = createVec2Property(property, elProperty, elLabel); break; } case 'color': { - createColorProperty(elProperty, elLabel, property); + properties[propertyID].el = createColorProperty(property, elProperty, elLabel); break; } case 'dropdown': { - createDropdownProperty(elProperty, elLabel, property); + properties[propertyID].el = createDropdownProperty(property, elProperty, elLabel); break; } case 'textarea': { - createTextareaProperty(elProperty, elLabel, property); + properties[propertyID].el = createTextareaProperty(property, elProperty, elLabel); break; } case 'icon': { - createIconProperty(elProperty, elLabel, property); + properties[propertyID].el = createIconProperty(property, elProperty, elLabel); break; } case 'buttons': { - createButtonsProperty(elProperty, elLabel, property); + properties[propertyID].el = createButtonsProperty(property, elProperty, elLabel); break; } case 'sub-header': { - properties[propertyName].el = elProperty; + properties[propertyID].el = elProperty; break; } default: { console.log("EntityProperties - Unknown property type " + - propertyType + " set to property " + propertyName); + propertyType + " set to property " + propertyID); break; } } - let showPropertyRule = property.showPropertyRule; + let showPropertyRule = propertyData.showPropertyRule; if (showPropertyRule !== undefined) { let dependentProperty = Object.keys(showPropertyRule)[0]; let dependentPropertyValue = showPropertyRule[dependentProperty]; @@ -2219,7 +2206,7 @@ function loaded() { if (properties[dependentProperty].showPropertyRules === undefined) { properties[dependentProperty].showPropertyRules = {}; } - properties[dependentProperty].showPropertyRules[propertyName] = dependentPropertyValue; + properties[dependentProperty].showPropertyRules[propertyID] = dependentPropertyValue; } }); @@ -2351,10 +2338,11 @@ function loaded() { elPropertiesList.className = selectedEntityProperties.type + 'Menu'; showGroupsForType(selectedEntityProperties.type); - for (let propertyName in properties) { - let property = properties[propertyName]; + for (let propertyID in properties) { + let property = properties[propertyID]; let elProperty = property.el; let propertyData = property.data; + let propertyName = property.name; // if this is a compound property name (i.e. animation.running) // then split it by . up to 3 times to find property value @@ -2376,11 +2364,6 @@ function loaded() { } else { propertyValue = selectedEntityProperties[propertyName]; } - - // workaround for shape Color & Light Color property fields sharing same property value "color" - if (propertyValue === undefined && propertyName === "lightColor") { - propertyValue = selectedEntityProperties["color"] - } let isSubProperty = propertyData.subPropertyOf !== undefined; if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { @@ -2397,7 +2380,7 @@ function loaded() { if (isSubProperty) { let propertyValue = selectedEntityProperties[propertyData.subPropertyOf]; let subProperties = propertyValue.split(","); - let subPropertyValue = subProperties.indexOf(propertyName) > -1; + let subPropertyValue = subProperties.indexOf(propertyID) > -1; elProperty.checked = inverse ? !subPropertyValue : subPropertyValue; } else { elProperty.checked = inverse ? !propertyValue : propertyValue; @@ -2583,8 +2566,9 @@ function loaded() { } // Server Script Status - let elServerScript = getPropertyElement("serverScripts"); - let serverScriptElementID = getPropertyElementID("serverScripts"); + let serverScriptProperty = properties["serverScripts"]; + let elServerScript = serverScriptProperty.el; + let serverScriptElementID = serverScriptProperty.elementID; let elDiv = document.createElement('div'); elDiv.setAttribute("class", "property"); let elLabel = document.createElement('label'); @@ -2609,8 +2593,9 @@ function loaded() { elServerScript.parentNode.setAttribute("class", "property url refresh"); // User Data - let elUserData = getPropertyElement("userData"); - let userDataElementID = getPropertyElementID("userData"); + let userDataProperty = properties["userData"]; + let elUserData = userDataProperty.el; + let userDataElementID = userDataProperty.elementID; elDiv = elUserData.parentNode; let elStaticUserData = document.createElement('div'); elStaticUserData.setAttribute("id", userDataElementID + "-static"); @@ -2624,8 +2609,9 @@ function loaded() { elDiv.insertBefore(elUserDataEditor, elUserData); // Material Data - let elMaterialData = getPropertyElement("materialData"); - let materialDataElementID = getPropertyElementID("materialData"); + let materialDataProperty = properties["materialData"]; + let elMaterialData = materialDataProperty.el; + let materialDataElementID = materialDataProperty.elementID; elDiv = elMaterialData.parentNode; let elStaticMaterialData = document.createElement('div'); elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); @@ -2763,9 +2749,10 @@ function loaded() { ul.appendChild(li); } - let propertyName = elDropdowns[dropDownIndex].getAttribute("propertyName"); - properties[propertyName].el = dt; - dt.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + let propertyID = elDropdowns[dropDownIndex].getAttribute("propertyID"); + let property = properties[propertyID]; + property.el = dt; + dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name)); } elDropdowns = document.getElementsByTagName("select"); From 3f08400741d834df27198d79b5cf9f13e2b0d14f Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Oct 2018 12:48:08 -0700 Subject: [PATCH 049/131] comment --- scripts/system/html/js/entityProperties.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index fcd9251485..3ff24d013c 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -474,9 +474,8 @@ const GROUPS = [ { label: "Light Color", type: "color", - propertyID: "lightColor", // this actually shares "color" property with shape Color but - // separating naming here to distinguish property element/data - propertyName: "color", + propertyID: "lightColor", + propertyName: "color", // actual entity property name }, { label: "Intensity", From 62c5fb8e2e34fa53b47814d89360ab5a117ae364 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Oct 2018 14:25:06 -0700 Subject: [PATCH 050/131] tweaks / dropdown fix --- scripts/system/html/js/entityProperties.js | 32 ++++++++++------------ 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 3ff24d013c..1d8a397f07 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1383,7 +1383,7 @@ function createColorProperty(property, elProperty, elLabel) { return [elColorPicker, elInputR, elInputG, elInputB]; } -function createDropdownProperty(property, elProperty, elLabel) { +function createDropdownProperty(property, propertyID, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; @@ -1392,7 +1392,7 @@ function createDropdownProperty(property, elProperty, elLabel) { let elInput = document.createElement('select'); elInput.setAttribute("id", elementID); - elInput.setAttribute("propertyID", propertyData.propertyID); + elInput.setAttribute("propertyID", propertyID); for (let optionKey in propertyData.options) { let option = document.createElement('option'); @@ -2015,7 +2015,7 @@ function bindAllNonJSONEditorElements() { } -// DROPDOWNS / TEXTAREAS / PARENT MATERIAL NAME FUNCTIONS +// DROPDOWN FUNCTIONS function setDropdownText(dropdown) { let lis = dropdown.parentNode.getElementsByTagName("li"); @@ -2050,6 +2050,9 @@ function setDropdownValue(event) { dt.dispatchEvent(evt); } + +// TEXTAREA / PARENT MATERIAL NAME FUNCTIONS + function setTextareaScrolling(element) { var isScrolling = element.scrollHeight > element.offsetHeight; element.setAttribute("scrolling", isScrolling ? "true" : "false"); @@ -2169,7 +2172,7 @@ function loaded() { break; } case 'dropdown': { - properties[propertyID].el = createDropdownProperty(property, elProperty, elLabel); + properties[propertyID].el = createDropdownProperty(property, propertyID, elProperty, elLabel); break; } case 'textarea': { @@ -2252,10 +2255,6 @@ function loaded() { } resetProperties(); - - getPropertyElement("type")[0].style.display = "none"; - getPropertyElement("type")[1].innerHTML = NO_SELECTION; - elPropertiesList.className = ''; showGroupsForType("None"); deleteJSONEditor(); @@ -2297,17 +2296,20 @@ function loaded() { } resetProperties(); - - getPropertyElement("type")[0].innerHTML = ICON_FOR_TYPE[type]; - getPropertyElement("type")[0].style.display = "inline-block"; - getPropertyElement("type")[1].innerHTML = type + " (" + data.selections.length + ")"; - elPropertiesList.className = ''; showGroupsForType(type); + let typeProperty = properties["type"]; + let elTypeProperty = typeProperty.el; + elTypeProperty[0].innerHTML = typeProperty.data.icons[type]; + elTypeProperty[0].style.display = "inline-block"; + elTypeProperty[1].innerHTML = type + " (" + data.selections.length + ")"; + disableProperties(); } else { selectedEntityProperties = data.selections[0].properties; + showGroupsForType(selectedEntityProperties.type); + if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { saveUserData(); @@ -2333,10 +2335,6 @@ function loaded() { } } - // Create class name for css ruleset filtering - elPropertiesList.className = selectedEntityProperties.type + 'Menu'; - showGroupsForType(selectedEntityProperties.type); - for (let propertyID in properties) { let property = properties[propertyID]; let elProperty = property.el; From 4883a60afc9326c016ef37f617d70c2bca27a13a Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Oct 2018 18:55:14 -0700 Subject: [PATCH 051/131] entity list type filter WIP - still some style issues --- scripts/system/html/css/edit-style.css | 46 +++++++++-- scripts/system/html/entityList.html | 20 +++-- scripts/system/html/js/entityList.js | 109 +++++++++++++++++++------ 3 files changed, 136 insertions(+), 39 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 6c1931932a..fc055174fb 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1031,30 +1031,60 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; /* New positioning context. */ } -#search-area { +#filter-area { padding-right: 168px; padding-bottom: 24px; } -#filter { - width: 98%; +#filter-type-multiselect { + position: relative; +} +#filter-type-selectBox { + position: absolute; +} +#filter-type-selectBox select { + font-weight: bold; + font-family: FiraSans-SemiBold; + color: #404040; + background-color: #afafaf; +} +#filter-type-checkboxes { + position: absolute; + top: 20px; + display: none; + border: 1px #dadada solid; +} +#filter-type-checkboxes label { + display: block; + font-family: FiraSans-SemiBold; + color: #404040; + background-color: #afafaf; +} +#filter-type-checkboxes label:hover { + background-color: #1e90ff; } -#in-view { +#filter-search-and-icon { + position: absolute; + left: 100px; + width: 60%; +} + +#filter-in-view { position: absolute; right: 126px; } -#radius-and-unit { +#filter-radius-and-unit { + position: relative; float: right; margin-right: -168px; - position: relative; top: -17px; } -#radius-and-unit label { +#filter-radius-and-unit label { margin-left: 2px; } -#radius-and-unit input { +#filter-radius-and-unit input { width: 120px; } diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 7eed78ecf3..8bcdc37d64 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -30,12 +30,22 @@
-
- Y - -
+
+
+
+ +
+
+ +
+
+
+ Y +
+ +
- +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index fed4dfb632..6b23d703ee 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -59,6 +59,18 @@ const COMPARE_DESCENDING = function(a, b) { return COMPARE_ASCENDING(b, a); } +const FILTER_TYPES = [ + "Shape", + "Model", + "Image", + "Light", + "Zone", + "Web", + "Material", + "ParticleEffect", + "Text", +]; + // List of all entities var entities = [] // List of all entities, indexed by Entity ID @@ -72,6 +84,7 @@ var entityList = null; // The ListView var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; +var typeFilters = []; var isFilterInView = false; var showExtraInfo = false; @@ -105,9 +118,11 @@ function loaded() { elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); elDelete = document.getElementById("delete"); - elFilter = document.getElementById("filter"); - elInView = document.getElementById("in-view") - elRadius = document.getElementById("radius"); + elFilterTypeSelectBox = document.getElementById("filter-type-selectBox"); + elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); + elFilterSearch = document.getElementById("filter-search"); + elFilterInView = document.getElementById("filter-in-view") + elFilterRadius = document.getElementById("filter-radius"); elExport = document.getElementById("export"); elPal = document.getElementById("pal"); elInfoToggle = document.getElementById("info-toggle"); @@ -171,13 +186,33 @@ function loaded() { elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); } - elFilter.onkeyup = refreshEntityList; - elFilter.onpaste = refreshEntityList; - elFilter.onchange = onFilterChange; - elFilter.onblur = refreshFooter; - elInView.onclick = toggleFilterInView; - elRadius.onchange = onRadiusChange; + elFilterSearch.onkeyup = refreshEntityList; + elFilterSearch.onpaste = refreshEntityList; + elFilterSearch.onchange = onFilterChange; + elFilterSearch.onblur = refreshFooter; + elFilterInView.onclick = toggleFilterInView; + elFilterRadius.onchange = onRadiusChange; elInfoToggle.onclick = toggleInfo; + + // create filter type dropdown checkboxes w/ label for each type + elFilterTypeSelectBox.onclick = toggleTypeDropdown; + for (let i = 0; i < FILTER_TYPES.length; ++i) { + let type = FILTER_TYPES[i]; + let typeFilterID = "filter-type-" + type; + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", typeFilterID); + elLabel.innerText = type; + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", typeFilterID); + elInput.checked = true; // all types are checked initially + toggleTypeFilter(type, false); // add all types to the initial type filter + elInput.onclick = onToggleTypeFilter(type); + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elFilterTypeCheckboxes.appendChild(elDiv); + } elNoEntitiesInView.style.display = "none"; @@ -301,17 +336,16 @@ function loaded() { function refreshEntityList() { PROFILE("refresh-entity-list", function() { PROFILE("filter", function() { - let searchTerm = elFilter.value.toLowerCase(); - if (searchTerm === '') { - visibleEntities = entities.slice(0); - } else { - visibleEntities = entities.filter(function(e) { - return e.name.toLowerCase().indexOf(searchTerm) > -1 - || e.type.toLowerCase().indexOf(searchTerm) > -1 - || e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 - || e.id.toLowerCase().indexOf(searchTerm) > -1; - }); - } + let searchTerm = elFilterSearch.value.toLowerCase(); + visibleEntities = entities.filter(function(e) { + let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; + let typeFilter = typeFilters.indexOf(type) > -1; + let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || + e.type.toLowerCase().indexOf(searchTerm) > -1 || + e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || + e.id.toLowerCase().indexOf(searchTerm) > -1); + return typeFilter && searchFilter; + }); }); PROFILE("sort", function() { @@ -588,10 +622,10 @@ function loaded() { function toggleFilterInView() { isFilterInView = !isFilterInView; if (isFilterInView) { - elInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); + elFilterInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); elNoEntitiesInView.style.display = "inline"; } else { - elInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); + elFilterInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); elNoEntitiesInView.style.display = "none"; } EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); @@ -604,12 +638,34 @@ function loaded() { } function onRadiusChange() { - elRadius.value = Math.max(elRadius.value, 0); - elNoEntitiesRadius.firstChild.nodeValue = elRadius.value; + elFilterRadius.value = Math.max(elFilterRadius.value, 0); + elNoEntitiesRadius.firstChild.nodeValue = elFilterRadius.value; elNoEntitiesMessage.style.display = "none"; - EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value })); + EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); } + + function toggleTypeDropdown() { + elFilterTypeCheckboxes.style.display = elFilterTypeCheckboxes.style.display === "block" ? "none" : "block"; + } + + function toggleTypeFilter(type, refresh) { + let typeFilterIndex = typeFilters.indexOf(type); + if (typeFilterIndex > -1) { + typeFilters.splice(typeFilterIndex, 1); + } else { + typeFilters.push(type); + } + if (refresh) { + refreshEntityList(); + } + } + + function onToggleTypeFilter(type) { + return function() { + toggleTypeFilter(type, true); + }; + } function toggleInfo(event) { showExtraInfo = !showExtraInfo; @@ -623,7 +679,7 @@ function loaded() { entityList.resize(); event.stopPropagation(); } - + document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { return; @@ -675,6 +731,7 @@ function loaded() { refreshSortOrder(); refreshEntities(); }); + augmentSpinButtons(); From 97438a08d931da46505830deaf6529943983ac9d Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Oct 2018 23:04:33 -0700 Subject: [PATCH 052/131] styling fixes, add icon, adjust Types text, click outside to close --- scripts/system/html/css/edit-style.css | 80 ++++++++---- scripts/system/html/entityList.html | 7 +- scripts/system/html/js/entityList.js | 168 +++++++++++++++---------- 3 files changed, 165 insertions(+), 90 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index fc055174fb..da0d806a41 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1036,38 +1036,74 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { padding-bottom: 24px; } -#filter-type-multiselect { - position: relative; +#filter-area .multiselect { + position: relative; } -#filter-type-selectBox { - position: absolute; +#filter-area .selectBox { + position: absolute; } -#filter-type-selectBox select { - font-weight: bold; - font-family: FiraSans-SemiBold; - color: #404040; - background-color: #afafaf; +#filter-area .selectBox select { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + background-color: #252525; + border: none; + height: 28px; + width: 107px; + text-align-last: center; +} +#filter-area .overSelect { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; } #filter-type-checkboxes { - position: absolute; - top: 20px; - display: none; - border: 1px #dadada solid; + position: absolute; + z-index: 2; + top: 15px; + display: none; + border: none; +} +#filter-type-checkboxes div { + height: 19px; +} +#filter-type-checkboxes span { + font-family: hifi-glyphs; + font-size: 16px; + color: #404040; + padding: 12px 12px; + vertical-align: middle; } #filter-type-checkboxes label { - display: block; - font-family: FiraSans-SemiBold; - color: #404040; - background-color: #afafaf; + display: block; + font-family: FiraSans-SemiBold; + color: #404040; + background-color: #afafaf; + padding: 0 20px; + vertical-align: middle; } #filter-type-checkboxes label:hover { - background-color: #1e90ff; + background-color: #1e90ff; +} +#filter-type-checkboxes input[type=checkbox] { + display: block; + position: relative; + top: 17px; + right: -10px; +} +#filter-type-checkboxes input[type=checkbox] + label { + background-image: none; +} +#filter-type-checkboxes input[type=checkbox]:checked + label { + background-image: none; } #filter-search-and-icon { - position: absolute; - left: 100px; - width: 60%; + position: absolute; + left: 120px; + width: calc(100% - 300px); } #filter-in-view { @@ -1076,7 +1112,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { } #filter-radius-and-unit { - position: relative; + position: relative; float: right; margin-right: -168px; top: -17px; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 8bcdc37d64..e301f36945 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -31,9 +31,12 @@
-
+
- + +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 6b23d703ee..8812594408 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -60,17 +60,29 @@ const COMPARE_DESCENDING = function(a, b) { } const FILTER_TYPES = [ - "Shape", - "Model", - "Image", - "Light", - "Zone", - "Web", - "Material", - "ParticleEffect", - "Text", + "Shape", + "Model", + "Image", + "Light", + "Zone", + "Web", + "Material", + "ParticleEffect", + "Text", ]; +const ICON_FOR_TYPE = { + Shape: "n", + Model: "", + Image: "", + Light: "p", + Zone: "o", + Web: "q", + Material: "", + ParticleEffect: "", + Text: "l", +}; + // List of all entities var entities = [] // List of all entities, indexed by Entity ID @@ -118,8 +130,9 @@ function loaded() { elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); elDelete = document.getElementById("delete"); - elFilterTypeSelectBox = document.getElementById("filter-type-selectBox"); - elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); + elFilterTypeSelectBox = document.getElementById("filter-type-selectBox"); + elFilterTypeText = document.getElementById("filter-type-text"); + elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); elFilterSearch = document.getElementById("filter-search"); elFilterInView = document.getElementById("filter-in-view") elFilterRadius = document.getElementById("filter-radius"); @@ -132,6 +145,8 @@ function loaded() { elNoEntitiesInView = document.getElementById("no-entities-in-view"); elNoEntitiesRadius = document.getElementById("no-entities-radius"); + document.body.onclick = onBodyClick; + document.getElementById("entity-name").onclick = function() { setSortColumn('name'); }; @@ -193,26 +208,30 @@ function loaded() { elFilterInView.onclick = toggleFilterInView; elFilterRadius.onchange = onRadiusChange; elInfoToggle.onclick = toggleInfo; - - // create filter type dropdown checkboxes w/ label for each type - elFilterTypeSelectBox.onclick = toggleTypeDropdown; - for (let i = 0; i < FILTER_TYPES.length; ++i) { - let type = FILTER_TYPES[i]; - let typeFilterID = "filter-type-" + type; - let elDiv = document.createElement('div'); - let elLabel = document.createElement('label'); - elLabel.setAttribute("for", typeFilterID); - elLabel.innerText = type; - let elInput = document.createElement('input'); - elInput.setAttribute("type", "checkbox"); - elInput.setAttribute("id", typeFilterID); - elInput.checked = true; // all types are checked initially - toggleTypeFilter(type, false); // add all types to the initial type filter - elInput.onclick = onToggleTypeFilter(type); - elDiv.appendChild(elInput); - elDiv.appendChild(elLabel); - elFilterTypeCheckboxes.appendChild(elDiv); - } + + // create filter type dropdown checkboxes w/ label for each type + elFilterTypeSelectBox.onclick = toggleTypeDropdown; + for (let i = 0; i < FILTER_TYPES.length; ++i) { + let type = FILTER_TYPES[i]; + let typeFilterID = "filter-type-" + type; + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", typeFilterID); + elLabel.innerText = type; + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "typeIcon"); + elSpan.innerHTML = ICON_FOR_TYPE[type]; + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", typeFilterID); + elInput.checked = true; // all types are checked initially + toggleTypeFilter(type, false); // add all types to the initial type filter + elInput.onclick = onToggleTypeFilter(type); + elDiv.appendChild(elInput); + elLabel.insertBefore(elSpan, elLabel.childNodes[0]); + elDiv.appendChild(elLabel); + elFilterTypeCheckboxes.appendChild(elDiv); + } elNoEntitiesInView.style.display = "none"; @@ -336,16 +355,16 @@ function loaded() { function refreshEntityList() { PROFILE("refresh-entity-list", function() { PROFILE("filter", function() { - let searchTerm = elFilterSearch.value.toLowerCase(); - visibleEntities = entities.filter(function(e) { - let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; - let typeFilter = typeFilters.indexOf(type) > -1; - let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || - e.type.toLowerCase().indexOf(searchTerm) > -1 || - e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || - e.id.toLowerCase().indexOf(searchTerm) > -1); - return typeFilter && searchFilter; - }); + let searchTerm = elFilterSearch.value.toLowerCase(); + visibleEntities = entities.filter(function(e) { + let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; + let typeFilter = typeFilters.indexOf(type) > -1; + let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || + e.type.toLowerCase().indexOf(searchTerm) > -1 || + e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || + e.id.toLowerCase().indexOf(searchTerm) > -1); + return typeFilter && searchFilter; + }); }); PROFILE("sort", function() { @@ -644,29 +663,38 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); } - - function toggleTypeDropdown() { - elFilterTypeCheckboxes.style.display = elFilterTypeCheckboxes.style.display === "block" ? "none" : "block"; - } - - function toggleTypeFilter(type, refresh) { - let typeFilterIndex = typeFilters.indexOf(type); - if (typeFilterIndex > -1) { - typeFilters.splice(typeFilterIndex, 1); - } else { - typeFilters.push(type); - } - if (refresh) { - refreshEntityList(); - } - } - - function onToggleTypeFilter(type) { - return function() { - toggleTypeFilter(type, true); - }; - } - + + function toggleTypeDropdown() { + elFilterTypeCheckboxes.style.display = elFilterTypeCheckboxes.style.display === "block" ? "none" : "block"; + } + + function toggleTypeFilter(type, refresh) { + let typeFilterIndex = typeFilters.indexOf(type); + if (typeFilterIndex > -1) { + typeFilters.splice(typeFilterIndex, 1); + } else { + typeFilters.push(type); + } + + if (typeFilters.length === 0) { + elFilterTypeText.innerText = "No Types"; + } else if (typeFilters.length === FILTER_TYPES.length) { + elFilterTypeText.innerText = "All Types"; + } else { + elFilterTypeText.innerText = "Types..."; + } + + if (refresh) { + refreshEntityList(); + } + } + + function onToggleTypeFilter(type) { + return function() { + toggleTypeFilter(type, true); + }; + } + function toggleInfo(event) { showExtraInfo = !showExtraInfo; if (showExtraInfo) { @@ -679,7 +707,15 @@ function loaded() { entityList.resize(); event.stopPropagation(); } - + + function onBodyClick(event) { + let targetNode = event.target; + if (!elFilterTypeSelectBox.contains(targetNode) && !elFilterTypeCheckboxes.contains(targetNode) && + elFilterTypeCheckboxes.style.display === "block") { + toggleTypeDropdown(); + } + } + document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { return; @@ -731,7 +767,7 @@ function loaded() { refreshSortOrder(); refreshEntities(); }); - + augmentSpinButtons(); From 52ed6cb6e95299c578b2c3803ed42959c192d040 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 12 Oct 2018 11:35:51 -0700 Subject: [PATCH 053/131] Abort settle animation if already moving --- interface/src/avatar/AvatarManager.cpp | 6 +++++- .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 8 +++++++- libraries/avatars-renderer/src/avatars-renderer/Avatar.h | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 80e4e4ba66..5fb9e0e0af 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -168,6 +168,10 @@ void AvatarManager::playTransitAnimations(AvatarTransit::Status status) { qDebug() << "END_FRAME"; _myAvatar->restoreAnimation(); break; + case AvatarTransit::Status::PRE_TRANSIT_IDLE: + break; + case AvatarTransit::Status::POST_TRANSIT_IDLE: + break; case AvatarTransit::Status::IDLE: break; case AvatarTransit::Status::TRANSITING: @@ -180,7 +184,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); - if (status != AvatarTransit::Status::IDLE) { + if (status != AvatarTransit::Status::IDLE && status != AvatarTransit::Status::PRE_TRANSIT_IDLE && status != AvatarTransit::Status::POST_TRANSIT_IDLE) { playTransitAnimations(status); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 53c2019138..d8587b6086 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -127,6 +127,10 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av } _lastPosition = avatarPosition; _status = updatePosition(deltaTime); + if (_isTransiting && oneFrameDistance > 0.1f && _status == Status::POST_TRANSIT_IDLE) { + reset(); + _status = Status::END_FRAME; + } return _status; } @@ -177,18 +181,20 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { float nextTime = _currentTime + deltaTime; if (nextTime < _preTime) { _currentPosition = _startPosition; + status = Status::PRE_TRANSIT_IDLE; if (_currentTime == 0) { status = Status::START_FRAME; } } else if (nextTime < _totalTime - _postTime){ + status = Status::TRANSITING; if (_currentTime <= _preTime) { status = Status::START_TRANSIT; } else { float percentageIntoTransit = (nextTime - _preTime) / _transitTime; _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; - status = Status::TRANSITING; } } else { + status = Status::POST_TRANSIT_IDLE; _currentPosition = _endPosition; if (nextTime >= _totalTime) { _isTransiting = false; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index ffe90ecaa2..fe163b9dae 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -55,9 +55,11 @@ public: enum Status { IDLE = 0, START_FRAME, + PRE_TRANSIT_IDLE, START_TRANSIT, TRANSITING, END_TRANSIT, + POST_TRANSIT_IDLE, END_FRAME, ABORT_TRANSIT }; From 6320f9bab7f6a34fde523e9544c75dbd83b0919e Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Oct 2018 11:39:54 -0700 Subject: [PATCH 054/131] tweaks --- scripts/system/html/css/edit-style.css | 26 ++++++++++++++++-------- scripts/system/html/js/entityList.js | 28 +++++++++++++++----------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index da0d806a41..8179b95e35 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1036,13 +1036,13 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { padding-bottom: 24px; } -#filter-area .multiselect { +.multiselect { position: relative; } -#filter-area .selectBox { +.selectBox { position: absolute; } -#filter-area .selectBox select { +.selectBox select { font-family: FiraSans-SemiBold; font-size: 15px; color: #afafaf; @@ -1052,22 +1052,24 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 107px; text-align-last: center; } -#filter-area .overSelect { +.overSelect { position: absolute; left: 0; right: 0; top: 0; bottom: 0; } + #filter-type-checkboxes { position: absolute; z-index: 2; - top: 15px; + top: 28px; display: none; border: none; } #filter-type-checkboxes div { - height: 19px; + position: relative; + height: 25px; } #filter-type-checkboxes span { font-family: hifi-glyphs; @@ -1077,21 +1079,26 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { vertical-align: middle; } #filter-type-checkboxes label { + position: relative; + top: -13px; + z-index: 3; display: block; font-family: FiraSans-SemiBold; color: #404040; background-color: #afafaf; padding: 0 20px; + height: 25px; vertical-align: middle; } #filter-type-checkboxes label:hover { background-color: #1e90ff; } #filter-type-checkboxes input[type=checkbox] { - display: block; position: relative; - top: 17px; + top: 4px; right: -10px; + z-index: 4; + display: block; } #filter-type-checkboxes input[type=checkbox] + label { background-image: none; @@ -1099,6 +1106,9 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { #filter-type-checkboxes input[type=checkbox]:checked + label { background-image: none; } +#filter-type-checkboxes input[type=checkbox]:hover + label { + background-color: #1e90ff; +} #filter-search-and-icon { position: absolute; diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 8812594408..f780fd0e2e 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -146,7 +146,6 @@ function loaded() { elNoEntitiesRadius = document.getElementById("no-entities-radius"); document.body.onclick = onBodyClick; - document.getElementById("entity-name").onclick = function() { setSortColumn('name'); }; @@ -209,7 +208,7 @@ function loaded() { elFilterRadius.onchange = onRadiusChange; elInfoToggle.onclick = toggleInfo; - // create filter type dropdown checkboxes w/ label for each type + // create filter type dropdown checkboxes with label and icon for each type elFilterTypeSelectBox.onclick = toggleTypeDropdown; for (let i = 0; i < FILTER_TYPES.length; ++i) { let type = FILTER_TYPES[i]; @@ -225,7 +224,7 @@ function loaded() { elInput.setAttribute("type", "checkbox"); elInput.setAttribute("id", typeFilterID); elInput.checked = true; // all types are checked initially - toggleTypeFilter(type, false); // add all types to the initial type filter + toggleTypeFilter(type, false); // add all types to the initial types filter elInput.onclick = onToggleTypeFilter(type); elDiv.appendChild(elInput); elLabel.insertBefore(elSpan, elLabel.childNodes[0]); @@ -664,8 +663,12 @@ function loaded() { refreshEntities(); } + function isTypeDropdownVisible() { + return elFilterTypeCheckboxes.style.display === "block"; + } + function toggleTypeDropdown() { - elFilterTypeCheckboxes.style.display = elFilterTypeCheckboxes.style.display === "block" ? "none" : "block"; + elFilterTypeCheckboxes.style.display = isTypeDropdownVisible() ? "none" : "block"; } function toggleTypeFilter(type, refresh) { @@ -695,6 +698,15 @@ function loaded() { }; } + function onBodyClick(event) { + // if clicking anywhere outside of the type filter dropdown and it's open then close it + let elTarget = event.target; + if (isTypeDropdownVisible() && !elFilterTypeSelectBox.contains(elTarget) && + !elFilterTypeCheckboxes.contains(elTarget)) { + toggleTypeDropdown(); + } + } + function toggleInfo(event) { showExtraInfo = !showExtraInfo; if (showExtraInfo) { @@ -707,14 +719,6 @@ function loaded() { entityList.resize(); event.stopPropagation(); } - - function onBodyClick(event) { - let targetNode = event.target; - if (!elFilterTypeSelectBox.contains(targetNode) && !elFilterTypeCheckboxes.contains(targetNode) && - elFilterTypeCheckboxes.style.display === "block") { - toggleTypeDropdown(); - } - } document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { From a85336044f0201afa66fd287e5d9cdca13a99b1c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Oct 2018 18:03:47 -0700 Subject: [PATCH 055/131] add X to clear button to filter search --- scripts/system/html/css/edit-style.css | 14 +++++++++++++- scripts/system/html/entityList.html | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8179b95e35..69fed02099 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -198,7 +198,7 @@ td.url { } -input[type="text"], input[type="number"], textarea { +input[type="text"], input[type="search"], input[type="number"], textarea { margin: 0; padding: 0 12px; color: #afafaf; @@ -257,6 +257,18 @@ input[type="text"] { width: 100%; } +input[type="search"] { + height: 28px; + width: 100%; +} +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 20px; + width: 20px; + border-radius:10px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goNAQIFbBwsbwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAZfSURBVDgRAVQGq/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9PT0YAwMDBgAAAAD8/Pz5+vr67MrKyv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAA+Pj4KAgICQgAAAE3///9RAQEBFQAAAAD////pAQEBu/39/ab+/v7BxcXF9gAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAADs7OzMEBASIAQEBRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAACm+/v7cMXFxewAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAPT09OwEBAagBAQEcAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAAA2f///2XCwsLDAAAAAAAAAAABAAAAAAAAAAA9PT0KAwMDt////z4AAAAAAAAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAcIBAQFJvr6+9gAAAAACAAAAAAAAAAAAAABg////PgEBAQAAAAAAS0tLADg4OAAAAAAAAAAAAP///wADAwMAQEBAACEhIQD///8A////AP7+/j76+vpWAAAAAAAAAAACAAAAAD09PQ8CAgJkAQEBAP///wD///8ACgoKAFhYWAAyMjIAAAAAAAICAgBGRkYAT09PABEREQAAAAAAAAAAAAAAAAACAgJwOjo6EAAAAAAEAAAAAAICAg8BAQExAAAAAAEBAQABAQEAsrKyAAoKCgBaWloA9/f3ABsbGwBISEgAtra2AM7OzgACAgIA////AP///wABAQEuBQUFDgAAAPAEAAAAAPz8/BkEBAQAAQEBAAAAAAAAAAAA+vr6AKioqAALCwsAZWVlAAcHBwC/v78Au7u7AAEBAQD///8AAAAAAAAAAAAAAAABAAAAAAAAAAACAAAAAAQEBOgBAQEAAQEBAAEBAQABAQEAAQEBAPz8/ADT09MADg4OAP39/QDQ0NAA/v7+AP///wAAAAAAAAAAAAEBAQABAQEAAQEBAAAAAAACAAAAAAAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAACkpKQBQUFAAx8fHAObm5gBfX18AFxcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAP39/fz+/v7z////AP///wD///8AJycnAGFhYQDc3NwApaWlAJaWlgD29vYAZmZmABQUFAACAgIAAQEBAAEBAQABAQH1AAAA/AAAAAACAAAAAPr6+ukBAQGkAAAAAAAAAAABAQEAQEBAAObm5gCmpqYA+fn5APPz8wCdnZ0A////ACwsLAD///8AAAAAAAAAAAD///+k9vb26QAAAAABAAAAAAAAAAA+Pj4uAgICxgAAAAsAAAAAEBAQAPr6+gD29vYAAAAAAAAAAAABAQEAAgICAP///wD+/v4AAAAAAAAAAPL8/Pw/xMTE0AAAAAACAAAAAAAAAAD5+fnV////nQICAgABAQEA8fHxAPX19QABAQEAAAAAAAAAAAD///8A/v7+AP7+/gAAAAAAAAAAAP7+/p36+vrSAAAAAAAAAAADAAAAAAAAAADl5eX/ICAgwQAAAA////8q////BgEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1/f39mAEBAXrGxsb7AAAAAAAAAAADAAAAAAAAAAAAAAAA4eHh/BgYGLsBAQHDBAQEHAAAACP///8AAQEBAAAAAAAAAAAAAAAA+////7QBAQFu+fn5m8bGxvoAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPz8/Cv7+/iUBAQFMAgICEQICAgD8/PzdAwMDs/j4+OvHx8f5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8TUnpZ7EwQgAAAABJRU5ErkJggg==') +} + input[type="number"] { position: relative; height: 28px; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index e301f36945..626402362d 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -43,7 +43,7 @@
- Y + Y
From 978cd4bb7764cb4bbc017da1164043d0f509d75c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Oct 2018 18:57:26 -0700 Subject: [PATCH 056/131] styling tweaks, fix X clear search not refreshing --- scripts/system/html/css/edit-style.css | 15 ++++++++------- scripts/system/html/entityList.html | 2 +- scripts/system/html/js/entityList.js | 9 +-------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 69fed02099..699c27448a 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -265,7 +265,6 @@ input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; height: 20px; width: 20px; - border-radius:10px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goNAQIFbBwsbwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAZfSURBVDgRAVQGq/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9PT0YAwMDBgAAAAD8/Pz5+vr67MrKyv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAA+Pj4KAgICQgAAAE3///9RAQEBFQAAAAD////pAQEBu/39/ab+/v7BxcXF9gAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAADs7OzMEBASIAQEBRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAACm+/v7cMXFxewAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAPT09OwEBAagBAQEcAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAAA2f///2XCwsLDAAAAAAAAAAABAAAAAAAAAAA9PT0KAwMDt////z4AAAAAAAAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAcIBAQFJvr6+9gAAAAACAAAAAAAAAAAAAABg////PgEBAQAAAAAAS0tLADg4OAAAAAAAAAAAAP///wADAwMAQEBAACEhIQD///8A////AP7+/j76+vpWAAAAAAAAAAACAAAAAD09PQ8CAgJkAQEBAP///wD///8ACgoKAFhYWAAyMjIAAAAAAAICAgBGRkYAT09PABEREQAAAAAAAAAAAAAAAAACAgJwOjo6EAAAAAAEAAAAAAICAg8BAQExAAAAAAEBAQABAQEAsrKyAAoKCgBaWloA9/f3ABsbGwBISEgAtra2AM7OzgACAgIA////AP///wABAQEuBQUFDgAAAPAEAAAAAPz8/BkEBAQAAQEBAAAAAAAAAAAA+vr6AKioqAALCwsAZWVlAAcHBwC/v78Au7u7AAEBAQD///8AAAAAAAAAAAAAAAABAAAAAAAAAAACAAAAAAQEBOgBAQEAAQEBAAEBAQABAQEAAQEBAPz8/ADT09MADg4OAP39/QDQ0NAA/v7+AP///wAAAAAAAAAAAAEBAQABAQEAAQEBAAAAAAACAAAAAAAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAACkpKQBQUFAAx8fHAObm5gBfX18AFxcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAP39/fz+/v7z////AP///wD///8AJycnAGFhYQDc3NwApaWlAJaWlgD29vYAZmZmABQUFAACAgIAAQEBAAEBAQABAQH1AAAA/AAAAAACAAAAAPr6+ukBAQGkAAAAAAAAAAABAQEAQEBAAObm5gCmpqYA+fn5APPz8wCdnZ0A////ACwsLAD///8AAAAAAAAAAAD///+k9vb26QAAAAABAAAAAAAAAAA+Pj4uAgICxgAAAAsAAAAAEBAQAPr6+gD29vYAAAAAAAAAAAABAQEAAgICAP///wD+/v4AAAAAAAAAAPL8/Pw/xMTE0AAAAAACAAAAAAAAAAD5+fnV////nQICAgABAQEA8fHxAPX19QABAQEAAAAAAAAAAAD///8A/v7+AP7+/gAAAAAAAAAAAP7+/p36+vrSAAAAAAAAAAADAAAAAAAAAADl5eX/ICAgwQAAAA////8q////BgEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1/f39mAEBAXrGxsb7AAAAAAAAAAADAAAAAAAAAAAAAAAA4eHh/BgYGLsBAQHDBAQEHAAAACP///8AAQEBAAAAAAAAAAAAAAAA+////7QBAQFu+fn5m8bGxvoAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPz8/Cv7+/iUBAQFMAgICEQICAgD8/PzdAwMDs/j4+OvHx8f5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8TUnpZ7EwQgAAAABJRU5ErkJggg==') } @@ -1084,11 +1083,13 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { height: 25px; } #filter-type-checkboxes span { + position: relative; + top: 3px; font-family: hifi-glyphs; font-size: 16px; color: #404040; - padding: 12px 12px; - vertical-align: middle; + padding-left: 12px; + padding-right: 12px; } #filter-type-checkboxes label { position: relative; @@ -1098,16 +1099,16 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { font-family: FiraSans-SemiBold; color: #404040; background-color: #afafaf; - padding: 0 20px; - height: 25px; - vertical-align: middle; + padding-top: 1px; + padding-right: 12px; + height: 24px; } #filter-type-checkboxes label:hover { background-color: #1e90ff; } #filter-type-checkboxes input[type=checkbox] { position: relative; - top: 4px; + top: 6px; right: -10px; z-index: 4; display: block; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 626402362d..06c2be8e73 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -39,7 +39,7 @@
- +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index f780fd0e2e..0f3f27a547 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -201,9 +201,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); } elFilterSearch.onkeyup = refreshEntityList; - elFilterSearch.onpaste = refreshEntityList; - elFilterSearch.onchange = onFilterChange; - elFilterSearch.onblur = refreshFooter; + elFilterSearch.onsearch = refreshEntityList; elFilterInView.onclick = toggleFilterInView; elFilterRadius.onchange = onRadiusChange; elInfoToggle.onclick = toggleInfo; @@ -650,11 +648,6 @@ function loaded() { refreshEntities(); } - function onFilterChange() { - refreshEntityList(); - entityList.resize(); - } - function onRadiusChange() { elFilterRadius.value = Math.max(elFilterRadius.value, 0); elNoEntitiesRadius.firstChild.nodeValue = elFilterRadius.value; From ad09007bd8f20f62618871b105bd288551534d26 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sat, 13 Oct 2018 16:32:51 -0700 Subject: [PATCH 057/131] Fix flickering holded entity, renames and refactors --- interface/src/avatar/AvatarActionHold.cpp | 4 +- interface/src/avatar/AvatarManager.cpp | 26 ++++--- interface/src/avatar/AvatarManager.h | 2 +- .../src/avatars-renderer/Avatar.cpp | 73 +++++++++---------- .../src/avatars-renderer/Avatar.h | 20 +++-- 5 files changed, 64 insertions(+), 61 deletions(-) diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 230f8aa64b..53074ac4ba 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -135,7 +135,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: glm::vec3 palmPosition; glm::quat palmRotation; - bool isTransitingWithAvatar = holdingAvatar->getTransit()->isTransiting(); + bool isTransitingWithAvatar = holdingAvatar->getTransit()->isActive(); if (isTransitingWithAvatar != _isTransitingWithAvatar) { _isTransitingWithAvatar = isTransitingWithAvatar; auto ownerEntity = _ownerEntity.lock(); @@ -424,7 +424,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { if (ownerEntity) { ownerEntity->setDynamicDataDirty(true); ownerEntity->setDynamicDataNeedsTransmit(true); - ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isTransiting()); + ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isActive()); } }); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 5fb9e0e0af..c1fddaa680 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -142,14 +142,14 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { _space = space; } -void AvatarManager::playTransitAnimations(AvatarTransit::Status status) { +void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { auto startAnimation = _transitConfig._startTransitAnimation; auto middleAnimation = _transitConfig._middleTransitAnimation; auto endAnimation = _transitConfig._endTransitAnimation; const float REFERENCE_FPS = 30.0f; switch (status) { - case AvatarTransit::Status::START_FRAME: + case AvatarTransit::Status::STARTED: qDebug() << "START_FRAME"; _myAvatar->overrideAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, startAnimation._firstFrame + startAnimation._frameCount); @@ -164,18 +164,20 @@ void AvatarManager::playTransitAnimations(AvatarTransit::Status status) { _myAvatar->overrideAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, endAnimation._firstFrame + endAnimation._frameCount); break; - case AvatarTransit::Status::END_FRAME: + case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; _myAvatar->restoreAnimation(); break; - case AvatarTransit::Status::PRE_TRANSIT_IDLE: + case AvatarTransit::Status::PRE_TRANSIT: break; - case AvatarTransit::Status::POST_TRANSIT_IDLE: + case AvatarTransit::Status::POST_TRANSIT: break; case AvatarTransit::Status::IDLE: break; case AvatarTransit::Status::TRANSITING: break; + case AvatarTransit::Status::ABORT_TRANSIT: + break; } } @@ -184,9 +186,10 @@ void AvatarManager::updateMyAvatar(float deltaTime) { PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); - if (status != AvatarTransit::Status::IDLE && status != AvatarTransit::Status::PRE_TRANSIT_IDLE && status != AvatarTransit::Status::POST_TRANSIT_IDLE) { - playTransitAnimations(status); - } + handleTransitAnimations(status); + + bool sendFirstTransitPacket = (status == AvatarTransit::Status::STARTED); + bool sendCurrentTransitPacket = (status != AvatarTransit::Status::IDLE && (status != AvatarTransit::Status::POST_TRANSIT || status != AvatarTransit::Status::ENDED)); _myAvatar->update(deltaTime); render::Transaction transaction; @@ -196,9 +199,14 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { + if (sendFirstTransitPacket || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused)) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); + if (sendFirstTransitPacket) { + _myAvatar->overrideNextPacketPositionData(_myAvatar->getTransit()->getEndPosition()); + } else if (sendCurrentTransitPacket) { + _myAvatar->overrideNextPacketPositionData(_myAvatar->getTransit()->getCurrentPosition()); + } _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 0dc39e991b..0f82aa7329 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -213,7 +213,7 @@ private: // frequently grabs a read lock on the hash to get a given avatar by ID void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; - void playTransitAnimations(AvatarTransit::Status status); + void handleTransitAnimations(AvatarTransit::Status status); QVector _avatarsToFade; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d8587b6086..b2747277c9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -114,10 +114,12 @@ void Avatar::setShowNamesAboveHeads(bool show) { } AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { - float oneFrameDistance = _isTransiting ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); - const float MAX_TRANSIT_DISTANCE = 30.0f; - float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance) { + bool checkDistance = (!_isActive || (_status == Status::POST_TRANSIT || _status == Status::ENDED)); + float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); + + const float TRANSIT_TRIGGER_MAX_DISTANCE = 30.0f; + float scaledMaxTransitDistance = TRANSIT_TRIGGER_MAX_DISTANCE * _scale; + if (oneFrameDistance > config._triggerDistance && checkDistance) { if (oneFrameDistance < scaledMaxTransitDistance) { start(deltaTime, _lastPosition, avatarPosition, config); } else { @@ -127,9 +129,12 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av } _lastPosition = avatarPosition; _status = updatePosition(deltaTime); - if (_isTransiting && oneFrameDistance > 0.1f && _status == Status::POST_TRANSIT_IDLE) { + + const float SETTLE_ABORT_DISTANCE = 0.1f; + float scaledSettleAbortDistance = SETTLE_ABORT_DISTANCE * _scale; + if (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT) { reset(); - _status = Status::END_FRAME; + _status = Status::ENDED; } return _status; } @@ -137,7 +142,7 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av void AvatarTransit::reset() { _lastPosition = _endPosition; _currentPosition = _endPosition; - _isTransiting = false; + _isActive = false; } void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) { _startPosition = startPosition; @@ -147,14 +152,14 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _totalDistance = glm::length(_transitLine); _easeType = config._easeType; const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - _preTime = (float)config._startTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; - _postTime = (float)config._endTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + _preTransitTime = (float)config._startTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + _postTransitTime = (float)config._endTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; - _totalTime = _transitTime + _preTime + _postTime; - _currentTime = _isTransiting ? _preTime : 0.0f; - _isTransiting = true; + _totalTime = _transitTime + _preTransitTime + _postTransitTime; + _currentTime = _isActive ? _preTransitTime : 0.0f; + _isActive = true; } float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) { @@ -177,29 +182,29 @@ float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) { AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { Status status = Status::IDLE; - if (_isTransiting) { + if (_isActive) { float nextTime = _currentTime + deltaTime; - if (nextTime < _preTime) { + if (nextTime < _preTransitTime) { _currentPosition = _startPosition; - status = Status::PRE_TRANSIT_IDLE; + status = Status::PRE_TRANSIT; if (_currentTime == 0) { - status = Status::START_FRAME; + status = Status::STARTED; } - } else if (nextTime < _totalTime - _postTime){ + } else if (nextTime < _totalTime - _postTransitTime){ status = Status::TRANSITING; - if (_currentTime <= _preTime) { + if (_currentTime <= _preTransitTime) { status = Status::START_TRANSIT; } else { - float percentageIntoTransit = (nextTime - _preTime) / _transitTime; + float percentageIntoTransit = (nextTime - _preTransitTime) / _transitTime; _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; } } else { - status = Status::POST_TRANSIT_IDLE; + status = Status::POST_TRANSIT; _currentPosition = _endPosition; if (nextTime >= _totalTime) { - _isTransiting = false; - status = Status::END_FRAME; - } else if (_currentTime < _totalTime - _postTime) { + _isActive = false; + status = Status::ENDED; + } else if (_currentTime < _totalTime - _postTransitTime) { status = Status::END_TRANSIT; } } @@ -208,11 +213,6 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { return status; } -bool AvatarTransit::getNextPosition(glm::vec3& nextPosition) { - nextPosition = _currentPosition; - return _isTransiting; -} - Avatar::Avatar(QThread* thread) : _voiceSphereID(GeometryCache::UNKNOWN_ID) { @@ -554,14 +554,11 @@ void Avatar::relayJointDataToChildren() { void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); - if (_transit.isTransiting()) { - glm::vec3 nextPosition; - if (_transit.getNextPosition(nextPosition)) { - _globalPosition = nextPosition; - _globalPositionChanged = usecTimestampNow(); - if (!hasParent()) { - setLocalPosition(nextPosition); - } + if (_transit.isActive()) { + _globalPosition = _transit.getCurrentPosition(); + _globalPositionChanged = usecTimestampNow(); + if (!hasParent()) { + setLocalPosition(_transit.getCurrentPosition()); } } @@ -575,7 +572,7 @@ void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "updateJoints"); if (inView) { Head* head = getHead(); - if (_hasNewJointData || _transit.isTransiting()) { + if (_hasNewJointData || _transit.isActive()) { _skeletonModel->getRig().copyJointsFromJointData(_jointData); glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); _skeletonModel->getRig().computeExternalPoses(rootTransform); @@ -2006,7 +2003,7 @@ void Avatar::setTransitScale(float scale) { return _transit.setScale(scale); } -void Avatar::overrideNextPackagePositionData(const glm::vec3& position) { +void Avatar::overrideNextPacketPositionData(const glm::vec3& position) { std::lock_guard lock(_transitLock); _overrideGlobalPosition = true; _globalPositionOverride = position; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index fe163b9dae..e4fd667c1f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -54,13 +54,13 @@ class AvatarTransit { public: enum Status { IDLE = 0, - START_FRAME, - PRE_TRANSIT_IDLE, + STARTED, + PRE_TRANSIT, START_TRANSIT, TRANSITING, END_TRANSIT, - POST_TRANSIT_IDLE, - END_FRAME, + POST_TRANSIT, + ENDED, ABORT_TRANSIT }; @@ -95,11 +95,9 @@ public: AvatarTransit() {}; Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config); Status getStatus() { return _status; } - bool isTransiting() { return _isTransiting; } + bool isActive() { return _isActive; } glm::vec3 getCurrentPosition() { return _currentPosition; } - bool getNextPosition(glm::vec3& nextPosition); glm::vec3 getEndPosition() { return _endPosition; } - float getTransitTime() { return _totalTime; } void setScale(float scale) { _scale = scale; } void reset(); @@ -107,7 +105,7 @@ private: Status updatePosition(float deltaTime); void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config); float getEaseValue(AvatarTransit::EaseType type, float value); - bool _isTransiting { false }; + bool _isActive { false }; glm::vec3 _startPosition; glm::vec3 _endPosition; @@ -117,10 +115,10 @@ private: glm::vec3 _transitLine; float _totalDistance { 0.0f }; - float _preTime { 0.0f }; + float _preTransitTime { 0.0f }; float _totalTime { 0.0f }; float _transitTime { 0.0f }; - float _postTime { 0.0f }; + float _postTransitTime { 0.0f }; float _currentTime { 0.0f }; EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; @@ -452,7 +450,7 @@ public: AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); void setTransitScale(float scale); - void overrideNextPackagePositionData(const glm::vec3& position); + void overrideNextPacketPositionData(const glm::vec3& position); signals: void targetScaleChanged(float targetScale); From fede22499c137785b50cfbb26023847b76b55b3b Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 15 Oct 2018 11:58:13 -0700 Subject: [PATCH 058/131] Added app to control exponential filters on vive trackers, in real time --- scripts/developer/exponentialFilterApp.js | 240 ++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 scripts/developer/exponentialFilterApp.js diff --git a/scripts/developer/exponentialFilterApp.js b/scripts/developer/exponentialFilterApp.js new file mode 100644 index 0000000000..774ea95533 --- /dev/null +++ b/scripts/developer/exponentialFilterApp.js @@ -0,0 +1,240 @@ +var LEFT_HAND_INDEX = 0; +var RIGHT_HAND_INDEX = 1; +var LEFT_FOOT_INDEX = 2; +var RIGHT_FOOT_INDEX = 3; +var HIPS_INDEX = 4; +var SPINE2_INDEX = 5; + +var HAND_SMOOTHING_TRANSLATION = 0.3; +var HAND_SMOOTHING_ROTATION = 0.15; +var FOOT_SMOOTHING_TRANSLATION = 0.3; +var FOOT_SMOOTHING_ROTATION = 0.15; +var TORSO_SMOOTHING_TRANSLATION = 0.3; +var TORSO_SMOOTHING_ROTATION = 0.16; + +var mappingJson = { + name: "com.highfidelity.testing.exponentialFilterApp", + channels: [ + { + from: "Standard.LeftHand", + to: "Actions.LeftHand", + filters: [ + { + type: "exponentialSmoothing", + translation: HAND_SMOOTHING_TRANSLATION, + rotation: HAND_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.RightHand", + to: "Actions.RightHand", + filters: [ + { + type: "exponentialSmoothing", + translation: HAND_SMOOTHING_TRANSLATION, + rotation: HAND_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.LeftFoot", + to: "Actions.LeftFoot", + filters: [ + { + type: "exponentialSmoothing", + translation: FOOT_SMOOTHING_TRANSLATION, + rotation: FOOT_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.RightFoot", + to: "Actions.RightFoot", + filters: [ + { + type: "exponentialSmoothing", + translation: FOOT_SMOOTHING_TRANSLATION, + rotation: FOOT_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.Hips", + to: "Actions.Hips", + filters: [ + { + type: "exponentialSmoothing", + translation: TORSO_SMOOTHING_TRANSLATION, + rotation: TORSO_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.Spine2", + to: "Actions.Spine2", + filters: [ + { + type: "exponentialSmoothing", + translation: TORSO_SMOOTHING_TRANSLATION, + rotation: TORSO_SMOOTHING_ROTATION + } + ] + } + ] +}; + +// +// tablet app boiler plate +// + +var TABLET_BUTTON_NAME = "EXPFILT"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/exponentialFilterApp.html?7"; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg" +}); + +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +}); + +var shown = false; + +function onScreenChanged(type, url) { + if (type === "Web" && url === HTML_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} + +function getTranslation(i) { + return mappingJson.channels[i].filters[0].translation; +} +function setTranslation(i, value) { + mappingJson.channels[i].filters[0].translation = value; + mappingChanged(); +} +function getRotation(i) { + return mappingJson.channels[i].filters[0].rotation; +} +function setRotation(i, value) { + mappingJson.channels[i].filters[0].rotation = value; mappingChanged(); +} + +function onWebEventReceived(msg) { + if (msg.name === "init-complete") { + var values = [ + {name: "enable-filtering", val: filterEnabled ? "on" : "off", checked: false}, + {name: "left-hand-translation", val: getTranslation(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-rotation", val: getRotation(LEFT_HAND_INDEX), checked: false}, + {name: "right-hand-translation", val: getTranslation(RIGHT_HAND_INDEX), checked: false}, + {name: "right-hand-rotation", val: getRotation(RIGHT_HAND_INDEX), checked: false}, + {name: "left-foot-translation", val: getTranslation(LEFT_FOOT_INDEX), checked: false}, + {name: "left-foot-rotation", val: getRotation(LEFT_FOOT_INDEX), checked: false}, + {name: "right-foot-translation", val: getTranslation(RIGHT_FOOT_INDEX), checked: false}, + {name: "right-foot-rotation", val: getRotation(RIGHT_FOOT_INDEX), checked: false}, + {name: "hips-translation", val: getTranslation(HIPS_INDEX), checked: false}, + {name: "hips-rotation", val: getRotation(HIPS_INDEX), checked: false}, + {name: "spine2-translation", val: getTranslation(SPINE2_INDEX), checked: false}, + {name: "spine2-rotation", val: getRotation(SPINE2_INDEX), checked: false} + ]; + tablet.emitScriptEvent(JSON.stringify(values)); + } else if (msg.name === "enable-filtering") { + if (msg.val === "on") { + filterEnabled = true; + } else if (msg.val === "off") { + filterEnabled = false; + } + mappingChanged(); + } else if (msg.name === "left-hand-translation") { + setTranslation(LEFT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "left-hand-rotation") { + setRotation(LEFT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "right-hand-translation") { + setTranslation(RIGHT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "right-hand-rotation") { + setRotation(RIGHT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "left-foot-translation") { + setTranslation(LEFT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "left-foot-rotation") { + setRotation(LEFT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "right-foot-translation") { + setTranslation(RIGHT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "right-foot-rotation") { + setRotation(RIGHT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "hips-translation") { + setTranslation(HIPS_INDEX, Number(msg.val)); + } else if (msg.name === "hips-rotation") { + setRotation(HIPS_INDEX, Number(msg.val)); + } else if (msg.name === "spine2-translation") { + setTranslation(SPINE2_INDEX, Number(msg.val)); + } else if (msg.name === "spine2-rotation") { + setRotation(SPINE2_INDEX, Number(msg.val)); + } +} + +tablet.screenChanged.connect(onScreenChanged); + +function shutdownTabletApp() { + tablet.removeButton(tabletButton); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +} + +// +// end tablet app boiler plate +// + +var filterEnabled = true; +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + if (filterEnabled) { + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); + } +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +mappingChanged(); + +Script.scriptEnding.connect(function() { + if (mapping) { + mapping.disable(); + } + tablet.removeButton(tabletButton); +}); + From 640ed6a32a23b4f9b0ebe980d7952ad47359f2bb Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 15 Oct 2018 12:42:07 -0700 Subject: [PATCH 059/131] Added "Developer > Avatar > Show Tracked Objects" menu --- interface/src/Application.cpp | 46 ++++++++++--------- interface/src/Application.h | 5 ++ interface/src/Menu.cpp | 2 + interface/src/Menu.h | 1 + .../animation/src/AnimInverseKinematics.cpp | 2 +- libraries/animation/src/Rig.cpp | 2 +- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7457afe091..b05de598a0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5802,43 +5802,41 @@ void Application::update(float deltaTime) { myAvatar->setControllerPoseInSensorFrame(action, pose.transform(avatarToSensorMatrix)); } - // AJT: TODO put a nice menu around this. - // Make sure to remove all markers when menu is turned off. - { + static const std::vector trackedObjectStringLiterals = { + QStringLiteral("_TrackedObject00"), QStringLiteral("_TrackedObject01"), QStringLiteral("_TrackedObject02"), QStringLiteral("_TrackedObject03"), + QStringLiteral("_TrackedObject04"), QStringLiteral("_TrackedObject05"), QStringLiteral("_TrackedObject06"), QStringLiteral("_TrackedObject07"), + QStringLiteral("_TrackedObject08"), QStringLiteral("_TrackedObject09"), QStringLiteral("_TrackedObject10"), QStringLiteral("_TrackedObject11"), + QStringLiteral("_TrackedObject12"), QStringLiteral("_TrackedObject13"), QStringLiteral("_TrackedObject14"), QStringLiteral("_TrackedObject15") + }; + + // Controlled by the Developer > Avatar > Show Tracked Objects menu. + if (_showTrackedObjects) { static const std::vector trackedObjectActions = { - controller::Action::TRACKED_OBJECT_00, - controller::Action::TRACKED_OBJECT_01, - controller::Action::TRACKED_OBJECT_02, - controller::Action::TRACKED_OBJECT_03, - controller::Action::TRACKED_OBJECT_04, - controller::Action::TRACKED_OBJECT_05, - controller::Action::TRACKED_OBJECT_06, - controller::Action::TRACKED_OBJECT_07, - controller::Action::TRACKED_OBJECT_08, - controller::Action::TRACKED_OBJECT_09, - controller::Action::TRACKED_OBJECT_10, - controller::Action::TRACKED_OBJECT_11, - controller::Action::TRACKED_OBJECT_12, - controller::Action::TRACKED_OBJECT_13, - controller::Action::TRACKED_OBJECT_14, - controller::Action::TRACKED_OBJECT_15 + controller::Action::TRACKED_OBJECT_00, controller::Action::TRACKED_OBJECT_01, controller::Action::TRACKED_OBJECT_02, controller::Action::TRACKED_OBJECT_03, + controller::Action::TRACKED_OBJECT_04, controller::Action::TRACKED_OBJECT_05, controller::Action::TRACKED_OBJECT_06, controller::Action::TRACKED_OBJECT_07, + controller::Action::TRACKED_OBJECT_08, controller::Action::TRACKED_OBJECT_09, controller::Action::TRACKED_OBJECT_10, controller::Action::TRACKED_OBJECT_11, + controller::Action::TRACKED_OBJECT_12, controller::Action::TRACKED_OBJECT_13, controller::Action::TRACKED_OBJECT_14, controller::Action::TRACKED_OBJECT_15 }; int i = 0; glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); for (auto& action : trackedObjectActions) { - QString key = QString("_TrackedObject%1").arg(i); controller::Pose pose = userInputMapper->getPoseState(action); if (pose.valid) { glm::vec3 pos = transformPoint(myAvatarMatrix, pose.translation); glm::quat rot = glmExtractRotation(myAvatarMatrix) * pose.rotation; - DebugDraw::getInstance().addMarker(key, rot, pos, BLUE); + DebugDraw::getInstance().addMarker(trackedObjectStringLiterals[i], rot, pos, BLUE); } else { - DebugDraw::getInstance().removeMarker(key); + DebugDraw::getInstance().removeMarker(trackedObjectStringLiterals[i]); } i++; } + } else if (_prevShowTrackedObjects) { + for (auto& key : trackedObjectStringLiterals) { + DebugDraw::getInstance().removeMarker(key); + } } + _prevShowTrackedObjects = _showTrackedObjects; } updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... @@ -8330,6 +8328,10 @@ void Application::setShowBulletConstraintLimits(bool value) { _physicsEngine->setShowBulletConstraintLimits(value); } +void Application::setShowTrackedObjects(bool value) { + _showTrackedObjects = value; +} + void Application::startHMDStandBySession() { _autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 75260b910f..739738e7e8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -496,6 +496,8 @@ private slots: void setShowBulletConstraints(bool value); void setShowBulletConstraintLimits(bool value); + void setShowTrackedObjects(bool value); + private: void init(); bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event); @@ -777,5 +779,8 @@ private: std::atomic _pendingRenderEvent { true }; bool quitWhenFinished { false }; + + bool _showTrackedObjects { false }; + bool _prevShowTrackedObjects { false }; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index eef14c873e..8340cb8d20 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -570,6 +570,8 @@ Menu::Menu() { avatar.get(), SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowTrackedObjects, 0, false, qApp, SLOT(setShowTrackedObjects(bool))); + // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 031ee2561c..1e9955a760 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -183,6 +183,7 @@ namespace MenuOption { const QString RunClientScriptTests = "Run Client Script Tests"; const QString RunTimingTests = "Run Timing Tests"; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString ShowTrackedObjects = "Show Tracked Objects"; const QString SendWrongDSConnectVersion = "Send wrong DS connect version"; const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SetHomeLocation = "Set Home Location"; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 399eaf3fab..a1809f3438 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -24,7 +24,7 @@ #include "AnimUtil.h" static const int MAX_TARGET_MARKERS = 30; -static const float JOINT_CHAIN_INTERP_TIME = 0.25f; +static const float JOINT_CHAIN_INTERP_TIME = 0.5f; static void lookupJointInfo(const AnimInverseKinematics::JointChainInfo& jointChainInfo, int indexA, int indexB, diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d076ce5029..55cfbd6901 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1666,7 +1666,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo // This should help smooth out problems with the vive tracker when the sensor is occluded. if (prevHipsEnabled && hipsEstimated != prevHipsEstimated) { // blend from a snapshot of the previous hips. - const float HIPS_BLEND_DURATION = 0.3f; + const float HIPS_BLEND_DURATION = 0.5f; _hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION); _hipsBlendHelper.setSnapshot(_previousControllerParameters.primaryControllerPoses[PrimaryControllerType_Hips]); } else if (!prevHipsEnabled) { From 738ac30f1dc880f1dd3c903554282b543176be12 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 15 Oct 2018 11:16:30 -0700 Subject: [PATCH 060/131] Update default entity properties in Create --- scripts/system/edit.js | 215 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 203 insertions(+), 12 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 27858722ec..96d3d763b0 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -42,6 +42,9 @@ var TITLE_OFFSET = 60; var CREATE_TOOLS_WIDTH = 490; var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; +var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; +var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + var createToolsWindow = new CreateWindow( Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml", 'Create Tools', @@ -294,6 +297,187 @@ function checkEditPermissionsAndUpdate() { } } +const DEFAULT_ENTITY_PROPERTIES = { + All: { + collisionless: true, + description: "", + rotation: { x: 0, y: 0, z: 0, w: 1 }, + collidesWith: "static,dynamic,kinematic,otherAvatar", + collisionSoundURL: "", + cloneable: false, + ignoreIK: true, + canCastShadow: true, + href: "", + script: "", + serverScripts:"", + velocity: { + x: 0, + y: 0, + z: 0 + }, + damping: 0, + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + angularDamping: 0, + restitution: 0.5, + friction: 0.5, + density: 1000, + gravity: { + x: 0, + y: 0, + z: 0 + }, + acceleration: { + x: 0, + y: 0, + z: 0 + }, + dynamic: false, + }, + Shape: { + shape: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 0, green: 180, blue: 239 }, + }, + Text: { + text: "Text", + textColor: { red: 255, green: 255, blue: 255 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + lineHeight: 0.06, + faceCamera: false, + }, + Zone: { + flyingAllowed: true, + ghostingAllowed: true, + filter: "", + keyLightMode: "inherit", + keyLightColor: { red: 255, green: 255, blue: 255 }, + keyLight: { + intensity: 1.0, + direction: { + x: 0.0, + y: Math.PI / 4, + }, + castShadows: true + }, + ambientLightMode: "inherit", + ambientLight: { + ambientIntensity: 0.5, + ambientURL: "" + }, + hazeMode: "inherit", + haze: { + hazeRange: 1000, + hazeAltitudeEffect: false, + hazeBaseRef: 0, + hazeColor: { + red: 128, + green: 154, + blue: 129 + }, + hazeBackgroundBlend: 0, + hazeEnableGlare: false, + hazeGlareColor: { + red: 255, + green: 229, + blue: 179 + }, + }, + bloomMode: "inherit" + }, + Model: { + modelURL: "", + collisionShape: "none", + compoundShapeURL: "", + animation: { + url: "", + running: false, + allowTranslation: false, + loop: true, + hold: false, + currentFrame: 0, + firstFrame: 0, + lastFrame: 100000, + fps: 30.0, + } + }, + Image: { + dimensions: { + x: 0.5385, + y: 0.2819, + z: 0.0092 + }, + shapeType: "box", + collisionless: true, + modelURL: IMAGE_MODEL, + textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }) + }, + Web: { + sourceUrl: "https://highfidelity.com/", + dpi: 30, + }, + Particles: { + lifespan: 1.5, + maxParticles: 10, + textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + emitRate: 5.5, + emitSpeed: 0, + speedSpread: 0, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, + emitterShouldTrail: true, + particleRadius: 0.25, + radiusStart: 0, + radiusFinish: 0.1, + radiusSpread: 0, + particleColor: { + red: 255, + green: 255, + blue: 255 + }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0, + alphaStart: 1, + alphaFinish: 0, + alphaSpread: 0, + emitAcceleration: { + x: 0, + y: 2.5, + z: 0 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + particleSpin: 0, + spinStart: 0, + spinFinish: 0, + spinSpread: 0, + rotateWithEntity: false, + azimuthStart: 0, + azimuthFinish: 0, + polarStart: -Math.PI, + polarFinish: Math.PI + }, + Light: { + color: { red: 255, green: 255, blue: 255 }, + intensity: 5.0, + falloffRadius: 1.0, + isSpotlight: false, + exponent: 1.0, + cutoff: 75.0, + dimensions: { x: 20, y: 20, z: 20 }, + }, +}; + var toolBar = (function () { var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts var that = {}, @@ -303,11 +487,29 @@ var toolBar = (function () { dialogWindow = null, tablet = null; + function applyProperties(originalProperties, newProperties) { + for (var key in newProperties) { + originalProperties[key] = newProperties[key]; + } + } function createNewEntity(properties) { var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS; var position = getPositionToCreateEntity(); var entityID = null; + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + + var type = properties.type; + if (type == "Box" || type == "Sphere") { + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); + } else if (type == "Image") { + properties.type = "Model"; + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Image); + } else { + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); + } + + if (position !== null && position !== undefined) { var direction; if (Camera.mode === "entity" || Camera.mode === "independent") { @@ -712,19 +914,8 @@ var toolBar = (function () { }); addButton("newImageButton", function () { - var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; - var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; createNewEntity({ - type: "Model", - dimensions: { - x: 0.5385, - y: 0.2819, - z: 0.0092 - }, - shapeType: "box", - collisionless: true, - modelURL: IMAGE_MODEL, - textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }) + type: "Image", }); }); From 7da5fa9ea7fcba68b9c4ac46af3d558c5a0b3cba Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 16 Oct 2018 11:33:58 -0700 Subject: [PATCH 061/131] Transit animations won't play, they just get sent over the network --- interface/src/avatar/AvatarManager.cpp | 8 +- interface/src/avatar/MyAvatar.cpp | 17 ++++ interface/src/avatar/MyAvatar.h | 2 + libraries/animation/src/Rig.cpp | 120 +++++++++++++++++++++++-- libraries/animation/src/Rig.h | 8 ++ 5 files changed, 146 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c1fddaa680..373ae9980a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -151,22 +151,22 @@ void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { switch (status) { case AvatarTransit::Status::STARTED: qDebug() << "START_FRAME"; - _myAvatar->overrideAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, + _myAvatar->overrideNetworkAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, startAnimation._firstFrame + startAnimation._frameCount); break; case AvatarTransit::Status::START_TRANSIT: qDebug() << "START_TRANSIT"; - _myAvatar->overrideAnimation(middleAnimation._animationUrl, REFERENCE_FPS, false, middleAnimation._firstFrame, + _myAvatar->overrideNetworkAnimation(middleAnimation._animationUrl, REFERENCE_FPS, false, middleAnimation._firstFrame, middleAnimation._firstFrame + middleAnimation._frameCount); break; case AvatarTransit::Status::END_TRANSIT: qDebug() << "END_TRANSIT"; - _myAvatar->overrideAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, + _myAvatar->overrideNetworkAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, endAnimation._firstFrame + endAnimation._frameCount); break; case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; - _myAvatar->restoreAnimation(); + _myAvatar->restoreNetworkAnimation(); break; case AvatarTransit::Status::PRE_TRANSIT: break; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b347963cf1..152215e717 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1124,6 +1124,15 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame); } +void MyAvatar::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "overrideNetworkAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), + Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); + return; + } + _skeletonModel->getRig().overrideNetworkAnimation(url, fps, loop, firstFrame, lastFrame); +} + void MyAvatar::restoreAnimation() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "restoreAnimation"); @@ -1132,6 +1141,14 @@ void MyAvatar::restoreAnimation() { _skeletonModel->getRig().restoreAnimation(); } +void MyAvatar::restoreNetworkAnimation() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "restoreNetworkAnimation"); + return; + } + _skeletonModel->getRig().restoreNetworkAnimation(); +} + QStringList MyAvatar::getAnimationRoles() { if (QThread::currentThread() != thread()) { QStringList result; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 16b765711a..9770a5bb1a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -376,6 +376,7 @@ public: * }, 3000); */ Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + Q_INVOKABLE void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); /**jsdoc * The avatar animation system includes a set of default animations along with rules for how those animations are blended together with @@ -392,6 +393,7 @@ public: * }, 3000); */ Q_INVOKABLE void restoreAnimation(); + Q_INVOKABLE void restoreNetworkAnimation(); /**jsdoc * Each avatar has an avatar-animation.json file that defines which animations are used and how they are blended together with procedural data diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 91d4e0f9d3..b9e654964a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -133,6 +133,43 @@ void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firs _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } +void Rig::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { + UserAnimState::ClipNodeEnum clipNodeEnum; + if (_networkAnimState.clipNodeEnum == UserAnimState::None || _networkAnimState.clipNodeEnum == UserAnimState::B) { + clipNodeEnum = UserAnimState::A; + } + else { + clipNodeEnum = UserAnimState::B; + } + if (_networkNode) { + // find an unused AnimClip clipNode + _sendNetworkNode = true; + std::shared_ptr clip; + if (clipNodeEnum == UserAnimState::A) { + clip = std::dynamic_pointer_cast(_networkNode->findByName("userAnimA")); + } + else { + clip = std::dynamic_pointer_cast(_networkNode->findByName("userAnimB")); + } + if (clip) { + // set parameters + clip->setLoopFlag(loop); + clip->setStartFrame(firstFrame); + clip->setEndFrame(lastFrame); + const float REFERENCE_FRAMES_PER_SECOND = 30.0f; + float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; + clip->setTimeScale(timeScale); + clip->loadURL(url); + } + } + // store current user anim state. + _networkAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame }; + // notify the userAnimStateMachine the desired state. + _networkVars.set("userAnimNone", false); + _networkVars.set("userAnimA", clipNodeEnum == UserAnimState::A); + _networkVars.set("userAnimB", clipNodeEnum == UserAnimState::B); +} + void Rig::restoreAnimation() { if (_userAnimState.clipNodeEnum != UserAnimState::None) { _userAnimState.clipNodeEnum = UserAnimState::None; @@ -144,6 +181,17 @@ void Rig::restoreAnimation() { } } +void Rig::restoreNetworkAnimation() { + _sendNetworkNode = false; + if (_networkAnimState.clipNodeEnum != UserAnimState::None) { + _networkAnimState.clipNodeEnum = UserAnimState::None; + // notify the userAnimStateMachine the desired state. + _networkVars.set("userAnimNone", true); + _networkVars.set("userAnimA", false); + _networkVars.set("userAnimB", false); + } +} + QStringList Rig::getAnimationRoles() const { if (_animNode) { QStringList list; @@ -208,11 +256,17 @@ void Rig::restoreRoleAnimation(const QString& role) { void Rig::destroyAnimGraph() { _animSkeleton.reset(); _animLoader.reset(); + _networkLoader.reset(); _animNode.reset(); _internalPoseSet._relativePoses.clear(); _internalPoseSet._absolutePoses.clear(); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overrideFlags.clear(); + _networkNode.reset(); + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._absolutePoses.clear(); + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overrideFlags.clear(); _numOverrides = 0; _leftEyeJointChildren.clear(); _rightEyeJointChildren.clear(); @@ -229,14 +283,24 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); _internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); + + _networkPoseSet._overrideFlags.clear(); + _networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _numOverrides = 0; buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); @@ -270,6 +334,18 @@ void Rig::reset(const FBXGeometry& geometry) { _internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); + + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); + + _networkPoseSet._overrideFlags.clear(); + _networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _numOverrides = 0; buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); @@ -1049,26 +1125,37 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); - + if (_networkNode) { + _networkVars.setRigToGeometryTransform(_rigToGeometryTransform); + } AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains, getGeometryToRigTransform(), rigToWorldTransform); // evaluate the animation AnimVariantMap triggersOut; - + AnimVariantMap networkTriggersOut; _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); + if (_networkNode) { + _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + } if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } + if ((int)_networkPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { + // animations haven't fully loaded yet. + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + } _lastAnimVars = _animVars; _animVars.clearTriggers(); _animVars = triggersOut; + _networkVars.clearTriggers(); + _networkVars = networkTriggersOut; _lastContext = context; } applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); - + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); // copy internal poses to external poses { QWriteLocker writeLock(&_externalPoseSetLock); @@ -1707,9 +1794,12 @@ void Rig::initAnimGraph(const QUrl& url) { _animGraphURL = url; _animNode.reset(); + _networkNode.reset(); // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); + _networkLoader.reset(new AnimNodeLoader(url)); + std::weak_ptr weakSkeletonPtr = _animSkeleton; connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { _animNode = nodeIn; @@ -1740,6 +1830,26 @@ void Rig::initAnimGraph(const QUrl& url) { connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; }); + + connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { + _networkNode = nodeIn; + // abort load if the previous skeleton was deleted. + auto sharedSkeletonPtr = weakSkeletonPtr.lock(); + if (!sharedSkeletonPtr) { + return; + } + _networkNode->setSkeleton(sharedSkeletonPtr); + if (_networkAnimState.clipNodeEnum != UserAnimState::None) { + // restore the user animation we had before reset. + UserAnimState origState = _networkAnimState; + _networkAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; + overrideNetworkAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); + } + // emit onLoadComplete(); + }); + connect(_networkLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { + qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + }); } } @@ -1817,13 +1927,13 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { if (isIndexValid(i)) { // rotations are in absolute rig frame. glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot(); - data.rotation = _internalPoseSet._absolutePoses[i].rot(); + data.rotation = !_sendNetworkNode ? _internalPoseSet._absolutePoses[i].rot() : _networkPoseSet._absolutePoses[i].rot(); data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot); // translations are in relative frame but scaled so that they are in meters, // instead of geometry units. glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans(); - data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans(); + data.translation = _geometryOffset.scale() * (!_sendNetworkNode ? _internalPoseSet._relativePoses[i].trans() : _networkPoseSet._relativePoses[i].trans()); data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans); } else { data.translationIsDefaultPose = true; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 48f00d4e5d..e1012df029 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -113,7 +113,9 @@ public: void destroyAnimGraph(); void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreAnimation(); + void restoreNetworkAnimation(); QStringList getAnimationRoles() const; void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreRoleAnimation(const QString& role); @@ -270,6 +272,7 @@ protected: // Only accessed by the main thread PoseSet _internalPoseSet; + PoseSet _networkPoseSet; // Copy of the _poseSet for external threads. PoseSet _externalPoseSet; @@ -301,9 +304,12 @@ protected: QUrl _animGraphURL; std::shared_ptr _animNode; + std::shared_ptr _networkNode; std::shared_ptr _animSkeleton; std::unique_ptr _animLoader; + std::unique_ptr _networkLoader; AnimVariantMap _animVars; + AnimVariantMap _networkVars; enum class RigRole { Idle = 0, @@ -350,6 +356,7 @@ protected: }; UserAnimState _userAnimState; + UserAnimState _networkAnimState; std::map _roleAnimStates; float _leftHandOverlayAlpha { 0.0f }; @@ -391,6 +398,7 @@ protected: int _rigId; bool _headEnabled { false }; + bool _sendNetworkNode { false }; AnimContext _lastContext; AnimVariantMap _lastAnimVars; From 50becb5c378e80da26740a2b4702923d71481c37 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 16 Oct 2018 12:39:34 -0700 Subject: [PATCH 062/131] merge particle explorer into properties, resurrect sliders, other tidying/fixes --- .../resources/qml/hifi/tablet/EditTabView.qml | 20 +- .../qml/hifi/tablet/EditToolsTabView.qml | 23 +- scripts/system/edit.js | 77 +- scripts/system/html/css/edit-style.css | 68 +- scripts/system/html/entityProperties.html | 1 + scripts/system/html/js/entityProperties.js | 1074 ++++++++++++----- scripts/system/html/js/underscore-min.js | 6 + 7 files changed, 863 insertions(+), 406 deletions(-) create mode 100644 scripts/system/html/js/underscore-min.js diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 4ac8755570..bf7dd3e66b 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -175,7 +175,7 @@ TabBar { method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } }); - editTabView.currentIndex = 4 + editTabView.currentIndex = 2 } } @@ -279,21 +279,6 @@ TabBar { } } - EditTabButton { - title: "P" - active: true - enabled: true - property string originalUrl: "" - - property Component visualItem: Component { - WebView { - id: particleExplorerWebView - url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" - enabled: true - } - } - } - function fromScript(message) { switch (message.method) { case 'selectTab': @@ -326,9 +311,6 @@ TabBar { case 'grid': editTabView.currentIndex = 3; break; - case 'particle': - editTabView.currentIndex = 4; - break; default: console.warn('Attempt to switch to invalid tab:', id); } diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml index 00084b8ca9..13b1caf8fb 100644 --- a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml @@ -18,7 +18,6 @@ TabBar { readonly property int create: 0 readonly property int properties: 1 readonly property int grid: 2 - readonly property int particle: 3 } readonly property HifiConstants hifi: HifiConstants {} @@ -182,7 +181,7 @@ TabBar { method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } }); - editTabView.currentIndex = tabIndex.particle + editTabView.currentIndex = tabIndex.properties } } @@ -271,21 +270,6 @@ TabBar { } } - EditTabButton { - title: "P" - active: true - enabled: true - property string originalUrl: "" - - property Component visualItem: Component { - WebView { - id: particleExplorerWebView - url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" - enabled: true - } - } - } - function fromScript(message) { switch (message.method) { case 'selectTab': @@ -299,7 +283,7 @@ TabBar { // Changes the current tab based on tab index or title as input function selectTab(id) { if (typeof id === 'number') { - if (id >= tabIndex.create && id <= tabIndex.particle) { + if (id >= tabIndex.create && id <= tabIndex.grid) { editTabView.currentIndex = id; } else { console.warn('Attempt to switch to invalid tab:', id); @@ -315,9 +299,6 @@ TabBar { case 'grid': editTabView.currentIndex = tabIndex.grid; break; - case 'particle': - editTabView.currentIndex = tabIndex.particle; - break; default: console.warn('Attempt to switch to invalid tab:', id); } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 27858722ec..bcfc831d57 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,7 +12,7 @@ /* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, - progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool, OverlaySystemWindow */ + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow */ (function() { // BEGIN LOCAL_SCOPE @@ -32,7 +32,6 @@ Script.include([ "libraries/gridTool.js", "libraries/entityList.js", "libraries/utils.js", - "particle_explorer/particleExplorerTool.js", "libraries/entityIconOverlayManager.js" ]); @@ -109,28 +108,6 @@ var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); - - // Update particle explorer - var needToDestroyParticleExplorer = false; - if (selectionManager.selections.length === 1) { - var selectedEntityID = selectionManager.selections[0]; - if (selectedEntityID === selectedParticleEntityID) { - return; - } - var type = Entities.getEntityProperties(selectedEntityID, "type").type; - if (type === "ParticleEffect") { - selectParticleEntity(selectedEntityID); - } else { - needToDestroyParticleExplorer = true; - } - } else { - needToDestroyParticleExplorer = true; - } - - if (needToDestroyParticleExplorer && selectedParticleEntityID !== null) { - selectedParticleEntityID = null; - particleExplorerTool.destroyWebView(); - } }); var KEY_P = 80; //Key code for letter p used for Parenting hotkey. @@ -359,10 +336,6 @@ var toolBar = (function () { properties: properties }], [], true); - if (properties.type === "ParticleEffect") { - selectParticleEntity(entityID); - } - var POST_ADJUST_ENTITY_TYPES = ["Model"]; if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box after it has been created and auto-resized. @@ -1178,13 +1151,6 @@ function mouseClickEvent(event) { orientation = MyAvatar.orientation; intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); - if (event.isShifted) { - particleExplorerTool.destroyWebView(); - } - if (properties.type !== "ParticleEffect") { - particleExplorerTool.destroyWebView(); - } - if (!event.isShifted) { selectionManager.setSelections([foundEntity]); } else { @@ -1604,8 +1570,6 @@ function deleteSelectedEntities() { if (SelectionManager.hasSelection()) { var deletedIDs = []; - selectedParticleEntityID = null; - particleExplorerTool.destroyWebView(); SelectionManager.saveProperties(); var savedProperties = []; var newSortedSelection = sortSelectedEntities(selectionManager.selections); @@ -2572,31 +2536,6 @@ propertyMenu.onSelectMenuItem = function (name) { var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); var propertiesTool = new PropertiesTool(); -var particleExplorerTool = new ParticleExplorerTool(createToolsWindow); -var selectedParticleEntityID = null; - -function selectParticleEntity(entityID) { - selectedParticleEntityID = entityID; - - var properties = Entities.getEntityProperties(entityID); - if (properties.emitOrientation) { - properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); - } - - particleExplorerTool.destroyWebView(); - particleExplorerTool.createWebView(); - - particleExplorerTool.setActiveParticleEntity(entityID); - - // Switch to particle explorer - var selectTabMethod = { method: 'selectTab', params: { id: 'particle' } }; - if (shouldUseEditTabletApp()) { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.sendToQml(selectTabMethod); - } else { - createToolsWindow.sendToQml(selectTabMethod); - } -} entityListTool.webView.webEventReceived.connect(function(data) { try { @@ -2610,20 +2549,6 @@ entityListTool.webView.webEventReceived.connect(function(data) { parentSelectedEntities(); } else if (data.type === 'unparent') { unparentSelectedEntities(); - } else if (data.type === "selectionUpdate") { - var ids = data.entityIds; - if (ids.length === 1) { - if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") { - if (JSON.stringify(selectedParticleEntityID) === JSON.stringify(ids[0])) { - // This particle entity is already selected, so return - return; - } - // Destroy the old particles web view first - } else { - selectedParticleEntityID = 0; - particleExplorerTool.destroyWebView(); - } - } } }); diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index f69ace2401..8d334609a6 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -300,6 +300,28 @@ input[type=number].hover-down::-webkit-inner-spin-button:after { color: #ffffff; } +input[type=range] { + -webkit-appearance: none; + background: #2e2e2e; + height: 1.8rem; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb { + -webkit-appearance:none; + width: 0.6rem; + height: 1.8rem; + padding:0; + margin: 0; + background-color: #696969; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb:hover { + background-color: white; +} +input[type=range]:focus { /*#252525*/ + outline: none; +} + input.no-spin::-webkit-outer-spin-button, input.no-spin::-webkit-inner-spin-button { display: none; @@ -623,6 +645,15 @@ hr { margin-left: 10px; } +.property.range label{ + display: block; +} +.property.range input[type=number]{ + margin-left: 0.8rem; + width: 5.4rem; + height: 1.8rem; +} + .text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label,.pyr label, .dropdown label, .gen label { float: left; margin-left: 1px; @@ -853,13 +884,13 @@ div.refresh input[type="button"] { color: #1080b8; } -.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus { +.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus, .tuple .width:focus { outline-color: #e2334d; } -.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus { +.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus, .tuple .height:focus { outline-color: #1ac567; } -tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { +.tuple .blue:focus, .tuple .z:focus, .tuple .roll:focus { outline-color: #1080b8; } @@ -884,6 +915,37 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { float: left; } +.property.texture { + display: block; +} +.property.texture input{ + margin: 0.4rem 0; +} +.texture-image img{ + padding: 0; + margin: 0; + width: 100%; + height: 100%; + display: none; +} +.texture-image { + display: block; + position: relative; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; + margin-top: 0.4rem; + height:128px; + width: 128px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII='); +} +.texture-image.no-texture { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); +} +.texture-image.no-preview { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); +} + .two-column { display: table; width: 100%; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 93f80e19b3..370681339e 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -20,6 +20,7 @@ + diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 1d8a397f07..328a998dda 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -58,6 +58,11 @@ const GROUPS = [ type: "string", propertyID: "parentID", }, + { + label: "Parent Joint Index", + type: "number", + propertyID: "parentJointIndex", + }, { label: "Locked", glyph: "", @@ -116,7 +121,7 @@ const GROUPS = [ type: "number", min: 0, step: 0.005, - fixedDecimals: 4, + decimals: 4, unit: "m", propertyID: "lineHeight" }, @@ -165,14 +170,14 @@ const GROUPS = [ min: 0, max: 10, step: 0.1, - fixedDecimals: 2, + decimals: 2, propertyID: "keyLight.intensity", showPropertyRule: { "keyLightMode": "enabled" }, }, { label: "Light Altitude", type: "number", - fixedDecimals: 2, + decimals: 2, unit: "deg", propertyID: "keyLight.direction.y", showPropertyRule: { "keyLightMode": "enabled" }, @@ -180,7 +185,7 @@ const GROUPS = [ { label: "Light Azimuth", type: "number", - fixedDecimals: 2, + decimals: 2, unit: "deg", propertyID: "keyLight.direction.x", showPropertyRule: { "keyLightMode": "enabled" }, @@ -228,7 +233,7 @@ const GROUPS = [ min: 0, max: 10, step: 0.1, - fixedDecimals: 2, + decimals: 2, propertyID: "ambientLight.ambientIntensity", showPropertyRule: { "ambientLightMode": "enabled" }, }, @@ -250,7 +255,7 @@ const GROUPS = [ min: 5, max: 10000, step: 5, - fixedDecimals: 0, + decimals: 0, unit: "m", propertyID: "haze.hazeRange", showPropertyRule: { "hazeMode": "enabled" }, @@ -267,7 +272,7 @@ const GROUPS = [ min: -1000, max: 1000, step: 10, - fixedDecimals: 0, + decimals: 0, unit: "m", propertyID: "haze.hazeBaseRef", showPropertyRule: { "hazeMode": "enabled" }, @@ -278,7 +283,7 @@ const GROUPS = [ min: -1000, max: 5000, step: 10, - fixedDecimals: 0, + decimals: 0, unit: "m", propertyID: "haze.hazeCeiling", showPropertyRule: { "hazeMode": "enabled" }, @@ -291,11 +296,11 @@ const GROUPS = [ }, { label: "Background Blend", - type: "number", + type: "slider", min: 0.0, max: 1.0, step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "haze.hazeBackgroundBlend", showPropertyRule: { "hazeMode": "enabled" }, }, @@ -313,11 +318,11 @@ const GROUPS = [ }, { label: "Glare Angle", - type: "number", + type: "slider", min: 0, max: 180, step: 1, - fixedDecimals: 0, + decimals: 0, propertyID: "haze.hazeGlareAngle", showPropertyRule: { "hazeMode": "enabled" }, }, @@ -329,31 +334,31 @@ const GROUPS = [ }, { label: "Bloom Intensity", - type: "number", + type: "slider", min: 0, max: 1, step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "bloom.bloomIntensity", showPropertyRule: { "bloomMode": "enabled" }, }, { label: "Bloom Threshold", - type: "number", + type: "slider", min: 0, min: 1, step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "bloom.bloomThreshold", showPropertyRule: { "bloomMode": "enabled" }, }, { label: "Bloom Size", - type: "number", + type: "slider", min: 0, min: 2, step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "bloom.bloomSize", showPropertyRule: { "bloomMode": "enabled" }, }, @@ -469,7 +474,6 @@ const GROUPS = [ { id: "light", addToGroup: "base", - addToGroup: "base", properties: [ { label: "Light Color", @@ -482,7 +486,7 @@ const GROUPS = [ type: "number", min: 0, step: 0.1, - fixedDecimals: 1, + decimals: 1, propertyID: "intensity", }, { @@ -490,7 +494,7 @@ const GROUPS = [ type: "number", min: 0, step: 0.1, - fixedDecimals: 1, + decimals: 1, unit: "m", propertyID: "falloffRadius", }, @@ -503,14 +507,14 @@ const GROUPS = [ label: "Spotlight Exponent", type: "number", step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "exponent", }, { label: "Spotlight Cut-Off", type: "number", step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "cutoff", }, ] @@ -558,19 +562,21 @@ const GROUPS = [ { label: "Material Position", type: "vec2", + vec2Type: "xy", min: 0, min: 1, step: 0.1, - vec2Type: "xy", + decimals: 4, subLabels: [ "x", "y" ], propertyID: "materialMappingPos", }, { label: "Material Scale", type: "vec2", + vec2Type: "wh", min: 0, step: 0.1, - vec2Type: "wh", + decimals: 4, subLabels: [ "width", "height" ], propertyID: "materialMappingScale", }, @@ -578,12 +584,326 @@ const GROUPS = [ label: "Material Rotation", type: "number", step: 0.1, - fixedDecimals: 2, + decimals: 2, unit: "deg", propertyID: "materialMappingRot", }, ] }, + { + id: "particles", + addToGroup: "base", + properties: [ + { + label: "Emit", + type: "bool", + propertyID: "isEmitting", + }, + { + label: "Lifespan", + type: "slider", + unit: "s", + min: 0.01, + max: 10, + step: 0.01, + propertyID: "lifespan", + }, + { + label: "Max Particles", + type: "slider", + min: 1, + max: 10000, + step: 1, + propertyID: "maxParticles", + }, + { + label: "Texture", + type: "texture", + propertyID: "particleTextures", + propertyName: "textures", // actual entity property name + }, + ] + }, + { + id: "particles_emit", + label: "EMIT", + properties: [ + { + label: "Emit Rate", + type: "slider", + min: 1, + max: 1000, + step: 1, + propertyID: "emitRate", + }, + { + label: "Emit Speed", + type: "slider", + min: 0, + max: 5, + step: 0.01, + propertyID: "emitSpeed", + }, + { + label: "Speed Spread", + type: "slider", + min: 0, + max: 5, + step: 0.01, + propertyID: "speedSpread", + }, + { + label: "Emit Dimension", + type: "vec3", + vec3Type: "xyz", + min: 0, + step: 0.01, + subLabels: [ "x", "y", "z" ], + propertyID: "emitDimensions", + }, + { + label: "Emit Orientation", + type: "vec3", + vec3Type: "pyr", + min: 0, + step: 0.01, + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg", + propertyID: "emitOrientation", + }, + { + label: "Trails", + type: "bool", + propertyID: "emitterShouldTrail", + }, + ] + }, + { + id: "particles_size", + label: "SIZE", + properties: [ + { + label: "Size", + type: "slider", + max: 4, + step: 0.01, + propertyID: "particleRadius", + }, + { + label: "Size Spread", + type: "slider", + max: 4, + step: 0.01, + propertyID: "radiusSpread", + }, + { + label: "Size Start", + type: "slider", + max: 4, + step: 0.01, + propertyID: "radiusStart", + fallbackProperty: "particleRadius", + }, + { + label: "Size Finish", + type: "slider", + max: 4, + step: 0.01, + propertyID: "radiusFinish", + fallbackProperty: "particleRadius", + }, + ] + }, + { + id: "particles_color", + label: "COLOR", + properties: [ + { + label: "Color", + type: "color", + propertyID: "particleColor", + propertyName: "color", // actual entity property name + }, + { + label: "Color Start", + type: "color", + propertyID: "colorStart", + fallbackProperty: "color", + }, + { + label: "Color Finish", + type: "color", + propertyID: "colorFinish", + fallbackProperty: "color", + }, + { + label: "Color Spread", + type: "color", + propertyID: "colorSpread", + }, + ] + }, + { + id: "particles_alpha", + label: "ALPHA", + properties: [ + { + label: "Alpha", + type: "slider", + min: 0, + max: 1, + step: 0.01, + propertyID: "alpha", + }, + { + label: "Alpha Spread", + type: "slider", + min: 0, + max: 1, + step: 0.01, + propertyID: "alphaSpread", + }, + { + label: "Alpha Start", + type: "slider", + min: 0, + max: 1, + step: 0.01, + propertyID: "alphaStart", + fallbackProperty: "alpha", + }, + { + label: "Alpha Finish", + type: "slider", + min: 0, + max: 1, + step: 0.01, + propertyID: "alphaFinish", + fallbackProperty: "alpha", + }, + ] + }, + { + id: "particles_acceleration", + label: "ACCELERATION", + properties: [ + { + label: "Emit Acceleration", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + subLabels: [ "x", "y", "z" ], + propertyID: "emitAcceleration", + }, + { + label: "Acceleration Spread", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + subLabels: [ "x", "y", "z" ], + propertyID: "accelerationSpread", + }, + ] + }, + { + id: "particles_spin", + label: "SPIN", + properties: [ + { + label: "Spin", + type: "slider", + min: -360, + max: 360, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "particleSpin", + }, + { + label: "Spin Spread", + type: "slider", + min: 0, + max: 360, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinSpread", + }, + { + label: "Spin Start", + type: "slider", + min: -360, + max: 360, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinStart", + fallbackProperty: "particleSpin", + }, + { + label: "Spin Finish", + type: "slider", + min: -360, + max: 360, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinFinish", + fallbackProperty: "particleSpin", + }, + { + label: "Rotate with Entity", + type: "bool", + propertyID: "rotateWithEntity", + }, + ] + }, + { + id: "particles_constraints", + label: "CONSTRAINTS", + properties: [ + { + label: "Horizontal Angle Start", + type: "slider", + min: 0, + max: 180, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthStart", + }, + { + label: "Horizontal Angle Finish", + type: "slider", + min: -180, + max: 0, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthFinish", + }, + { + label: "Verical Angle Start", + type: "slider", + min: 0, + max: 180, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarStart", + }, + { + label: "Verical Angle Finish", + type: "slider", + min: 0, + max: 180, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarFinish", + }, + ] + }, { id: "spatial", label: "SPATIAL", @@ -592,6 +912,7 @@ const GROUPS = [ label: "Position", type: "vec3", vec3Type: "xyz", + decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m", propertyID: "position", @@ -599,8 +920,9 @@ const GROUPS = [ { label: "Rotation", type: "vec3", - step: 0.1, vec3Type: "pyr", + step: 0.1, + decimals: 4, subLabels: [ "pitch", "yaw", "roll" ], unit: "deg", propertyID: "rotation", @@ -608,8 +930,9 @@ const GROUPS = [ { label: "Dimension", type: "vec3", - step: 0.1, vec3Type: "xyz", + step: 0.1, + decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m", propertyID: "dimensions", @@ -626,8 +949,9 @@ const GROUPS = [ { label: "Pivot", type: "vec3", - step: 0.1, vec3Type: "xyz", + step: 0.1, + decimals: 4, subLabels: [ "x", "y", "z" ], unit: "(ratio of dimension)", propertyID: "registrationPoint", @@ -821,6 +1145,7 @@ const GROUPS = [ label: "Linear Velocity", type: "vec3", vec3Type: "xyz", + decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m/s", propertyID: "velocity", @@ -828,14 +1153,15 @@ const GROUPS = [ { label: "Linear Damping", type: "number", - fixedDecimals: 2, + decimals: 2, propertyID: "damping", }, { label: "Angular Velocity", type: "vec3", - multiplier: DEGREES_TO_RADIANS, vec3Type: "pyr", + multiplier: DEGREES_TO_RADIANS, + decimals: 4, subLabels: [ "pitch", "yaw", "roll" ], unit: "deg/s", propertyID: "angularVelocity", @@ -843,25 +1169,25 @@ const GROUPS = [ { label: "Angular Damping", type: "number", - fixedDecimals: 4, + decimals: 4, propertyID: "angularDamping", }, { label: "Bounciness", type: "number", - fixedDecimals: 4, + decimals: 4, propertyID: "restitution", }, { label: "Friction", type: "number", - fixedDecimals: 4, + decimals: 4, propertyID: "friction", }, { label: "Density", type: "number", - fixedDecimals: 4, + decimals: 4, propertyID: "density", }, { @@ -877,6 +1203,7 @@ const GROUPS = [ type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], + decimals: 4, unit: "m/s2", propertyID: "acceleration", }, @@ -894,11 +1221,13 @@ const GROUPS_PER_TYPE = { Web: [ 'base', 'web', 'spatial', 'collision', 'behavior', 'physics' ], Light: [ 'base', 'light', 'spatial', 'collision', 'behavior', 'physics' ], Material: [ 'base', 'material', 'spatial', 'behavior' ], - ParticleEffect: [ 'base', 'spatial', 'behavior', 'physics' ], + ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', + 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], Multiple: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], }; const EDITOR_TIMEOUT_DURATION = 1500; +const DEBOUNCE_TIMEOUT = 125; const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MATERIAL_PREFIX_STRING = "mat::"; @@ -921,10 +1250,31 @@ function debugPrint(message) { } -// GENERAL PROPERTY/GROUP FUNCTIONS +/** + * GENERAL PROPERTY/GROUP FUNCTIONS + */ -function getPropertyElement(propertyID) { - return properties[propertyID].el; +function getPropertyInputElement(propertyID) { + let property = properties[propertyID]; + switch (property.data.type) { + case 'string': + case 'bool': + case 'number': + case 'slider': + case 'dropdown': + case 'textarea': + case 'texture': + return property.elInput; + case 'vec3': + case 'vec2': + return { x: property.elInputX, y: property.elInputY, z: property.elInputZ }; + case 'color': + return { red: property.elInputR, green: property.elInputG, blue: property.elInputB }; + case 'icon': + return property.elLabel; + default: + return undefined; + } } function enableChildren(el, selector) { @@ -944,7 +1294,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = getPropertyElement("locked"); + var elLocked = getPropertyInputElement("locked"); if (elLocked.checked === false) { removeStaticUserData(); @@ -958,7 +1308,7 @@ function disableProperties() { for (var pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = getPropertyElement("locked"); + var elLocked = getPropertyInputElement("locked"); if (elLocked.checked === true) { if ($('#property-userData-editor').css('display') === "block") { @@ -971,74 +1321,70 @@ function disableProperties() { } function showPropertyElement(propertyID, show) { - let elProperty = properties[propertyID].el; - let elNode = elProperty; - if (elNode.nodeName !== "DIV") { - let elParent = elProperty.parentNode; - if (elParent === undefined && elProperty instanceof Array) { - elParent = elProperty[0].parentNode; - } - if (elParent !== undefined) { - elNode = elParent; - } - } - elNode.style.display = show ? "table" : "none"; + let elProperty = properties[propertyID].elProperty; + elProperty.style.display = show ? "table" : "none"; } function resetProperties() { for (let propertyID in properties) { - let elProperty = properties[propertyID].el; - let propertyData = properties[propertyID].data; + let property = properties[propertyID]; + let propertyData = property.data; switch (propertyData.type) { case 'string': { - elProperty.value = ""; + property.elInput.value = ""; break; } case 'bool': { - elProperty.checked = false; + property.elInput.checked = false; break; } - case 'number': { + case 'number': + case 'slider': { if (propertyData.defaultValue !== undefined) { - elProperty.value = propertyData.defaultValue; + property.elInput.value = propertyData.defaultValue; } else { - elProperty.value = ""; + property.elInput.value = ""; + } + if (property.elSlider !== undefined) { + property.elSlider.value = property.elInput.value; } break; } case 'vec3': case 'vec2': { - // vec2/vec3 are array of 2/3 elInput numbers - elProperty[0].value = ""; - elProperty[1].value = ""; - if (elProperty[2] !== undefined) { - elProperty[2].value = ""; + property.elInputX.value = ""; + property.elInputY.value = ""; + if (property.elInputZ !== undefined) { + property.elInputZ.value = ""; } break; } case 'color': { - // color is array of color picker and 3 elInput numbers - elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elProperty[1].value = ""; - elProperty[2].value = ""; - elProperty[3].value = ""; + property.elColorPicker.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + property.elInputR.value = ""; + property.elInputG.value = ""; + property.elInputB.value = ""; break; } case 'dropdown': { - elProperty.value = ""; - setDropdownText(elProperty); + property.elInput.value = ""; + setDropdownText(property.elInput); break; } case 'textarea': { - elProperty.value = ""; - setTextareaScrolling(elProperty); + property.elInput.value = ""; + setTextareaScrolling(property.elInput); break; } case 'icon': { - // icon is array of elSpan (icon glyph) and elLabel - elProperty[0].style.display = "none"; - elProperty[1].innerHTML = propertyData.label; + property.elSpan.style.display = "none"; + property.elLabel.innerHTML = propertyData.label; + break; + } + case 'texture': { + property.elInput.value = ""; + property.elInput.imageLoad(property.elInput.value); break; } } @@ -1069,8 +1415,34 @@ function showGroupsForType(type) { } } +function getPropertyValue(propertyName) { + // if this is a compound property name (i.e. animation.running) + // then split it by . up to 3 times to find property value + let propertyValue; + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + let groupProperties = selectedEntityProperties[propertyGroupName]; + if (groupProperties === undefined || groupProperties[subPropertyName] === undefined) { + return undefined; + } + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + propertyValue = groupProperties[subPropertyName][subSubPropertyName]; + } else { + propertyValue = groupProperties[subPropertyName]; + } + } else { + propertyValue = selectedEntityProperties[propertyName]; + } + return propertyValue; +} -// PROPERTY UPDATE FUNCTIONS + +/** + * PROPERTY UPDATE FUNCTIONS + */ function updateProperty(propertyName, propertyValue) { let propertyUpdate = {}; @@ -1114,26 +1486,24 @@ function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { }; } -function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { - decimals = ((decimals === undefined) ? 4 : decimals); +function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, decimals) { return function() { - let value = parseFloat(this.value).toFixed(decimals); + let value = parseFloat(this.value); + if (multiplier !== undefined) { + value *= multiplier; + } + if (decimals !== undefined) { + value = value.toFixed(decimals); + } updateProperty(propertyName, value); }; } -function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY) { - return function () { - let newValue = { - x: elX.value, - y: elY.value - }; - updateProperty(propertyName, newValue); - }; -} - -function createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elX, elY, multiplier) { +function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier) { return function () { + if (multiplier === undefined) { + multiplier = 1; + } let newValue = { x: elX.value * multiplier, y: elY.value * multiplier @@ -1142,19 +1512,11 @@ function createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elX, e }; } -function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ) { - return function() { - let newValue = { - x: elX.value, - y: elY.value, - z: elZ ? elZ.value : 0 - }; - updateProperty(propertyName, newValue); - }; -} - -function createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elX, elY, elZ, multiplier) { +function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier) { return function() { + if (multiplier === undefined) { + multiplier = 1; + } let newValue = { x: elX.value * multiplier, y: elY.value * multiplier, @@ -1199,14 +1561,16 @@ function createImageURLUpdateFunction(propertyName) { } -// PROPERTY ELEMENT CREATION FUNCTIONS +/** + * PROPERTY ELEMENT CREATION FUNCTIONS + */ function createStringProperty(property, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property text"); + elProperty.className = "property text"; let elInput = document.createElement('input'); elInput.setAttribute("id", elementID); @@ -1232,7 +1596,7 @@ function createBoolProperty(property, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property checkbox"); + elProperty.className = "property checkbox"; if (propertyData.glyph !== undefined) { elLabel.innerText = " " + propertyData.label; @@ -1260,15 +1624,108 @@ function createBoolProperty(property, elProperty, elLabel) { return elInput; } -function createVec3Property(property, elProperty, elLabel) { +function createNumberProperty(property, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property " + propertyData.vec3Type + " fstuple"); + elProperty.className = "property number"; + + addUnit(propertyData.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "number"); + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + } + + let defaultValue = propertyData.defaultValue; + if (defaultValue !== undefined) { + elInput.value = defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.muliplier, propertyData.decimals)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, true); + } + + return elInput; +} + +function createSliderProperty(property, elProperty, elLabel) { + let propertyData = property.data; + + elProperty.className = "property range"; + + let elDiv = document.createElement("div"); + elDiv.className = "slider-wrapper"; + + let elSlider = document.createElement("input"); + elSlider.setAttribute("type", "range"); + + let elInput = document.createElement("input"); + elInput.setAttribute("type", "number"); + + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + elSlider.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + elSlider.setAttribute("max", propertyData.max); + elSlider.setAttribute("data-max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + elSlider.setAttribute("step", propertyData.step); + } + + elInput.oninput = function (event) { + let value = event.target.value; + elSlider.value = value; + if (propertyData.multiplier !== undefined) { + value *= propertyData.multiplier; + } + updateProperty(property.name, value); + }; + elInput.onchange = elInput.oninput; + elSlider.oninput = function (event) { + let value = event.target.value; + elInput.value = value; + if (propertyData.multiplier !== undefined) { + value *= propertyData.multiplier; + } + updateProperty(property.name, value); + }; + + elDiv.appendChild(elLabel); + elDiv.appendChild(elSlider); + elDiv.appendChild(elInput); + elProperty.appendChild(elDiv); + + return [ elSlider, elInput ]; +} + +function createVec3Property(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property " + propertyData.vec3Type + " fstuple"; let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); + elTuple.className = "tuple"; addUnit(propertyData.unit, elLabel); @@ -1282,13 +1739,8 @@ function createVec3Property(property, elProperty, elLabel) { let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[2], propertyData.min, propertyData.max, propertyData.step); - let inputChangeFunction; - if (propertyData.multiplier !== undefined) { - inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, - elInputZ, propertyData.multiplier); - } else { - inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); - } + let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, + elInputZ, propertyData.multiplier); elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); elInputZ.addEventListener('change', inputChangeFunction); @@ -1301,10 +1753,10 @@ function createVec2Property(property, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property " + propertyData.vec2Type + " fstuple"); + elProperty.className = "property " + propertyData.vec2Type + " fstuple"; let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); + elTuple.className = "tuple"; addUnit(propertyData.unit, elLabel); @@ -1316,13 +1768,8 @@ function createVec2Property(property, elProperty, elLabel) { let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], propertyData.min, propertyData.max, propertyData.step); - let inputChangeFunction; - if (propertyData.multiplier !== undefined) { - inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, - propertyData.multiplier); - } else { - inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY); - } + let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, + elInputY, propertyData.multiplier); elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); @@ -1333,14 +1780,14 @@ function createColorProperty(property, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; - elProperty.setAttribute("class", "property rgb fstuple"); + elProperty.className = "property rgb fstuple"; let elColorPicker = document.createElement('div'); - elColorPicker.setAttribute("class", "color-picker"); + elColorPicker.className = "color-picker"; elColorPicker.setAttribute("id", elementID); let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); + elTuple.className = "tuple"; elProperty.appendChild(elColorPicker); elProperty.appendChild(elLabel); @@ -1388,7 +1835,7 @@ function createDropdownProperty(property, propertyID, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property dropdown"); + elProperty.className = "property dropdown"; let elInput = document.createElement('select'); elInput.setAttribute("id", elementID); @@ -1409,43 +1856,12 @@ function createDropdownProperty(property, propertyID, elProperty, elLabel) { return elInput; } -function createNumberProperty(property, elProperty, elLabel) { - let propertyName = property.name; - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.setAttribute("class", "property number"); - - addUnit(propertyData.unit, elLabel); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementID); - elInput.setAttribute("type", "number"); - - let defaultValue = propertyData.defaultValue; - if (defaultValue !== undefined) { - elInput.value = defaultValue; - } - - let fixedDecimals = propertyData.fixedDecimals !== undefined ? propertyData.fixedDecimals : 0; - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (propertyData.buttons !== undefined) { - addButtons(elProperty, elementID, propertyData.buttons, true); - } - - return elInput; -} - function createTextareaProperty(property, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property textarea"); + elProperty.className = "property textarea"; elProperty.appendChild(elLabel); @@ -1470,7 +1886,7 @@ function createIconProperty(property, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property value"); + elProperty.className = "property value"; elLabel.setAttribute("id", elementID); elLabel.innerHTML = " " + propertyData.label; @@ -1484,11 +1900,62 @@ function createIconProperty(property, elProperty, elLabel) { return [ elSpan, elLabel ]; } +function createTextureProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + + elProperty.className = "property texture"; + + let elDiv = document.createElement("div"); + let elImage = document.createElement("img"); + elDiv.className = "texture-image no-texture"; + elDiv.appendChild(elImage); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "text"); + + let imageLoad = _.debounce(function (url) { + if (url.slice(0, 5).toLowerCase() === "atp:/") { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-texture"); + elDiv.classList.add("no-preview"); + } else if (url.length > 0) { + elDiv.classList.remove("no-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("with-texture"); + elImage.src = url; + elImage.style.display = "block"; + } else { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("no-texture"); + } + }, DEBOUNCE_TIMEOUT * 2); + elInput.imageLoad = imageLoad; + elInput.oninput = function (event) { + // Add throttle + var url = event.target.value; + imageLoad(url); + updateProperty(property.name, url) + }; + elInput.onchange = elInput.oninput; + + elProperty.appendChild(elLabel); + elProperty.appendChild(elDiv); + elProperty.appendChild(elInput); + + return [ elImage, elInput ]; +} + function createButtonsProperty(property, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property text"); + elProperty.className = "property text"; let hasLabel = propertyData.label !== undefined; if (hasLabel) { @@ -1511,9 +1978,12 @@ function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, elLabel.setAttribute("for", elementID); let elInput = document.createElement('input'); + elInput.className = subLabel; elInput.setAttribute("id", elementID); elInput.setAttribute("type", "number"); - elInput.setAttribute("class", subLabel); + elInput.setAttribute("min", min); + elInput.setAttribute("max", max); + elInput.setAttribute("step", step); elDiv.appendChild(elInput); elDiv.appendChild(elLabel); @@ -1525,7 +1995,7 @@ function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, function addUnit(unit, elLabel) { if (unit !== undefined) { let elSpan = document.createElement('span'); - elSpan.setAttribute("class", "unit"); + elSpan.className = "unit"; elSpan.innerHTML = unit; elLabel.appendChild(elSpan); } @@ -1533,12 +2003,12 @@ function addUnit(unit, elLabel) { function addButtons(elProperty, propertyID, buttons, newRow) { let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "row"); + elDiv.className = "row"; buttons.forEach(function(button) { let elButton = document.createElement('input'); + elButton.className = button.className; elButton.setAttribute("type", "button"); - elButton.setAttribute("class", button.className); elButton.setAttribute("id", propertyID + "-button-" + button.id); elButton.setAttribute("value", button.label); elButton.addEventListener("click", button.onClick); @@ -1556,7 +2026,9 @@ function addButtons(elProperty, propertyID, buttons, newRow) { } -// BUTTON CALLBACKS +/** + * BUTTON CALLBACKS + */ function rescaleDimensions() { EventBridge.emitWebEvent(JSON.stringify({ @@ -1604,16 +2076,18 @@ function reloadServerScripts() { } function copySkyboxURLToAmbientURL() { - let skyboxURL = getPropertyElement("skybox.url").value; - getPropertyElement("ambientLight.ambientURL").value = skyboxURL; + let skyboxURL = getPropertyInputElement("skybox.url").value; + getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL; updateProperty("ambientLight.ambientURL", skyboxURL); } -// USER DATA FUNCTIONS +/** + * USER DATA FUNCTIONS + */ function clearUserData() { - let elUserData = getPropertyElement("userData"); + let elUserData = getPropertyInputElement("userData"); deleteJSONEditor(); elUserData.value = ""; showUserDataTextArea(); @@ -1831,10 +2305,12 @@ function saveJSONUserData(noUpdate) { } -// MATERIAL DATA FUNCTIONS +/** + * MATERIAL DATA FUNCTIONS + */ function clearMaterialData() { - let elMaterialData = getPropertyElement("materialData"); + let elMaterialData = getPropertyInputElement("materialData"); deleteJSONMaterialEditor(); elMaterialData.value = ""; showMaterialDataTextArea(); @@ -2015,7 +2491,9 @@ function bindAllNonJSONEditorElements() { } -// DROPDOWN FUNCTIONS +/** + * DROPDOWN FUNCTIONS + */ function setDropdownText(dropdown) { let lis = dropdown.parentNode.getElementsByTagName("li"); @@ -2051,7 +2529,9 @@ function setDropdownValue(event) { } -// TEXTAREA / PARENT MATERIAL NAME FUNCTIONS +/** + * TEXTAREA / PARENT MATERIAL NAME FUNCTIONS + */ function setTextareaScrolling(element) { var isScrolling = element.scrollHeight > element.offsetHeight; @@ -2084,17 +2564,17 @@ function loaded() { fieldset.appendChild(elGroup); } else { elGroup = document.createElement('fieldset'); - elGroup.setAttribute("class", "major"); + elGroup.className = "major"; elGroup.setAttribute("id", "properties-" + group.id); elPropertiesList.appendChild(elGroup); } if (group.label !== undefined) { let elLegend = document.createElement('legend'); + elLegend.className = "section-header"; elLegend.innerText = group.label; - elLegend.setAttribute("class", "section-header"); let elSpan = document.createElement('span'); - elSpan.setAttribute("class", ".collapse-icon"); + elSpan.className = ".collapse-icon"; elSpan.innerText = "M"; elLegend.appendChild(elSpan); elGroup.appendChild(elLegend); @@ -2109,8 +2589,8 @@ function loaded() { let elProperty; if (propertyType === "sub-header") { elProperty = document.createElement('legend'); + elProperty.className = "sub-section-header"; elProperty.innerText = propertyData.label; - elProperty.setAttribute("class", "sub-section-header"); } else { elProperty = document.createElement('div'); elProperty.setAttribute("id", "div-" + propertyElementID); @@ -2124,12 +2604,12 @@ function loaded() { let elColumnDiv = document.getElementById(columnDivName); if (!elColumnDiv) { elColumnDiv = document.createElement('div'); - elColumnDiv.setAttribute("class", "two-column"); + elColumnDiv.className = "two-column"; elColumnDiv.setAttribute("id", group.id + "columnDiv"); elGroup.appendChild(elColumnDiv); } elColumn = document.createElement('fieldset'); - elColumn.setAttribute("class", "column"); + elColumn.className = "column"; elColumn.setAttribute("id", columnName); elColumnDiv.appendChild(elColumn); } @@ -2142,53 +2622,79 @@ function loaded() { elLabel.innerText = propertyData.label; elLabel.setAttribute("for", propertyElementID); - properties[propertyID] = { data: propertyData, elementID: propertyElementID, name: propertyName }; - - let property = properties[propertyID]; + let property = { + data: propertyData, + elementID: propertyElementID, + name: propertyName, + elProperty: elProperty + }; + properties[propertyID] = property; switch (propertyType) { case 'string': { - properties[propertyID].el = createStringProperty(property, elProperty, elLabel); + properties[propertyID].elInput = createStringProperty(property, elProperty, elLabel); break; } case 'bool': { - properties[propertyID].el = createBoolProperty(property, elProperty, elLabel); + properties[propertyID].elInput = createBoolProperty(property, elProperty, elLabel); break; } case 'number': { - properties[propertyID].el = createNumberProperty(property, elProperty, elLabel); + properties[propertyID].elInput = createNumberProperty(property, elProperty, elLabel); + break; + } + case 'slider': { + let elSlider = createSliderProperty(property, elProperty, elLabel); + properties[propertyID].elSlider = elSlider[0]; + properties[propertyID].elInput = elSlider[1]; break; } case 'vec3': { - properties[propertyID].el = createVec3Property(property, elProperty, elLabel); + let elVec3 = createVec3Property(property, elProperty, elLabel); + properties[propertyID].elInputX = elVec3[0]; + properties[propertyID].elInputY = elVec3[1]; + properties[propertyID].elInputZ = elVec3[2]; break; } case 'vec2': { - properties[propertyID].el = createVec2Property(property, elProperty, elLabel); + let elVec2 = createVec2Property(property, elProperty, elLabel); + properties[propertyID].elInputX = elVec2[0]; + properties[propertyID].elInputY = elVec2[1]; break; } case 'color': { - properties[propertyID].el = createColorProperty(property, elProperty, elLabel); + let elColor = createColorProperty(property, elProperty, elLabel); + properties[propertyID].elColorPicker = elColor[0]; + properties[propertyID].elInputR = elColor[1]; + properties[propertyID].elInputG = elColor[2]; + properties[propertyID].elInputB = elColor[3]; break; } case 'dropdown': { - properties[propertyID].el = createDropdownProperty(property, propertyID, elProperty, elLabel); + properties[propertyID].elInput = createDropdownProperty(property, propertyID, elProperty, elLabel); break; } case 'textarea': { - properties[propertyID].el = createTextareaProperty(property, elProperty, elLabel); + properties[propertyID].elInput = createTextareaProperty(property, elProperty, elLabel); break; } case 'icon': { - properties[propertyID].el = createIconProperty(property, elProperty, elLabel); + let elIcon = createIconProperty(property, elProperty, elLabel); + properties[propertyID].elSpan = elIcon[0]; + properties[propertyID].elLabel = elIcon[1]; + break; + } + case 'texture': { + let elTexture = createTextureProperty(property, elProperty, elLabel); + properties[propertyID].elImage = elTexture[0]; + properties[propertyID].elInput = elTexture[1]; break; } case 'buttons': { - properties[propertyID].el = createButtonsProperty(property, elProperty, elLabel); + properties[propertyID].elProperty = createButtonsProperty(property, elProperty, elLabel); break; } case 'sub-header': { - properties[propertyID].el = elProperty; break; } default: { @@ -2258,13 +2764,13 @@ function loaded() { showGroupsForType("None"); deleteJSONEditor(); - getPropertyElement("userData").value = ""; + getPropertyInputElement("userData").value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); deleteJSONMaterialEditor(); - getPropertyElement("materialData").value = ""; + getPropertyInputElement("materialData").value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -2299,16 +2805,13 @@ function loaded() { showGroupsForType(type); let typeProperty = properties["type"]; - let elTypeProperty = typeProperty.el; - elTypeProperty[0].innerHTML = typeProperty.data.icons[type]; - elTypeProperty[0].style.display = "inline-block"; - elTypeProperty[1].innerHTML = type + " (" + data.selections.length + ")"; + typeProperty.elSpan.innerHTML = typeProperty.data.icons[type]; + typeProperty.elSpan.style.display = "inline-block"; + typeProperty.elLabel.innerHTML = type + " (" + data.selections.length + ")"; disableProperties(); } else { - selectedEntityProperties = data.selections[0].properties; - - showGroupsForType(selectedEntityProperties.type); + selectedEntityProperties = data.selections[0].properties; if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { @@ -2335,41 +2838,26 @@ function loaded() { } } + showGroupsForType(selectedEntityProperties.type); + for (let propertyID in properties) { let property = properties[propertyID]; - let elProperty = property.el; let propertyData = property.data; let propertyName = property.name; - - // if this is a compound property name (i.e. animation.running) - // then split it by . up to 3 times to find property value - let propertyValue; - let splitPropertyName = propertyName.split('.'); - if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; - let groupProperties = selectedEntityProperties[propertyGroupName]; - if (groupProperties === undefined || groupProperties[subPropertyName] === undefined) { - continue; - } - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - propertyValue = groupProperties[subPropertyName][subSubPropertyName]; - } else { - propertyValue = groupProperties[subPropertyName]; - } - } else { - propertyValue = selectedEntityProperties[propertyName]; - } + let propertyValue = getPropertyValue(propertyName); let isSubProperty = propertyData.subPropertyOf !== undefined; - if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { + if (propertyValue === undefined && !isSubProperty) { continue; } + if (!propertyValue && propertyData.fallbackProperty !== undefined) { + propertyValue = getPropertyValue(propertyData.fallbackProperty); + } + switch (propertyData.type) { case 'string': { - elProperty.value = propertyValue; + property.elInput.value = propertyValue; break; } case 'bool': { @@ -2378,53 +2866,64 @@ function loaded() { let propertyValue = selectedEntityProperties[propertyData.subPropertyOf]; let subProperties = propertyValue.split(","); let subPropertyValue = subProperties.indexOf(propertyID) > -1; - elProperty.checked = inverse ? !subPropertyValue : subPropertyValue; + property.elInput.checked = inverse ? !subPropertyValue : subPropertyValue; } else { - elProperty.checked = inverse ? !propertyValue : propertyValue; + property.elInput.checked = inverse ? !propertyValue : propertyValue; } break; } - case 'number': { - let fixedDecimals = propertyData.fixedDecimals !== undefined ? propertyData.fixedDecimals : 0; - elProperty.value = propertyValue.toFixed(fixedDecimals); + case 'number': + case 'slider': { + let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + let decimals = propertyData.decimals !== undefined ? propertyData.decimals : 0; + property.elInput.value = (propertyValue / multiplier).toFixed(decimals); + if (property.elSlider !== undefined) { + property.elSlider.value = property.elInput.value; + } break; } case 'vec3': case 'vec2': { - // vec2/vec3 are array of 2/3 elInput numbers let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; - elProperty[0].value = (propertyValue.x / multiplier).toFixed(4); - elProperty[1].value = (propertyValue.y / multiplier).toFixed(4); - if (elProperty[2] !== undefined) { - elProperty[2].value = (propertyValue.z / multiplier).toFixed(4); + let decimals = propertyData.decimals !== undefined ? propertyData.decimals : 0; + property.elInputX.value = (propertyValue.x / multiplier).toFixed(decimals); + property.elInputY.value = (propertyValue.y / multiplier).toFixed(decimals); + if (property.elInputZ !== undefined) { + property.elInputZ.value = (propertyValue.z / multiplier).toFixed(decimals); } break; } case 'color': { - // color is array of color picker and 3 elInput numbers - elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + - propertyValue.green + "," + - propertyValue.blue + ")"; - elProperty[1].value = propertyValue.red; - elProperty[2].value = propertyValue.green; - elProperty[3].value = propertyValue.blue; + if (!propertyValue.red && propertyData.fallbackProperty !== undefined) { + propertyValue = getPropertyValue(propertyData.fallbackProperty); + } + property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + + propertyValue.green + "," + + propertyValue.blue + ")"; + property.elInputR.value = propertyValue.red; + property.elInputG.value = propertyValue.green; + property.elInputB.value = propertyValue.blue; break; } case 'dropdown': { - elProperty.value = propertyValue; - setDropdownText(elProperty); + property.elInput.value = propertyValue; + setDropdownText(property.elInput); break; } case 'textarea': { - elProperty.value = propertyValue; - setTextareaScrolling(elProperty); + property.elInput.value = propertyValue; + setTextareaScrolling(property.elInput); break; } case 'icon': { - // icon is array of elSpan (icon glyph) and elLabel - elProperty[0].innerHTML = propertyData.icons[propertyValue]; - elProperty[0].style.display = "inline-block"; - elProperty[1].innerHTML = propertyValue; + property.elSpan.innerHTML = propertyData.icons[propertyValue]; + property.elSpan.style.display = "inline-block"; + property.elLabel.innerHTML = propertyValue; + break; + } + case 'texture': { + property.elInput.value = propertyValue; + property.elInput.imageLoad(property.elInput.value); break; } } @@ -2439,10 +2938,10 @@ function loaded() { } } - let elGrabbable = getPropertyElement("grabbable"); - let elTriggerable = getPropertyElement("triggerable"); - let elIgnoreIK = getPropertyElement("ignoreIK"); - elGrabbable.checked = getPropertyElement("dynamic").checked; + let elGrabbable = getPropertyInputElement("grabbable"); + let elTriggerable = getPropertyInputElement("triggerable"); + let elIgnoreIK = getPropertyInputElement("ignoreIK"); + elGrabbable.checked = getPropertyInputElement("dynamic").checked; elTriggerable.checked = false; elIgnoreIK.checked = true; let grabbablesSet = false; @@ -2481,11 +2980,11 @@ function loaded() { if (selectedEntityProperties.type === "Image") { let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; - getPropertyElement("image").value = imageLink; + getPropertyInputElement("image").value = imageLink; } else if (selectedEntityProperties.type === "Material") { - let elParentMaterialNameString = getPropertyElement("materialNameToReplace"); - let elParentMaterialNameNumber = getPropertyElement("submeshToReplace"); - let elParentMaterialNameCheckbox = getPropertyElement("selectSubmesh"); + let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); let parentMaterialName = selectedEntityProperties.parentMaterialName; if (parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { elParentMaterialNameString.value = parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); @@ -2504,7 +3003,7 @@ function loaded() { } catch (e) { // normal text deleteJSONEditor(); - getPropertyElement("userData").value = selectedEntityProperties.userData; + getPropertyInputElement("userData").value = selectedEntityProperties.userData; showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); @@ -2527,7 +3026,7 @@ function loaded() { } catch (e) { // normal text deleteJSONMaterialEditor(); - getPropertyElement("materialData").value = selectedEntityProperties.materialData; + getPropertyInputElement("materialData").value = selectedEntityProperties.materialData; showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); @@ -2546,7 +3045,7 @@ function loaded() { if (selectedEntityProperties.locked) { disableProperties(); - getPropertyElement("locked").removeAttribute('disabled'); + getPropertyInputElement("locked").removeAttribute('disabled'); } else { enableProperties(); disableSaveUserDataButton(); @@ -2564,10 +3063,10 @@ function loaded() { // Server Script Status let serverScriptProperty = properties["serverScripts"]; - let elServerScript = serverScriptProperty.el; + let elServerScript = serverScriptProperty.elInput; let serverScriptElementID = serverScriptProperty.elementID; let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "property"); + elDiv.className = "property"; let elLabel = document.createElement('label'); elLabel.setAttribute("for", serverScriptElementID + "-status"); elLabel.innerText = "Server Script Status"; @@ -2579,19 +3078,19 @@ function loaded() { // Server Script Error elDiv = document.createElement('div'); - elDiv.setAttribute("class", "property"); + elDiv.className = "property"; let elServerScriptError = document.createElement('textarea'); elServerScriptError.setAttribute("id", serverScriptElementID + "-error"); elDiv.appendChild(elServerScriptError); elServerScript.parentNode.appendChild(elDiv); - let elScript = getPropertyElement("script"); - elScript.parentNode.setAttribute("class", "property url refresh"); - elServerScript.parentNode.setAttribute("class", "property url refresh"); + let elScript = getPropertyInputElement("script"); + elScript.parentNode.className = "property url refresh"; + elServerScript.parentNode.className = "property url refresh"; // User Data let userDataProperty = properties["userData"]; - let elUserData = userDataProperty.el; + let elUserData = userDataProperty.elInput; let userDataElementID = userDataProperty.elementID; elDiv = elUserData.parentNode; let elStaticUserData = document.createElement('div'); @@ -2607,7 +3106,7 @@ function loaded() { // Material Data let materialDataProperty = properties["materialData"]; - let elMaterialData = materialDataProperty.el; + let elMaterialData = materialDataProperty.elInput; let materialDataElementID = materialDataProperty.elementID; elDiv = elMaterialData.parentNode; let elStaticMaterialData = document.createElement('div'); @@ -2622,9 +3121,9 @@ function loaded() { elDiv.insertBefore(elMaterialDataEditor, elMaterialData); // User Data Fields - let elGrabbable = getPropertyElement("grabbable"); - let elTriggerable = getPropertyElement("triggerable"); - let elIgnoreIK = getPropertyElement("ignoreIK"); + let elGrabbable = getPropertyInputElement("grabbable"); + let elTriggerable = getPropertyInputElement("triggerable"); + let elIgnoreIK = getPropertyInputElement("ignoreIK"); elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); @@ -2636,9 +3135,9 @@ function loaded() { }); // Special Property Callbacks - let elParentMaterialNameString = getPropertyElement("materialNameToReplace"); - let elParentMaterialNameNumber = getPropertyElement("submeshToReplace"); - let elParentMaterialNameCheckbox = getPropertyElement("selectSubmesh"); + let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); @@ -2655,7 +3154,7 @@ function loaded() { } }); - getPropertyElement("image").addEventListener('change', createImageURLUpdateFunction('textures')); + getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures')); // Collapsible sections let elCollapsible = document.getElementsByClassName("section-header"); @@ -2704,7 +3203,8 @@ function loaded() { // let elDropdowns = document.getElementsByTagName("select"); for (let dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { - let options = elDropdowns[dropDownIndex].getElementsByTagName("option"); + let elDropdown = elDropdowns[dropDownIndex]; + let options = elDropdown.getElementsByTagName("option"); let selectedOption = 0; for (let optionIndex = 0; optionIndex < options.length; ++optionIndex) { if (options[optionIndex].getAttribute("selected") === "selected") { @@ -2712,14 +3212,14 @@ function loaded() { // TODO: Shouldn't there be a break here? } } - let div = elDropdowns[dropDownIndex].parentNode; + let div = elDropdown.parentNode; let dl = document.createElement("dl"); div.appendChild(dl); let dt = document.createElement("dt"); - dt.name = elDropdowns[dropDownIndex].name; - dt.id = elDropdowns[dropDownIndex].id; + dt.name = elDropdown.name; + dt.id = elDropdown.id; dt.addEventListener("click", toggleDropdown, true); dl.appendChild(dt); @@ -2746,9 +3246,9 @@ function loaded() { ul.appendChild(li); } - let propertyID = elDropdowns[dropDownIndex].getAttribute("propertyID"); + let propertyID = elDropdown.getAttribute("propertyID"); let property = properties[propertyID]; - property.el = dt; + property.elInput = dt; dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name)); } diff --git a/scripts/system/html/js/underscore-min.js b/scripts/system/html/js/underscore-min.js new file mode 100644 index 0000000000..f01025b7bc --- /dev/null +++ b/scripts/system/html/js/underscore-min.js @@ -0,0 +1,6 @@ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); +//# sourceMappingURL=underscore-min.map \ No newline at end of file From 078baa86e4fbe56cd095e2ea46c59abe0dc34e05 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 16 Oct 2018 13:16:01 -0700 Subject: [PATCH 063/131] Sitting abort post transit animation --- interface/src/avatar/AvatarManager.cpp | 2 +- libraries/animation/src/Rig.h | 1 + .../src/avatars-renderer/Avatar.cpp | 13 +++++++++++-- .../avatars-renderer/src/avatars-renderer/Avatar.h | 9 +++++++++ .../controllers/controllerModules/teleport.js | 2 ++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 373ae9980a..c3f6579e90 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -162,7 +162,7 @@ void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { case AvatarTransit::Status::END_TRANSIT: qDebug() << "END_TRANSIT"; _myAvatar->overrideNetworkAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, - endAnimation._firstFrame + endAnimation._frameCount); + endAnimation._firstFrame + endAnimation._frameCount); break; case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e1012df029..37d1ec1dd3 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -116,6 +116,7 @@ public: void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreAnimation(); void restoreNetworkAnimation(); + QStringList getAnimationRoles() const; void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreRoleAnimation(const QString& role); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index b2747277c9..b43e1c23f6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -132,9 +132,12 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av const float SETTLE_ABORT_DISTANCE = 0.1f; float scaledSettleAbortDistance = SETTLE_ABORT_DISTANCE * _scale; - if (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT) { + bool abortPostTransit = (_status == Status::POST_TRANSIT && _purpose == Purpose::SIT) || + (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT); + if (abortPostTransit) { reset(); _status = Status::ENDED; + _purpose = Purpose::UNDEFINED; } return _status; } @@ -203,6 +206,7 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { _currentPosition = _endPosition; if (nextTime >= _totalTime) { _isActive = false; + _purpose = Purpose::UNDEFINED; status = Status::ENDED; } else if (_currentTime < _totalTime - _postTransitTime) { status = Status::END_TRANSIT; @@ -2000,7 +2004,12 @@ AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& av void Avatar::setTransitScale(float scale) { std::lock_guard lock(_transitLock); - return _transit.setScale(scale); + _transit.setScale(scale); +} + +void Avatar::setTransitPurpose(int purpose) { + std::lock_guard lock(_transitLock); + _transit.setPurpose(static_cast(purpose)); } void Avatar::overrideNextPacketPositionData(const glm::vec3& position) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index e4fd667c1f..d375909609 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -71,6 +71,12 @@ public: EASE_IN_OUT }; + enum Purpose { + UNDEFINED = 0, + TELEPORT, + SIT + }; + struct TransitAnimation { TransitAnimation(){}; TransitAnimation(const QString& animationUrl, int firstFrame, int frameCount) : @@ -99,6 +105,7 @@ public: glm::vec3 getCurrentPosition() { return _currentPosition; } glm::vec3 getEndPosition() { return _endPosition; } void setScale(float scale) { _scale = scale; } + void setPurpose(const Purpose& purpose) { _purpose = purpose; } void reset(); private: @@ -123,6 +130,7 @@ private: EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; float _scale { 1.0f }; + Purpose _purpose { Purpose::UNDEFINED }; }; class Avatar : public AvatarData, public scriptable::ModelProvider { @@ -449,6 +457,7 @@ public: AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); void setTransitScale(float scale); + Q_INVOKABLE void setTransitPurpose(int purpose); void overrideNextPacketPositionData(const glm::vec3& position); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index bf5022cdaf..d31f207b19 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -781,11 +781,13 @@ Script.include("/~/system/libraries/controllers.js"); if (target === TARGET.NONE || target === TARGET.INVALID) { // Do nothing } else if (target === TARGET.SEAT) { + MyAvatar.setTransitPurpose(2); Entities.callEntityMethod(result.objectID, 'sit'); } else if (target === TARGET.SURFACE || target === TARGET.DISCREPANCY) { var offset = getAvatarFootOffset(); result.intersection.y += offset; var shouldLandSafe = target === TARGET.DISCREPANCY; + MyAvatar.setTransitPurpose(1); MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false, shouldLandSafe); HMD.centerUI(); MyAvatar.centerBody(); From 0b77eb5e9ce7e5ebbab42f0db90dece9764a0e82 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 16 Oct 2018 14:25:38 -0700 Subject: [PATCH 064/131] Control UUID inclusion in avatar data with new param Increase estimate of avatars to be sent; remove start/end segment in avatar identity writes. --- .../src/avatars/AvatarMixerSlave.cpp | 5 ++-- libraries/avatars/src/AvatarData.cpp | 25 +++++++++++-------- libraries/avatars/src/AvatarData.h | 1 + 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 0e0802bf38..75d36783a5 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -72,9 +72,7 @@ int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarM if (destinationNode.getType() == NodeType::Agent && !destinationNode.isUpstream()) { QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious - packetList.startSegment(); packetList.write(individualData); - packetList.endSegment(); _stats.numIdentityPackets++; return individualData.size(); } else { @@ -248,7 +246,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) distribution.reset(); // Estimate number to sort on number sent last frame (with min. of 20). - const int numToSendEst = std::max(nodeData->getNumAvatarsSentLastFrame() * 2, 20); + const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20); // reset the number of sent avatars nodeData->resetNumAvatarsSentLastFrame(); @@ -455,6 +453,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const bool distanceAdjust = true; const bool dropFaceTracking = false; AvatarDataPacket::SendStatus sendStatus; + sendStatus.sendUUID = true; do { auto startSerialize = chrono::high_resolution_clock::now(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 422db2f2ed..3ac8b32d48 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -229,8 +229,7 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro AvatarDataPacket::SendStatus sendStatus; auto avatarByteArray = AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), sendStatus, dropFaceTracking, false, glm::vec3(0), nullptr, 0, &_outboundDataRate); - // Strip UUID - return avatarByteArray.right(avatarByteArray.size() - NUM_BYTES_RFC4122_UUID); + return avatarByteArray; } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, @@ -255,7 +254,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (dataDetail == NoData) { sendStatus.itemFlags = wantedFlags; - QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof wantedFlags); + QByteArray avatarDataByteArray; + if (sendStatus.sendUUID) { + avatarDataByteArray.append(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID); + } + avatarDataByteArray.append((char*) &wantedFlags, sizeof wantedFlags); return avatarDataByteArray; } @@ -392,13 +395,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const unsigned char* const startPosition = destinationBuffer; const unsigned char* const packetEnd = destinationBuffer + maxDataSize; - // Packets always have UUID. - memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); - destinationBuffer += NUM_BYTES_RFC4122_UUID; - - unsigned char * packetFlagsLocation = destinationBuffer; - destinationBuffer += sizeof(wantedFlags); - #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); @@ -409,6 +405,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent && (packetEnd - destinationBuffer) >= (ptrdiff_t)(space) \ && (includedFlags |= AvatarDataPacket::flag)) + if (sendStatus.sendUUID) { + memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); + destinationBuffer += NUM_BYTES_RFC4122_UUID; + } + + unsigned char * packetFlagsLocation = destinationBuffer; + destinationBuffer += sizeof(wantedFlags); + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { auto startSection = destinationBuffer; if (_overrideGlobalPosition) { @@ -417,7 +421,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent AVATAR_MEMCPY(_globalPosition); } - int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index aa3d3d328c..1f5b7093b9 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -302,6 +302,7 @@ namespace AvatarDataPacket { struct SendStatus { HasFlags itemFlags { 0 }; + bool sendUUID { false }; int rotationsSent { 0 }; // ie: index of next unsent joint int translationsSent { 0 }; operator bool() { return itemFlags == 0; } From a2aa63e9ed8ee169cef523c22de24f40abd52a9f Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 16 Oct 2018 14:28:52 -0700 Subject: [PATCH 065/131] adjust no entities message --- scripts/system/html/entityList.html | 2 +- scripts/system/html/js/entityList.js | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 06c2be8e73..f9b6fbd59b 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -108,7 +108,7 @@
- No entities found in view within a 100 meter radius. Try moving to a different location and refreshing. + There are no entities to display. Please check your filters or create an entity to begin.
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0f3f27a547..2b720f614d 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -142,8 +142,6 @@ function loaded() { elInfoToggleGlyph = elInfoToggle.firstChild; elFooter = document.getElementById("footer-text"); elNoEntitiesMessage = document.getElementById("no-entities"); - elNoEntitiesInView = document.getElementById("no-entities-in-view"); - elNoEntitiesRadius = document.getElementById("no-entities-radius"); document.body.onclick = onBodyClick; document.getElementById("entity-name").onclick = function() { @@ -230,8 +228,6 @@ function loaded() { elFilterTypeCheckboxes.appendChild(elDiv); } - elNoEntitiesInView.style.display = "none"; - entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT); @@ -639,10 +635,8 @@ function loaded() { isFilterInView = !isFilterInView; if (isFilterInView) { elFilterInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); - elNoEntitiesInView.style.display = "inline"; } else { elFilterInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); - elNoEntitiesInView.style.display = "none"; } EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); refreshEntities(); @@ -650,8 +644,6 @@ function loaded() { function onRadiusChange() { elFilterRadius.value = Math.max(elFilterRadius.value, 0); - elNoEntitiesRadius.firstChild.nodeValue = elFilterRadius.value; - elNoEntitiesMessage.style.display = "none"; EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); } @@ -765,7 +757,6 @@ function loaded() { refreshEntities(); }); - augmentSpinButtons(); // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked From 9cd3c35cc6858473c012e4f380b7d92f9a4381e1 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 17 Oct 2018 18:18:51 -0700 Subject: [PATCH 066/131] style changes and fix hidden top row --- scripts/system/html/css/edit-style.css | 58 ++++++++++++++------------ scripts/system/html/entityList.html | 28 ++++++------- scripts/system/html/js/entityList.js | 7 +++- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 088b0952ae..1b0094cfb7 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -273,7 +273,6 @@ input[type="number"] { height: 28px; width: 124px; } - input[type=number] { padding-right: 3px; } @@ -1085,66 +1084,68 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { bottom: 0; } +#filter-type-selectBox select { + border-radius: 14.5px; +} #filter-type-checkboxes { position: absolute; z-index: 2; - top: 28px; + top: 48px; display: none; border: none; } #filter-type-checkboxes div { position: relative; - height: 25px; + height: 22px; } #filter-type-checkboxes span { position: relative; top: 3px; font-family: hifi-glyphs; - font-size: 16px; - color: #404040; - padding-left: 12px; - padding-right: 12px; + font-size: 13px; + color: #000000; + padding-left: 6px; + padding-right: 4px; } #filter-type-checkboxes label { - position: relative; - top: -13px; - z-index: 3; + position: absolute; + top: -20px; + z-index: 2; display: block; font-family: FiraSans-SemiBold; - color: #404040; + font-size: 11px; + color: #000000; background-color: #afafaf; + width: 200px; + height: 22px; padding-top: 1px; - padding-right: 12px; - height: 24px; } #filter-type-checkboxes label:hover { background-color: #1e90ff; } -#filter-type-checkboxes input[type=checkbox] { - position: relative; - top: 6px; - right: -10px; - z-index: 4; - display: block; -} #filter-type-checkboxes input[type=checkbox] + label { - background-image: none; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; } #filter-type-checkboxes input[type=checkbox]:checked + label { - background-image: none; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; } #filter-type-checkboxes input[type=checkbox]:hover + label { background-color: #1e90ff; } #filter-search-and-icon { - position: absolute; - left: 120px; - width: calc(100% - 300px); + position: relative; + left: 118px; + width: calc(100% - 126px); } #filter-in-view { position: absolute; + top: 0px; right: 126px; } @@ -1152,13 +1153,18 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; float: right; margin-right: -168px; - top: -17px; + top: -45px; } #filter-radius-and-unit label { margin-left: 2px; } #filter-radius-and-unit input { width: 120px; + border-radius: 14.5px; + font-style: italic; +} +#filter-radius-and-unit input[type=number]::-webkit-inner-spin-button { + display: none; } #entity-table-scroll { diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 8371e2cbd7..dc022c9ab9 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -30,20 +30,20 @@
-
-
- -
-
-
- -
-
-
- Y -
+
+
+ +
+
+
+ +
+
+
+ Y +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 400cdc7dce..66ad08e27c 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -23,6 +23,7 @@ const FILTER_IN_VIEW_ATTRIBUTE = "pressed"; const WINDOW_NONVARIABLE_HEIGHT = 227; const NUM_COLUMNS = 12; const EMPTY_ENTITY_ID = "0"; +const MAX_LENGTH_RADIUS = 9; const DELETE = 46; // Key code for the delete key. const KEY_P = 80; // Key code for letter p used for Parenting hotkey. @@ -203,7 +204,11 @@ function loaded() { elFilterSearch.onsearch = refreshEntityList; elFilterInView.onclick = toggleFilterInView; elFilterRadius.onchange = onRadiusChange; - elInfoToggle.onclick = toggleInfo; + elFilterRadius.oninput = function(event) { + if (event.target.value.length > MAX_LENGTH_RADIUS) { + event.target.value = event.target.value.slice(0, MAX_LENGTH_RADIUS); + } + } // create filter type dropdown checkboxes with label and icon for each type elFilterTypeSelectBox.onclick = toggleTypeDropdown; From 9e35af0c4a3ff246d3fe32410c9b67952f22e265 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 17 Oct 2018 18:24:09 -0700 Subject: [PATCH 067/131] remove space --- scripts/system/html/entityList.html | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index dc022c9ab9..434f8a5f87 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -100,7 +100,6 @@ -
There are no entities to display. Please check your filters or create an entity to begin.
From 684f7d5689b78def61ca3e9f6c035297669e6fe1 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 3 Oct 2018 19:35:03 +0200 Subject: [PATCH 068/131] move export selection button to bottom of the list --- scripts/system/html/css/edit-style.css | 9 +++++++++ scripts/system/html/entityList.html | 13 ++++++++++--- scripts/system/html/js/entityList.js | 18 ++++++------------ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8d334609a6..fa64960487 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1063,6 +1063,15 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; /* New positioning context. */ } +#footer-text { + float: right; + margin-right: 0; +} + +#entity-list-footer { + padding-top: 38px; +} + #search-area { padding-right: 168px; padding-bottom: 24px; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 7eed78ecf3..cc7afe3bdb 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -1,4 +1,4 @@ -
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index fcc457eb7f..060ed1f67a 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1059,13 +1059,13 @@ const GROUPS = [ { label: "Grabbable", type: "bool", - propertyID: "grabbable", + propertyID: "grab.grabbable", column: 1, }, { label: "Triggerable", type: "bool", - propertyID: "triggerable", + propertyID: "grab.triggerable", column: 2, }, { @@ -1075,9 +1075,9 @@ const GROUPS = [ column: 1, }, { - label: "Ignore inverse kinematics", + label: "Follow Controller", type: "bool", - propertyID: "ignoreIK", + propertyID: "grab.grabFollowsController", column: 2, }, { @@ -2767,7 +2767,7 @@ function loaded() { showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); - + deleteJSONMaterialEditor(); getPropertyInputElement("materialData").value = ""; showMaterialDataTextArea(); @@ -2937,46 +2937,6 @@ function loaded() { } } - let elGrabbable = getPropertyInputElement("grabbable"); - let elTriggerable = getPropertyInputElement("triggerable"); - let elIgnoreIK = getPropertyInputElement("ignoreIK"); - elGrabbable.checked = getPropertyInputElement("dynamic").checked; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - let grabbablesSet = false; - let parsedUserData = {}; - try { - parsedUserData = JSON.parse(selectedEntityProperties.userData); - if ("grabbableKey" in parsedUserData) { - grabbablesSet = true; - let grabbableData = parsedUserData.grabbableKey; - if ("grabbable" in grabbableData) { - elGrabbable.checked = grabbableData.grabbable; - } else { - elGrabbable.checked = true; - } - if ("triggerable" in grabbableData) { - elTriggerable.checked = grabbableData.triggerable; - } else if ("wantsTrigger" in grabbableData) { - elTriggerable.checked = grabbableData.wantsTrigger; - } else { - elTriggerable.checked = false; - } - if ("ignoreIK" in grabbableData) { - elIgnoreIK.checked = grabbableData.ignoreIK; - } else { - elIgnoreIK.checked = true; - } - } - } catch (e) { - // TODO: What should go here? - } - if (!grabbablesSet) { - elGrabbable.checked = true; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - } - if (selectedEntityProperties.type === "Image") { let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; getPropertyInputElement("image").value = imageLink; @@ -3119,20 +3079,6 @@ function loaded() { elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData); - // User Data Fields - let elGrabbable = getPropertyInputElement("grabbable"); - let elTriggerable = getPropertyInputElement("triggerable"); - let elIgnoreIK = getPropertyInputElement("ignoreIK"); - elGrabbable.addEventListener('change', function() { - userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); - }); - elTriggerable.addEventListener('change', function() { - userDataChanger("grabbableKey", "triggerable", elTriggerable, elUserData, false, ['wantsTrigger']); - }); - elIgnoreIK.addEventListener('change', function() { - userDataChanger("grabbableKey", "ignoreIK", elIgnoreIK, elUserData, true); - }); - // Special Property Callbacks let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); From 11add70ef2b31986eb089eef38fa39c7d417e8d4 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 18 Oct 2018 14:58:51 -0700 Subject: [PATCH 073/131] style tweaks --- scripts/system/html/css/edit-style.css | 109 +++++++++++---------- scripts/system/html/js/entityProperties.js | 6 +- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 30dc959ef3..b50b5233f5 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -473,7 +473,7 @@ input[type=checkbox]:checked + label:hover { #properties-list fieldset { position: relative; /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ - margin: 21px -21px 0 -21px; + margin: 0 -21px 21px -21px; padding: 0.1px 21px 0 21px; border: none; border-top: 1px rgb(90,90,90) solid; @@ -500,11 +500,6 @@ input[type=checkbox]:checked + label:hover { box-shadow: none; } -#properties-list > fieldset#properties-header { - margin-top: 0; - padding-bottom: 0; -} - #properties-list > fieldset > legend { position: relative; display: table; @@ -523,7 +518,7 @@ input[type=checkbox]:checked + label:hover { box-shadow: 0 -1px 0 rgb(37,37,37), 0 4px 4px 0 rgba(0,0,0,0.75); } -div.section-header, .sub-section-header, hr { +div.section-header, hr { display: table; width: 100%; margin: 21px -21px 0 -21px; @@ -536,17 +531,6 @@ div.section-header, .sub-section-header, hr { outline: none; } - - -.column .sub-section-header { - background-image: none; - padding-top: 0; -} - -.sub-section-header, .no-collapse, hr { - background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; -} - div.section-header:first-child { margin-top: -2px; padding-top: 0; @@ -554,10 +538,6 @@ div.section-header:first-child { height: auto; } -.sub-section-header { - margin-bottom: -10px; -} - #properties-list > fieldset > legend span, .section-header span { font-family: HiFi-Glyphs; font-size: 30px; @@ -646,7 +626,7 @@ hr { } .property.range label{ - display: block; + padding-bottom: 3px; } .property.range input[type=number]{ margin-left: 0.8rem; @@ -956,12 +936,11 @@ div.refresh input[type="button"] { } #properties-list fieldset .two-column { - padding-top: 21px; + padding-top: 10px; display: flex; } -#properties-list .two-column fieldset { - /*display: table-cell;*/ +#properties-list .two-column fieldset { width: 50%; margin: 0; padding: 0; @@ -969,19 +948,27 @@ div.refresh input[type="button"] { box-shadow: none; } +#properties-list .two-column .column { + position: relative; + top: -10px; +} + #properties-list .two-column fieldset legend { - display: table; width: 100%; margin: 21px -21px 0 -21px; - padding: 0 0 0 21px; + padding: 6px 0 0 21px; font-family: Raleway-Regular; font-size: 12px; color: #afafaf; - height: 20px; + height: 10px; text-transform: uppercase; outline: none; } +#properties-list .two-column + .property { + margin-top: 6px; +} + fieldset .checkbox-sub-props { margin-top: 0; } @@ -1321,16 +1308,11 @@ th#entity-hasScript { border-right: 1px solid #575757; } - -#no-entities { - display: none; - position: absolute; - top: 80px; - padding: 12px; - font-family: FiraSans-SemiBold; - font-size: 15px; - font-style: italic; - color: #afafaf; +#properties-base { + border-top: none !important; + box-shadow: none !important; + margin-bottom: 5px !important; + top: -15px; } #properties-base #property-type-icon { @@ -1351,14 +1333,23 @@ th#entity-hasScript { #properties-base #div-property-locked { position: absolute; - top: 0px; + top: 6px; right: 140px; + display: table-cell; + vertical-align: middle; } #properties-base #div-property-visible { position: absolute; - top: 20px; + top: 26px; right: 20px; + display: table-cell; + vertical-align: middle; +} + +#properties-base #div-property-locked label, +#properties-base #div-property-visible label { + background-position-y: 1px; } #properties-base .checkbox label span { @@ -1367,18 +1358,13 @@ th#entity-hasScript { padding-right: 6px; vertical-align: top; position: relative; - top: -2px; + top: -4px; } #properties-base input[type=checkbox]:checked + label span { color: #ffffff; } -#properties-base + hr { - margin-top: 12px; -} - - #id label { width: 24px; } @@ -1409,22 +1395,26 @@ input#property-scale-button-reset { z-index: 99; position: absolute; width: 96%; - padding-left:1%; - margin-top:5px; - margin-bottom:10px; + padding-left: 1%; + margin-top: 5px; + margin-bottom: 10px; background-color: #2e2e2e; } #property-userData-saved, #property-materialData-saved { - margin-top:5px; - font-size:16px; - display:none; + margin-top: 5px; + font-size: 16px; + display: none; } +#div-property-serverScripts-status label { + position: relative; + top: 8px; +} #property-serverScripts-status { position: relative; - top: -3px; + top: 5px; right: -20px; } @@ -1439,6 +1429,17 @@ input#property-scale-button-reset { flex-direction: column; } +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + input[type=button]#export { height: 38px; width: 180px; diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 060ed1f67a..1a52cf939d 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3024,13 +3024,15 @@ function loaded() { let serverScriptProperty = properties["serverScripts"]; let elServerScript = serverScriptProperty.elInput; let serverScriptElementID = serverScriptProperty.elementID; + let serverScriptStatusElementID = serverScriptElementID + "-status"; let elDiv = document.createElement('div'); elDiv.className = "property"; + elDiv.setAttribute("id", "div-" + serverScriptStatusElementID); let elLabel = document.createElement('label'); - elLabel.setAttribute("for", serverScriptElementID + "-status"); + elLabel.setAttribute("for", serverScriptStatusElementID); elLabel.innerText = "Server Script Status"; let elServerScriptStatus = document.createElement('span'); - elServerScriptStatus.setAttribute("id", serverScriptElementID + "-status"); + elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); elDiv.appendChild(elLabel); elDiv.appendChild(elServerScriptStatus); elServerScript.parentNode.appendChild(elDiv); From 45a92fd53745bf961e028cbf6dd9342ca8c1485b Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 18 Oct 2018 15:12:38 -0700 Subject: [PATCH 074/131] style tweaks --- scripts/system/html/css/edit-style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index b50b5233f5..8205fcd340 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -956,7 +956,7 @@ div.refresh input[type="button"] { #properties-list .two-column fieldset legend { width: 100%; margin: 21px -21px 0 -21px; - padding: 6px 0 0 21px; + padding: 16px 0 0 21px; font-family: Raleway-Regular; font-size: 12px; color: #afafaf; From a4adf2c71678853b4edd3737177585b3e1a5a9ab Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 18 Oct 2018 17:39:24 -0700 Subject: [PATCH 075/131] Fix name of Particles to ParticleEffect for default properties in edit.js --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 96d3d763b0..71b4b2c54d 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -419,7 +419,7 @@ const DEFAULT_ENTITY_PROPERTIES = { sourceUrl: "https://highfidelity.com/", dpi: 30, }, - Particles: { + ParticleEffect: { lifespan: 1.5, maxParticles: 10, textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", From 51d767ac2bd9989f67f18a53e029f6e6796985a2 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 18 Oct 2018 17:46:32 -0700 Subject: [PATCH 076/131] Reasonable defaults for position and rotation filtering for vive tracked joints. --- interface/resources/controllers/vive.json | 33 +++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 42be6f3f04..24b1587691 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -53,15 +53,32 @@ { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, { "from": "Vive.RightHand", "to": "Standard.RightHand" }, - - { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot" }, - { "from": "Vive.RightFoot", "to" : "Standard.RightFoot" }, - { "from": "Vive.Hips", "to" : "Standard.Hips" }, - { "from": "Vive.Spine2", "to" : "Standard.Spine2" }, - { "from": "Vive.Head", "to" : "Standard.Head" }, - { "from": "Vive.RightArm", "to" : "Standard.RightArm" }, - { "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }, + + { + "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.RightFoot", "to" : "Standard.RightFoot", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.Hips", "to" : "Standard.Hips", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.Spine2", "to" : "Standard.Spine2", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.RightArm", "to" : "Standard.RightArm", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.LeftArm", "to" : "Standard.LeftArm", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, { "from": "Vive.TrackedObject00", "to" : "Standard.TrackedObject00" }, { "from": "Vive.TrackedObject01", "to" : "Standard.TrackedObject01" }, From bb5c042f16bc0dd86785861af429db54925a572d Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 18 Oct 2018 17:51:20 -0700 Subject: [PATCH 077/131] Blend network animation --- .../resources/avatar/network-animation.json | 140 ++++++++++++++++++ interface/src/avatar/AvatarManager.cpp | 37 +---- interface/src/avatar/MyAvatar.cpp | 17 --- interface/src/avatar/MyAvatar.h | 2 - libraries/animation/src/AnimClip.h | 2 + libraries/animation/src/Rig.cpp | 117 ++++++++------- libraries/animation/src/Rig.h | 23 ++- .../src/avatars-renderer/Avatar.cpp | 24 +-- .../src/avatars-renderer/Avatar.h | 21 --- .../controllers/controllerModules/teleport.js | 2 - 10 files changed, 241 insertions(+), 144 deletions(-) create mode 100644 interface/resources/avatar/network-animation.json diff --git a/interface/resources/avatar/network-animation.json b/interface/resources/avatar/network-animation.json new file mode 100644 index 0000000000..0ba4ea465c --- /dev/null +++ b/interface/resources/avatar/network-animation.json @@ -0,0 +1,140 @@ +{ + "version": "1.1", + "root": { + "id": "userAnimStateMachine", + "type": "stateMachine", + "data": { + "currentState": "idleAnim", + "states": [ + { + "id": "idleAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "postTransitAnim", "state": "postTransitAnim" }, + { "var": "preTransitAnim", "state": "preTransitAnim" } + ] + }, + { + "id": "preTransitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "transitAnim", "state": "transitAnim" } + ] + }, + { + "id": "transitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "preTransitAnim", "state": "preTransitAnim" }, + { "var": "postTransitAnim", "state": "postTransitAnim" } + ] + }, + { + "id": "postTransitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "transitAnim", "state": "transitAnim" }, + { "var": "idleAnim", "state": "idleAnim" } + ] + }, + { + "id": "userAnimA", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "userAnimB", "state": "userAnimB" } + ] + }, + { + "id": "userAnimB", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "userAnimA", "state": "userAnimA" } + ] + } + ] + }, + "children": [ + { + "id": "idleAnim", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "preTransitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 0.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "transitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 11.0, + "endFrame": 11.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "postTransitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 22.0, + "endFrame": 49.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "userAnimA", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "userAnimB", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } +} diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c3f6579e90..dd56c03d26 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -83,18 +83,10 @@ AvatarManager::AvatarManager(QObject* parent) : const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; // Based on testing - const QString START_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; - const QString MIDDLE_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; - const QString END_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; - _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; _transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; _transitConfig._isDistanceBased = true; - - _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 10); - _transitConfig._middleTransitAnimation = AvatarTransit::TransitAnimation(MIDDLE_ANIMATION_URL, 15, 0); - _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 22, 27); } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -143,30 +135,22 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { } void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { - auto startAnimation = _transitConfig._startTransitAnimation; - auto middleAnimation = _transitConfig._middleTransitAnimation; - auto endAnimation = _transitConfig._endTransitAnimation; - - const float REFERENCE_FPS = 30.0f; switch (status) { case AvatarTransit::Status::STARTED: qDebug() << "START_FRAME"; - _myAvatar->overrideNetworkAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, - startAnimation._firstFrame + startAnimation._frameCount); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("preTransitAnim"); break; case AvatarTransit::Status::START_TRANSIT: qDebug() << "START_TRANSIT"; - _myAvatar->overrideNetworkAnimation(middleAnimation._animationUrl, REFERENCE_FPS, false, middleAnimation._firstFrame, - middleAnimation._firstFrame + middleAnimation._frameCount); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("transitAnim"); break; case AvatarTransit::Status::END_TRANSIT: qDebug() << "END_TRANSIT"; - _myAvatar->overrideNetworkAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, - endAnimation._firstFrame + endAnimation._frameCount); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("postTransitAnim"); break; case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; - _myAvatar->restoreNetworkAnimation(); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("idleAnim"); break; case AvatarTransit::Status::PRE_TRANSIT: break; @@ -188,9 +172,6 @@ void AvatarManager::updateMyAvatar(float deltaTime) { AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); handleTransitAnimations(status); - bool sendFirstTransitPacket = (status == AvatarTransit::Status::STARTED); - bool sendCurrentTransitPacket = (status != AvatarTransit::Status::IDLE && (status != AvatarTransit::Status::POST_TRANSIT || status != AvatarTransit::Status::ENDED)); - _myAvatar->update(deltaTime); render::Transaction transaction; _myAvatar->updateRenderItem(transaction); @@ -199,19 +180,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (sendFirstTransitPacket || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused)) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server - PerformanceTimer perfTimer("send"); - if (sendFirstTransitPacket) { - _myAvatar->overrideNextPacketPositionData(_myAvatar->getTransit()->getEndPosition()); - } else if (sendCurrentTransitPacket) { - _myAvatar->overrideNextPacketPositionData(_myAvatar->getTransit()->getCurrentPosition()); - } + PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); } - } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 152215e717..b347963cf1 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1124,15 +1124,6 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame); } -void MyAvatar::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "overrideNetworkAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), - Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); - return; - } - _skeletonModel->getRig().overrideNetworkAnimation(url, fps, loop, firstFrame, lastFrame); -} - void MyAvatar::restoreAnimation() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "restoreAnimation"); @@ -1141,14 +1132,6 @@ void MyAvatar::restoreAnimation() { _skeletonModel->getRig().restoreAnimation(); } -void MyAvatar::restoreNetworkAnimation() { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "restoreNetworkAnimation"); - return; - } - _skeletonModel->getRig().restoreNetworkAnimation(); -} - QStringList MyAvatar::getAnimationRoles() { if (QThread::currentThread() != thread()) { QStringList result; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9770a5bb1a..16b765711a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -376,7 +376,6 @@ public: * }, 3000); */ Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - Q_INVOKABLE void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); /**jsdoc * The avatar animation system includes a set of default animations along with rules for how those animations are blended together with @@ -393,7 +392,6 @@ public: * }, 3000); */ Q_INVOKABLE void restoreAnimation(); - Q_INVOKABLE void restoreNetworkAnimation(); /**jsdoc * Each avatar has an avatar-animation.json file that defines which animations are used and how they are blended together with procedural data diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index eba361fd4c..92f4c01dcc 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -51,6 +51,8 @@ public: bool getMirrorFlag() const { return _mirrorFlag; } void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; } + float getFrame() const { return _frame; } + void loadURL(const QString& url); protected: diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b9e654964a..df7e322da7 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -31,6 +31,7 @@ #include "AnimSkeleton.h" #include "AnimUtil.h" #include "IKTarget.h" +#include "PathUtils.h" static int nextRigId = 1; @@ -133,41 +134,28 @@ void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firs _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } -void Rig::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { - UserAnimState::ClipNodeEnum clipNodeEnum; - if (_networkAnimState.clipNodeEnum == UserAnimState::None || _networkAnimState.clipNodeEnum == UserAnimState::B) { - clipNodeEnum = UserAnimState::A; +void Rig::triggerNetworkAnimation(const QString& animName) { + _networkVars.set("idleAnim", false); + _networkVars.set("preTransitAnim", false); + _networkVars.set("transitAnim", false); + _networkVars.set("postTransitAnim", false); + _sendNetworkNode = true; + + if (animName == "idleAnim") { + _networkVars.set("idleAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::Idle; + _sendNetworkNode = false; + } else if (animName == "preTransitAnim") { + _networkVars.set("preTransitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::PreTransit; + } else if (animName == "transitAnim") { + _networkVars.set("transitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::Transit; + } else if (animName == "postTransitAnim") { + _networkVars.set("postTransitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::PostTransit; } - else { - clipNodeEnum = UserAnimState::B; - } - if (_networkNode) { - // find an unused AnimClip clipNode - _sendNetworkNode = true; - std::shared_ptr clip; - if (clipNodeEnum == UserAnimState::A) { - clip = std::dynamic_pointer_cast(_networkNode->findByName("userAnimA")); - } - else { - clip = std::dynamic_pointer_cast(_networkNode->findByName("userAnimB")); - } - if (clip) { - // set parameters - clip->setLoopFlag(loop); - clip->setStartFrame(firstFrame); - clip->setEndFrame(lastFrame); - const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; - clip->setTimeScale(timeScale); - clip->loadURL(url); - } - } - // store current user anim state. - _networkAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame }; - // notify the userAnimStateMachine the desired state. - _networkVars.set("userAnimNone", false); - _networkVars.set("userAnimA", clipNodeEnum == UserAnimState::A); - _networkVars.set("userAnimB", clipNodeEnum == UserAnimState::B); + } void Rig::restoreAnimation() { @@ -182,13 +170,12 @@ void Rig::restoreAnimation() { } void Rig::restoreNetworkAnimation() { - _sendNetworkNode = false; - if (_networkAnimState.clipNodeEnum != UserAnimState::None) { - _networkAnimState.clipNodeEnum = UserAnimState::None; - // notify the userAnimStateMachine the desired state. - _networkVars.set("userAnimNone", true); - _networkVars.set("userAnimA", false); - _networkVars.set("userAnimB", false); + if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) { + _networkAnimState.clipNodeEnum = NetworkAnimState::Idle; + _networkVars.set("idleAnim", true); + _networkVars.set("preTransitAnim", false); + _networkVars.set("transitAnim", false); + _networkVars.set("postTransitAnim", false); } } @@ -1137,6 +1124,24 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); if (_networkNode) { _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + float alpha = 1.0f; + std::shared_ptr clip; + if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { + clip = std::dynamic_pointer_cast(_networkNode->findByName("preTransitAnim")); + if (clip) { + alpha = (clip->getFrame() - clip->getStartFrame()) / 6.0f; + } + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { + clip = std::dynamic_pointer_cast(_networkNode->findByName("postTransitAnim")); + if (clip) { + alpha = (clip->getEndFrame() - clip->getFrame()) / 6.0f; + } + } + if (_sendNetworkNode) { + for (int i = 0; i < _networkPoseSet._relativePoses.size(); i++) { + _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha)); + } + } } if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. @@ -1798,10 +1803,10 @@ void Rig::initAnimGraph(const QUrl& url) { // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); - _networkLoader.reset(new AnimNodeLoader(url)); - + auto networkUrl = PathUtils::resourcesUrl("avatar/network-animation.json"); + _networkLoader.reset(new AnimNodeLoader(networkUrl)); std::weak_ptr weakSkeletonPtr = _animSkeleton; - connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { + connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, url](AnimNode::Pointer nodeIn) { _animNode = nodeIn; // abort load if the previous skeleton was deleted. @@ -1828,10 +1833,10 @@ void Rig::initAnimGraph(const QUrl& url) { emit onLoadComplete(); }); connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { - qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + qCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; }); - connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { + connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, networkUrl](AnimNode::Pointer nodeIn) { _networkNode = nodeIn; // abort load if the previous skeleton was deleted. auto sharedSkeletonPtr = weakSkeletonPtr.lock(); @@ -1839,16 +1844,22 @@ void Rig::initAnimGraph(const QUrl& url) { return; } _networkNode->setSkeleton(sharedSkeletonPtr); - if (_networkAnimState.clipNodeEnum != UserAnimState::None) { + if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) { // restore the user animation we had before reset. - UserAnimState origState = _networkAnimState; - _networkAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; - overrideNetworkAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); + NetworkAnimState origState = _networkAnimState; + _networkAnimState = { NetworkAnimState::Idle, "", 30.0f, false, 0.0f, 0.0f }; + if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { + triggerNetworkAnimation("preTransitAnim"); + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::Transit) { + triggerNetworkAnimation("transitAnim"); + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { + triggerNetworkAnimation("postTransitAnim"); + } } - // emit onLoadComplete(); + }); - connect(_networkLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { - qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) { + qCritical(animation) << "Error loading" << networkUrl.toDisplayString() << "code = " << error << "str =" << str; }); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 37d1ec1dd3..af786a5e70 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -113,7 +113,7 @@ public: void destroyAnimGraph(); void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + void triggerNetworkAnimation(const QString& animName); void restoreAnimation(); void restoreNetworkAnimation(); @@ -323,6 +323,25 @@ protected: RigRole _state { RigRole::Idle }; RigRole _desiredState { RigRole::Idle }; float _desiredStateAge { 0.0f }; + + struct NetworkAnimState { + enum ClipNodeEnum { + Idle = 0, + PreTransit, + Transit, + PostTransit + }; + NetworkAnimState() : clipNodeEnum(NetworkAnimState::Idle) {} + NetworkAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) : + clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {} + + ClipNodeEnum clipNodeEnum; + QString url; + float fps; + bool loop; + float firstFrame; + float lastFrame; + }; struct UserAnimState { enum ClipNodeEnum { @@ -357,7 +376,7 @@ protected: }; UserAnimState _userAnimState; - UserAnimState _networkAnimState; + NetworkAnimState _networkAnimState; std::map _roleAnimStates; float _leftHandOverlayAlpha { 0.0f }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index b43e1c23f6..705ebc0b13 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -114,12 +114,10 @@ void Avatar::setShowNamesAboveHeads(bool show) { } AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { - bool checkDistance = (!_isActive || (_status == Status::POST_TRANSIT || _status == Status::ENDED)); float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); - const float TRANSIT_TRIGGER_MAX_DISTANCE = 30.0f; float scaledMaxTransitDistance = TRANSIT_TRIGGER_MAX_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance && checkDistance) { + if (oneFrameDistance > config._triggerDistance) { if (oneFrameDistance < scaledMaxTransitDistance) { start(deltaTime, _lastPosition, avatarPosition, config); } else { @@ -132,12 +130,9 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av const float SETTLE_ABORT_DISTANCE = 0.1f; float scaledSettleAbortDistance = SETTLE_ABORT_DISTANCE * _scale; - bool abortPostTransit = (_status == Status::POST_TRANSIT && _purpose == Purpose::SIT) || - (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT); - if (abortPostTransit) { + if (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT) { reset(); _status = Status::ENDED; - _purpose = Purpose::UNDEFINED; } return _status; } @@ -154,10 +149,13 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _transitLine = endPosition - startPosition; _totalDistance = glm::length(_transitLine); _easeType = config._easeType; - const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - _preTransitTime = (float)config._startTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; - _postTransitTime = (float)config._endTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + const float REFERENCE_FRAMES_PER_SECOND = 30.0f; + const float PRE_TRANSIT_FRAME_COUNT = 10.0f; + const float POST_TRANSIT_FRAME_COUNT = 27.0f; + + _preTransitTime = PRE_TRANSIT_FRAME_COUNT / REFERENCE_FRAMES_PER_SECOND; + _postTransitTime = POST_TRANSIT_FRAME_COUNT / REFERENCE_FRAMES_PER_SECOND; int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; _totalTime = _transitTime + _preTransitTime + _postTransitTime; @@ -206,7 +204,6 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { _currentPosition = _endPosition; if (nextTime >= _totalTime) { _isActive = false; - _purpose = Purpose::UNDEFINED; status = Status::ENDED; } else if (_currentTime < _totalTime - _postTransitTime) { status = Status::END_TRANSIT; @@ -2007,11 +2004,6 @@ void Avatar::setTransitScale(float scale) { _transit.setScale(scale); } -void Avatar::setTransitPurpose(int purpose) { - std::lock_guard lock(_transitLock); - _transit.setPurpose(static_cast(purpose)); -} - void Avatar::overrideNextPacketPositionData(const glm::vec3& position) { std::lock_guard lock(_transitLock); _overrideGlobalPosition = true; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index d375909609..2995c0f7b4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -71,21 +71,6 @@ public: EASE_IN_OUT }; - enum Purpose { - UNDEFINED = 0, - TELEPORT, - SIT - }; - - struct TransitAnimation { - TransitAnimation(){}; - TransitAnimation(const QString& animationUrl, int firstFrame, int frameCount) : - _firstFrame(firstFrame), _frameCount(frameCount), _animationUrl(animationUrl){}; - int _firstFrame; - int _frameCount; - QString _animationUrl; - }; - struct TransitConfig { TransitConfig() {}; int _totalFrames { 0 }; @@ -93,9 +78,6 @@ public: bool _isDistanceBased { false }; float _triggerDistance { 0 }; EaseType _easeType { EaseType::EASE_OUT }; - TransitAnimation _startTransitAnimation; - TransitAnimation _middleTransitAnimation; - TransitAnimation _endTransitAnimation; }; AvatarTransit() {}; @@ -105,7 +87,6 @@ public: glm::vec3 getCurrentPosition() { return _currentPosition; } glm::vec3 getEndPosition() { return _endPosition; } void setScale(float scale) { _scale = scale; } - void setPurpose(const Purpose& purpose) { _purpose = purpose; } void reset(); private: @@ -130,7 +111,6 @@ private: EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; float _scale { 1.0f }; - Purpose _purpose { Purpose::UNDEFINED }; }; class Avatar : public AvatarData, public scriptable::ModelProvider { @@ -457,7 +437,6 @@ public: AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); void setTransitScale(float scale); - Q_INVOKABLE void setTransitPurpose(int purpose); void overrideNextPacketPositionData(const glm::vec3& position); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index d31f207b19..bf5022cdaf 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -781,13 +781,11 @@ Script.include("/~/system/libraries/controllers.js"); if (target === TARGET.NONE || target === TARGET.INVALID) { // Do nothing } else if (target === TARGET.SEAT) { - MyAvatar.setTransitPurpose(2); Entities.callEntityMethod(result.objectID, 'sit'); } else if (target === TARGET.SURFACE || target === TARGET.DISCREPANCY) { var offset = getAvatarFootOffset(); result.intersection.y += offset; var shouldLandSafe = target === TARGET.DISCREPANCY; - MyAvatar.setTransitPurpose(1); MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false, shouldLandSafe); HMD.centerUI(); MyAvatar.centerBody(); From ef740140750e2ef1a143f86f2d2672e883ccfd91 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 19 Oct 2018 13:55:56 -0700 Subject: [PATCH 078/131] Small avatars now have a minimum jump height of 0.25 meters This should improve the quality of the jump animation and improve the mobility of small avatars. --- interface/src/avatar/MySkeletonModel.cpp | 2 +- libraries/animation/src/Rig.cpp | 16 ++++++++++++---- libraries/animation/src/Rig.h | 3 ++- libraries/physics/src/CharacterController.cpp | 8 +++++--- libraries/shared/src/AvatarConstants.h | 7 ++++--- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index c1a49d7a10..08a1e190f1 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -298,7 +298,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { auto velocity = myAvatar->getLocalVelocity() / myAvatar->getSensorToWorldScale(); auto position = myAvatar->getLocalPosition(); auto orientation = myAvatar->getLocalOrientation(); - _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); + _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState, myAvatar->getSensorToWorldScale()); // evaluate AnimGraph animation and update jointStates. Model::updateRig(deltaTime, parentTransform); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 341b554949..335cddf218 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -30,6 +30,7 @@ #include "AnimOverlay.h" #include "AnimSkeleton.h" #include "AnimUtil.h" +#include "AvatarConstants.h" #include "IKTarget.h" @@ -629,7 +630,8 @@ bool Rig::getRelativeDefaultJointTranslation(int index, glm::vec3& translationOu } } -void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState) { +void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, + const glm::quat& worldRotation, CharacterControllerState ccState, float sensorToWorldScale) { glm::vec3 forward = worldRotation * IDENTITY_FORWARD; glm::vec3 workingVelocity = worldVelocity; @@ -924,9 +926,15 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } _animVars.set("isNotInAir", false); - // compute blend based on velocity - const float JUMP_SPEED = 3.5f; - float alpha = glm::clamp(-workingVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; + // We want to preserve the apparent jump height in sensor space. + const float jumpHeight = std::max(sensorToWorldScale * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + + // convert jump height to a initial jump speed with the given gravity. + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + + // compute inAirAlpha blend based on velocity + float alpha = glm::clamp((-workingVelocity.y * sensorToWorldScale) / jumpSpeed, -1.0f, 1.0f) + 1.0f; + _animVars.set("inAirAlpha", alpha); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index ed0b70d4b6..5330a06a01 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -172,7 +172,8 @@ public: AnimPose getJointPose(int jointIndex) const; // Start or stop animations as needed. - void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState); + void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, + const glm::quat& worldRotation, CharacterControllerState ccState, float sensorToWorldScale); // Regardless of who started the animations or how many, update the joints. void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 626184d1dc..8fd6d4eada 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -766,14 +766,16 @@ void CharacterController::updateState() { SET_STATE(State::InAir, "takeoff done"); // compute jumpSpeed based on the scaled jump height for the default avatar in default gravity. - float jumpSpeed = sqrtf(2.0f * DEFAULT_AVATAR_GRAVITY * _scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT); + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); velocity += jumpSpeed * _currentUp; _rigidBody->setLinearVelocity(velocity); } break; case State::InAir: { - const float JUMP_SPEED = _scaleFactor * DEFAULT_AVATAR_JUMP_SPEED; - if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { SET_STATE(State::Ground, "hit ground"); } else if (_flyingAllowed) { btVector3 desiredVelocity = _targetVelocity; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 6c38d08c96..87da47a27a 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -70,9 +70,10 @@ const float DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second const float DEFAULT_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f; -const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 -const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second -const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AVATAR_JUMP_SPEED) / (2.0f * DEFAULT_AVATAR_GRAVITY); // meters +const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 (world) +const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second (sensor) +const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AVATAR_JUMP_SPEED) / (2.0f * -DEFAULT_AVATAR_GRAVITY); // meters (sensor) +const float DEFAULT_AVATAR_MIN_JUMP_HEIGHT = 0.25f; // meters (world) // hack const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters From 31d099907aea02a6a11ca357b86ef80d3896413a Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 19 Oct 2018 14:00:52 -0700 Subject: [PATCH 079/131] Fix warnings --- libraries/animation/src/Rig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index df7e322da7..35f076c78d 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1138,7 +1138,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons } } if (_sendNetworkNode) { - for (int i = 0; i < _networkPoseSet._relativePoses.size(); i++) { + for (auto i = 0; i < _networkPoseSet._relativePoses.size(); i++) { _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha)); } } From e59f1516efc6d2f9ea5ab8ba7cb2848696101485 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 19 Oct 2018 16:01:53 -0700 Subject: [PATCH 080/131] CR changes --- scripts/system/html/css/edit-style.css | 14 +- scripts/system/html/js/entityProperties.js | 190 ++++++++++++++------- 2 files changed, 133 insertions(+), 71 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8205fcd340..c4ab00e689 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -614,8 +614,8 @@ hr { margin-top: 0; } -.checkbox-sub-props { - margin-top: 18px; +.checkbox-sub-props { + margin-top: 18px; } .property .number { @@ -969,12 +969,12 @@ div.refresh input[type="button"] { margin-top: 6px; } -fieldset .checkbox-sub-props { - margin-top: 0; -} +fieldset .checkbox-sub-props { + margin-top: 0; +} -fieldset .checkbox-sub-props .property:first-child { - margin-top: 0; +fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; } .column { diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 1a52cf939d..d3e31e4b9b 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1,6 +1,7 @@ // entityProperties.js // // Created by Ryan Huffman on 13 Nov 2014 +// Modified by David Back on 19 Oct 2018 // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -26,9 +27,7 @@ const ICON_FOR_TYPE = { Material: "" }; -const PI = 3.14159265358979; -const DEGREES_TO_RADIANS = PI / 180.0; -const RADIANS_TO_DEGREES = 180.0 / PI; +const DEGREES_TO_RADIANS = Math.PI / 180.0; const NO_SELECTION = "No selection"; @@ -564,7 +563,7 @@ const GROUPS = [ type: "vec2", vec2Type: "xy", min: 0, - min: 1, + max: 1, step: 0.1, decimals: 4, subLabels: [ "x", "y" ], @@ -883,7 +882,7 @@ const GROUPS = [ propertyID: "azimuthFinish", }, { - label: "Verical Angle Start", + label: "Vertical Angle Start", type: "slider", min: 0, max: 180, @@ -893,7 +892,7 @@ const GROUPS = [ propertyID: "polarStart", }, { - label: "Verical Angle Finish", + label: "Vertical Angle Finish", type: "slider", min: 0, max: 180, @@ -1233,12 +1232,53 @@ const GROUPS_PER_TYPE = { const EDITOR_TIMEOUT_DURATION = 1500; const DEBOUNCE_TIMEOUT = 125; + +const COLOR_MIN = 0; +const COLOR_MAX = 255; +const COLOR_STEP = 1; + const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MATERIAL_PREFIX_STRING = "mat::"; const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; +const PROPERTY_NAME_DIVISION = { + GROUP: 0, + PROPERTY: 1, + SUBPROPERTY: 2, +}; + +const VECTOR_ELEMENTS = { + X_INPUT: 0, + Y_INPUT: 1, + Z_INPUT: 2, +}; + +const COLOR_ELEMENTS = { + COLOR_PICKER: 0, + RED_INPUT: 1, + GREEN_INPUT: 2, + BLUE_INPUT: 3, +}; + +const SLIDER_ELEMENTS = { + SLIDER: 0, + NUMBER_INPUT: 1, +}; + +const ICON_ELEMENTS = { + ICON: 0, + LABEL: 1, +}; + +const TEXTURE_ELEMENTS = { + IMAGE: 0, + TEXT_INPUT: 1, +}; + +const JSON_EDITOR_ROW_DIV_INDEX = 2; + var elGroups = {}; var properties = {}; var colorPickers = {}; @@ -1420,26 +1460,26 @@ function showGroupsForType(type) { } } -function getPropertyValue(propertyName) { +function getPropertyValue(originalPropertyName) { // if this is a compound property name (i.e. animation.running) // then split it by . up to 3 times to find property value let propertyValue; - let splitPropertyName = propertyName.split('.'); + let splitPropertyName = originalPropertyName.split('.'); if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; let groupProperties = selectedEntityProperties[propertyGroupName]; - if (groupProperties === undefined || groupProperties[subPropertyName] === undefined) { + if (groupProperties === undefined || groupProperties[propertyName] === undefined) { return undefined; } - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - propertyValue = groupProperties[subPropertyName][subSubPropertyName]; + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUBPROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUBPROPERTY]; + propertyValue = groupProperties[propertyName][subPropertyName]; } else { - propertyValue = groupProperties[subPropertyName]; + propertyValue = groupProperties[propertyName]; } } else { - propertyValue = selectedEntityProperties[propertyName]; + propertyValue = selectedEntityProperties[originalPropertyName]; } return propertyValue; } @@ -1449,23 +1489,23 @@ function getPropertyValue(propertyName) { * PROPERTY UPDATE FUNCTIONS */ -function updateProperty(propertyName, propertyValue) { +function updateProperty(originalPropertyName, propertyValue) { let propertyUpdate = {}; // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times - let splitPropertyName = propertyName.split('.'); + let splitPropertyName = originalPropertyName.split('.'); if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; propertyUpdate[propertyGroupName] = {}; - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - propertyUpdate[propertyGroupName][subPropertyName] = {}; - propertyUpdate[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUBPROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUBPROPERTY]; + propertyUpdate[propertyGroupName][propertyName] = {}; + propertyUpdate[propertyGroupName][propertyName][subPropertyName] = propertyValue; } else { - propertyUpdate[propertyGroupName][subPropertyName] = propertyValue; + propertyUpdate[propertyGroupName][propertyName] = propertyValue; } } else { - propertyUpdate[propertyName] = propertyValue; + propertyUpdate[originalPropertyName] = propertyValue; } updateProperties(propertyUpdate); } @@ -1719,7 +1759,10 @@ function createSliderProperty(property, elProperty, elLabel) { elDiv.appendChild(elInput); elProperty.appendChild(elDiv); - return [ elSlider, elInput ]; + let elResult = []; + elResult[SLIDER_ELEMENTS.SLIDER] = elSlider; + elResult[SLIDER_ELEMENTS.NUMBER_INPUT] = elInput; + return elResult; } function createVec3Property(property, elProperty, elLabel) { @@ -1737,11 +1780,11 @@ function createVec3Property(property, elProperty, elLabel) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[0], + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_INPUT], propertyData.min, propertyData.max, propertyData.step); - let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT], propertyData.min, propertyData.max, propertyData.step); - let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[2], + let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_INPUT], propertyData.min, propertyData.max, propertyData.step); let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, @@ -1750,7 +1793,11 @@ function createVec3Property(property, elProperty, elLabel) { elInputY.addEventListener('change', inputChangeFunction); elInputZ.addEventListener('change', inputChangeFunction); - return [ elInputX, elInputY, elInputZ ]; + let elResult = []; + elResult[VECTOR_ELEMENTS.X_INPUT] = elInputX; + elResult[VECTOR_ELEMENTS.Y_INPUT] = elInputY; + elResult[VECTOR_ELEMENTS.Z_INPUT] = elInputZ; + return elResult; } function createVec2Property(property, elProperty, elLabel) { @@ -1768,9 +1815,9 @@ function createVec2Property(property, elProperty, elLabel) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[0], + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_INPUT], propertyData.min, propertyData.max, propertyData.step); - let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT], propertyData.min, propertyData.max, propertyData.step); let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, @@ -1778,7 +1825,10 @@ function createVec2Property(property, elProperty, elLabel) { elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); - return [elInputX, elInputY]; + let elResult = []; + elResult[VECTOR_ELEMENTS.X_INPUT] = elInputX; + elResult[VECTOR_ELEMENTS.Y_INPUT] = elInputY; + return elResult; } function createColorProperty(property, elProperty, elLabel) { @@ -1798,9 +1848,9 @@ function createColorProperty(property, elProperty, elLabel) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputR = createTupleNumberInput(elTuple, elementID, "red", 0, 255, 1); - let elInputG = createTupleNumberInput(elTuple, elementID, "green", 0, 255, 1); - let elInputB = createTupleNumberInput(elTuple, elementID, "blue", 0, 255, 1); + let elInputR = createTupleNumberInput(elTuple, elementID, "red", COLOR_MIN, COLOR_MAX, COLOR_STEP); + let elInputG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP); + let elInputB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP); let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); elInputR.addEventListener('change', inputChangeFunction); @@ -1832,7 +1882,12 @@ function createColorProperty(property, elProperty, elLabel) { } }); - return [elColorPicker, elInputR, elInputG, elInputB]; + let elResult = []; + elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker; + elResult[COLOR_ELEMENTS.RED_INPUT] = elInputR; + elResult[COLOR_ELEMENTS.GREEN_INPUT] = elInputG; + elResult[COLOR_ELEMENTS.BLUE_INPUT] = elInputB; + return elResult; } function createDropdownProperty(property, propertyID, elProperty, elLabel) { @@ -1902,7 +1957,10 @@ function createIconProperty(property, elProperty, elLabel) { elProperty.appendChild(elSpan); elProperty.appendChild(elLabel); - return [ elSpan, elLabel ]; + let elResult = []; + elResult[ICON_ELEMENTS.ICON] = elSpan; + elResult[ICON_ELEMENTS.LABEL] = elLabel; + return elResult; } function createTextureProperty(property, elProperty, elLabel) { @@ -1952,8 +2010,11 @@ function createTextureProperty(property, elProperty, elLabel) { elProperty.appendChild(elLabel); elProperty.appendChild(elDiv); elProperty.appendChild(elInput); - - return [ elImage, elInput ]; + + let elResult = []; + elResult[TEXTURE_ELEMENTS.IMAGE] = elImage; + elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput; + return elResult; } function createButtonsProperty(property, elProperty, elLabel) { @@ -2468,7 +2529,7 @@ function saveJSONMaterialData(noUpdate) { function bindAllNonJSONEditorElements() { var inputs = $('input'); var i; - for (i = 0; i < inputs.length; i++) { + for (i = 0; i < inputs.length; ++i) { var input = inputs[i]; var field = $(input); // TODO FIXME: (JSHint) Functions declared within loops referencing @@ -2497,7 +2558,7 @@ function bindAllNonJSONEditorElements() { function setDropdownText(dropdown) { let lis = dropdown.parentNode.getElementsByTagName("li"); let text = ""; - for (let i = 0; i < lis.length; i++) { + for (let i = 0; i < lis.length; ++i) { if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { text = lis[i].textContent; } @@ -2584,6 +2645,7 @@ function loaded() { let propertyID = propertyData.propertyID; let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; let propertyElementID = "property-" + propertyID; + propertyElementID = propertyElementID.replace('.', '-'); let elProperty; if (propertyType === "sub-header") { @@ -2644,29 +2706,29 @@ function loaded() { } case 'slider': { let elSlider = createSliderProperty(property, elProperty, elLabel); - properties[propertyID].elSlider = elSlider[0]; - properties[propertyID].elInput = elSlider[1]; + properties[propertyID].elSlider = elSlider[SLIDER_ELEMENTS.SLIDER]; + properties[propertyID].elInput = elSlider[SLIDER_ELEMENTS.NUMBER_INPUT]; break; } case 'vec3': { let elVec3 = createVec3Property(property, elProperty, elLabel); - properties[propertyID].elInputX = elVec3[0]; - properties[propertyID].elInputY = elVec3[1]; - properties[propertyID].elInputZ = elVec3[2]; + properties[propertyID].elInputX = elVec3[VECTOR_ELEMENTS.X_INPUT]; + properties[propertyID].elInputY = elVec3[VECTOR_ELEMENTS.Y_INPUT]; + properties[propertyID].elInputZ = elVec3[VECTOR_ELEMENTS.Z_INPUT]; break; } case 'vec2': { let elVec2 = createVec2Property(property, elProperty, elLabel); - properties[propertyID].elInputX = elVec2[0]; - properties[propertyID].elInputY = elVec2[1]; + properties[propertyID].elInputX = elVec2[VECTOR_ELEMENTS.X_INPUT]; + properties[propertyID].elInputY = elVec2[VECTOR_ELEMENTS.Y_INPUT]; break; } case 'color': { let elColor = createColorProperty(property, elProperty, elLabel); - properties[propertyID].elColorPicker = elColor[0]; - properties[propertyID].elInputR = elColor[1]; - properties[propertyID].elInputG = elColor[2]; - properties[propertyID].elInputB = elColor[3]; + properties[propertyID].elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER]; + properties[propertyID].elInputR = elColor[COLOR_ELEMENTS.RED_INPUT]; + properties[propertyID].elInputG = elColor[COLOR_ELEMENTS.GREEN_INPUT]; + properties[propertyID].elInputB = elColor[COLOR_ELEMENTS.BLUE_INPUT]; break; } case 'dropdown': { @@ -2679,14 +2741,14 @@ function loaded() { } case 'icon': { let elIcon = createIconProperty(property, elProperty, elLabel); - properties[propertyID].elSpan = elIcon[0]; - properties[propertyID].elLabel = elIcon[1]; + properties[propertyID].elSpan = elIcon[ICON_ELEMENTS.ICON]; + properties[propertyID].elLabel = elIcon[ICON_ELEMENTS.LABEL]; break; } case 'texture': { let elTexture = createTextureProperty(property, elProperty, elLabel); - properties[propertyID].elImage = elTexture[0]; - properties[propertyID].elInput = elTexture[1]; + properties[propertyID].elImage = elTexture[TEXTURE_ELEMENTS.IMAGE]; + properties[propertyID].elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT]; break; } case 'buttons': { @@ -2785,7 +2847,7 @@ function loaded() { let types = {}; let numTypes = 0; - for (let i = 0; i < selections.length; i++) { + for (let i = 0; i < selections.length; ++i) { ids.push(selections[i].id); let currentSelectedType = selections[i].properties.type; if (types[currentSelectedType] === undefined) { @@ -2810,7 +2872,7 @@ function loaded() { disableProperties(); } else { - selectedEntityProperties = data.selections[0].properties; + selectedEntityProperties = data.selections[0].properties; if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { @@ -3061,7 +3123,7 @@ function loaded() { let elUserDataSaved = document.createElement('span'); elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); elUserDataSaved.innerText = "Saved!"; - elDiv.childNodes[2].appendChild(elUserDataSaved); + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); elDiv.insertBefore(elStaticUserData, elUserData); elDiv.insertBefore(elUserDataEditor, elUserData); @@ -3077,7 +3139,7 @@ function loaded() { let elMaterialDataSaved = document.createElement('span'); elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); elMaterialDataSaved.innerText = "Saved!"; - elDiv.childNodes[2].appendChild(elMaterialDataSaved); + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData); @@ -3156,7 +3218,7 @@ function loaded() { for (let optionIndex = 0; optionIndex < options.length; ++optionIndex) { if (options[optionIndex].getAttribute("selected") === "selected") { selectedOption = optionIndex; - // TODO: Shouldn't there be a break here? + break; } } let div = elDropdown.parentNode; @@ -3225,7 +3287,7 @@ function loaded() { // For input and textarea elements, select all of the text on focus let els = document.querySelectorAll("input, textarea"); - for (let i = 0; i < els.length; i++) { + for (let i = 0; i < els.length; ++i) { els[i].onfocus = function (e) { e.target.select(); }; From f8d67d124fc8b80623f4557eeffeb944314f1de6 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sat, 20 Oct 2018 15:22:25 -0700 Subject: [PATCH 081/131] Hold flickering fixed and refactor --- interface/src/avatar/AvatarManager.cpp | 14 +++--- interface/src/avatar/MyAvatar.cpp | 1 - libraries/animation/src/Rig.cpp | 2 +- .../src/avatars-renderer/Avatar.cpp | 45 +++++-------------- .../src/avatars-renderer/Avatar.h | 10 ++--- libraries/avatars/src/AvatarData.cpp | 29 +++++------- libraries/avatars/src/AvatarData.h | 15 +++++-- 7 files changed, 46 insertions(+), 70 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index dd56c03d26..bd3fba9a69 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -79,14 +79,12 @@ AvatarManager::AvatarManager(QObject* parent) : } }); - const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f; - const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing - const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; // Based on testing - _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; - _transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE; + _transitConfig._minTriggerDistance = AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE; + _transitConfig._maxTriggerDistance = AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; - _transitConfig._isDistanceBased = true; + _transitConfig._isDistanceBased = AVATAR_TRANSIT_DISTANCE_BASED; + _transitConfig._abortDistance = AVATAR_TRANSIT_ABORT_DISTANCE; } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -169,7 +167,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); - AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); + AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _myAvatar->getSensorToWorldScale(), _transitConfig); handleTransitAnimations(status); _myAvatar->update(deltaTime); @@ -300,7 +298,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } - auto transitStatus = avatar->_transit.update(deltaTime, avatar->_lastPosition, _transitConfig); + auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig); if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { avatar->_transit.reset(); avatar->setIsNewAvatar(false); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b347963cf1..ca283616a5 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -929,7 +929,6 @@ void MyAvatar::updateSensorToWorldMatrix() { updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); if (hasSensorToWorldScaleChanged) { - setTransitScale(sensorToWorldScale); emit sensorToWorldScaleChanged(sensorToWorldScale); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 35f076c78d..31c90a3070 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1138,7 +1138,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons } } if (_sendNetworkNode) { - for (auto i = 0; i < _networkPoseSet._relativePoses.size(); i++) { + for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) { _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha)); } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 705ebc0b13..c140f1bede 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -115,10 +115,8 @@ void Avatar::setShowNamesAboveHeads(bool show) { AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); - const float TRANSIT_TRIGGER_MAX_DISTANCE = 30.0f; - float scaledMaxTransitDistance = TRANSIT_TRIGGER_MAX_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance) { - if (oneFrameDistance < scaledMaxTransitDistance) { + if (oneFrameDistance > (config._minTriggerDistance * _scale)) { + if (oneFrameDistance < (config._maxTriggerDistance * _scale)) { start(deltaTime, _lastPosition, avatarPosition, config); } else { _lastPosition = avatarPosition; @@ -128,9 +126,7 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av _lastPosition = avatarPosition; _status = updatePosition(deltaTime); - const float SETTLE_ABORT_DISTANCE = 0.1f; - float scaledSettleAbortDistance = SETTLE_ABORT_DISTANCE * _scale; - if (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT) { + if (_isActive && oneFrameDistance > (config._abortDistance * _scale) && _status == Status::POST_TRANSIT) { reset(); _status = Status::ENDED; } @@ -150,14 +146,10 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _totalDistance = glm::length(_transitLine); _easeType = config._easeType; - const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - const float PRE_TRANSIT_FRAME_COUNT = 10.0f; - const float POST_TRANSIT_FRAME_COUNT = 27.0f; - - _preTransitTime = PRE_TRANSIT_FRAME_COUNT / REFERENCE_FRAMES_PER_SECOND; - _postTransitTime = POST_TRANSIT_FRAME_COUNT / REFERENCE_FRAMES_PER_SECOND; + _preTransitTime = AVATAR_PRE_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND; + _postTransitTime = AVATAR_POST_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND; int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; - _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; + _transitTime = (float)transitFrames / AVATAR_TRANSIT_FRAMES_PER_SECOND; _totalTime = _transitTime + _preTransitTime + _postTransitTime; _currentTime = _isActive ? _preTransitTime : 0.0f; _isActive = true; @@ -554,13 +546,10 @@ void Avatar::relayJointDataToChildren() { void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); - - if (_transit.isActive()) { - _globalPosition = _transit.getCurrentPosition(); - _globalPositionChanged = usecTimestampNow(); - if (!hasParent()) { - setLocalPosition(_transit.getCurrentPosition()); - } + + _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition; + if (!hasParent()) { + setLocalPosition(_globalPosition); } _simulationRate.increment(); @@ -1994,22 +1983,12 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { } } -AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { +AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config) { std::lock_guard lock(_transitLock); + _transit.setScale(avatarScale); return _transit.update(deltaTime, avatarPosition, config); } -void Avatar::setTransitScale(float scale) { - std::lock_guard lock(_transitLock); - _transit.setScale(scale); -} - -void Avatar::overrideNextPacketPositionData(const glm::vec3& position) { - std::lock_guard lock(_transitLock); - _overrideGlobalPosition = true; - _globalPositionOverride = position; -} - void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].push(material); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 2995c0f7b4..3df923b048 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -76,7 +76,9 @@ public: int _totalFrames { 0 }; float _framesPerMeter { 0.0f }; bool _isDistanceBased { false }; - float _triggerDistance { 0 }; + float _minTriggerDistance { 0.0f }; + float _maxTriggerDistance { 0.0f }; + float _abortDistance{ 0.0f }; EaseType _easeType { EaseType::EASE_OUT }; }; @@ -434,11 +436,7 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override; std::shared_ptr getTransit() { return std::make_shared(_transit); }; - - AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); - void setTransitScale(float scale); - - void overrideNextPacketPositionData(const glm::vec3& position); + AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config); signals: void targetScaleChanged(float targetScale); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a22cc4a1d3..4ff3c0192d 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -371,12 +371,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarGlobalPosition) { auto startSection = destinationBuffer; - if (_overrideGlobalPosition) { - AVATAR_MEMCPY(_globalPositionOverride); - } else { - AVATAR_MEMCPY(_globalPosition); - } - + AVATAR_MEMCPY(_globalPosition); int numBytes = destinationBuffer - startSection; @@ -894,20 +889,22 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { offset = glm::vec3(row * SPACE_BETWEEN_AVATARS, 0.0f, col * SPACE_BETWEEN_AVATARS); } - auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; - if (_globalPosition != newValue) { - _lastPosition = _globalPosition = newValue; + _serverPosition = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; + auto oneStepDistance = glm::length(_globalPosition - _serverPosition); + if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) { + _globalPosition = _serverPosition; + // if we don't have a parent, make sure to also set our local position + if (!hasParent()) { + setLocalPosition(_serverPosition); + } + } + if (_globalPosition != _serverPosition) { _globalPositionChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; _globalPositionRate.increment(numBytesRead); _globalPositionUpdateRate.increment(); - - // if we don't have a parent, make sure to also set our local position - if (!hasParent()) { - setLocalPosition(newValue); - } } if (hasAvatarBoundingBox) { @@ -2110,10 +2107,6 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) { } } - if (_overrideGlobalPosition) { - _overrideGlobalPosition = false; - } - doneEncoding(cullSmallData); static AvatarDataSequenceNumber sequenceNumber = 0; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index dcdaa70ad7..fcbe05ca52 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -327,6 +327,17 @@ const float AVATAR_DISTANCE_LEVEL_5 = 200.0f; // meters // This is the start location in the Sandbox (xyz: 6270, 211, 6000). const glm::vec3 START_LOCATION(6270, 211, 6000); +// Avatar Transit Constants +const float AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE = 1.0f; +const float AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE = 30.0f; +const int AVATAR_TRANSIT_FRAME_COUNT = 11; +const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; +const float AVATAR_TRANSIT_ABORT_DISTANCE = 0.1f; +const bool AVATAR_TRANSIT_DISTANCE_BASED = true; +const float AVATAR_TRANSIT_FRAMES_PER_SECOND = 30.0f; +const float AVATAR_PRE_TRANSIT_FRAME_COUNT = 10.0f; +const float AVATAR_POST_TRANSIT_FRAME_COUNT = 27.0f; + enum KeyState { NO_KEY_DOWN = 0, INSERT_KEY_DOWN, @@ -1378,9 +1389,7 @@ protected: // where Entities are located. This is currently only used by the mixer to decide how often to send // updates about one avatar to another. glm::vec3 _globalPosition { 0, 0, 0 }; - glm::vec3 _lastPosition { 0, 0, 0 }; - glm::vec3 _globalPositionOverride { 0, 0, 0 }; - bool _overrideGlobalPosition { false }; + glm::vec3 _serverPosition { 0, 0, 0 }; quint64 _globalPositionChanged { 0 }; quint64 _avatarBoundingBoxChanged { 0 }; From ddbe02dd4e9b5993027a86ab72e9a405d80c5056 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 22 Oct 2018 11:56:47 -0700 Subject: [PATCH 082/131] CR changes and fix extra info toggle --- scripts/system/html/css/edit-style.css | 15 +++++--- scripts/system/html/entityList.html | 6 ++-- scripts/system/html/js/entityList.js | 48 +++++++++++++------------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 1b0094cfb7..13952c5e38 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1063,10 +1063,10 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { .multiselect { position: relative; } -.selectBox { +.select-box { position: absolute; } -.selectBox select { +.select-box select { font-family: FiraSans-SemiBold; font-size: 15px; color: #afafaf; @@ -1076,7 +1076,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 107px; text-align-last: center; } -.overSelect { +.over-select { position: absolute; left: 0; right: 0; @@ -1084,7 +1084,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { bottom: 0; } -#filter-type-selectBox select { +#filter-type-select-box select { border-radius: 14.5px; } #filter-type-checkboxes { @@ -1158,6 +1158,13 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { #filter-radius-and-unit label { margin-left: 2px; } +#filter-radius-and-unit span { + position: relative; + top: 25px; + right: 9px; + z-index: 2; + font-style: italic; +} #filter-radius-and-unit input { width: 120px; border-radius: 14.5px; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 434f8a5f87..187aa70347 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -31,11 +31,11 @@
-
+
-
+
@@ -47,7 +47,7 @@
- +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 66ad08e27c..2d248e48e6 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -131,7 +131,7 @@ function loaded() { elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); elDelete = document.getElementById("delete"); - elFilterTypeSelectBox = document.getElementById("filter-type-selectBox"); + elFilterTypeSelectBox = document.getElementById("filter-type-select-box"); elFilterTypeText = document.getElementById("filter-type-text"); elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); elFilterSearch = document.getElementById("filter-search"); @@ -155,31 +155,31 @@ function loaded() { document.getElementById("entity-url").onclick = function() { setSortColumn('url'); }; - document.getElementById("entity-locked").onclick = function () { + document.getElementById("entity-locked").onclick = function() { setSortColumn('locked'); }; - document.getElementById("entity-visible").onclick = function () { + document.getElementById("entity-visible").onclick = function() { setSortColumn('visible'); }; - document.getElementById("entity-verticesCount").onclick = function () { + document.getElementById("entity-verticesCount").onclick = function() { setSortColumn('verticesCount'); }; - document.getElementById("entity-texturesCount").onclick = function () { + document.getElementById("entity-texturesCount").onclick = function() { setSortColumn('texturesCount'); }; - document.getElementById("entity-texturesSize").onclick = function () { + document.getElementById("entity-texturesSize").onclick = function() { setSortColumn('texturesSize'); }; - document.getElementById("entity-hasTransparent").onclick = function () { + document.getElementById("entity-hasTransparent").onclick = function() { setSortColumn('hasTransparent'); }; - document.getElementById("entity-isBaked").onclick = function () { + document.getElementById("entity-isBaked").onclick = function() { setSortColumn('isBaked'); }; - document.getElementById("entity-drawCalls").onclick = function () { + document.getElementById("entity-drawCalls").onclick = function() { setSortColumn('drawCalls'); }; - document.getElementById("entity-hasScript").onclick = function () { + document.getElementById("entity-hasScript").onclick = function() { setSortColumn('hasScript'); }; elRefresh.onclick = function() { @@ -203,12 +203,9 @@ function loaded() { elFilterSearch.onkeyup = refreshEntityList; elFilterSearch.onsearch = refreshEntityList; elFilterInView.onclick = toggleFilterInView; + elFilterRadius.onkeyup = onRadiusChange; elFilterRadius.onchange = onRadiusChange; - elFilterRadius.oninput = function(event) { - if (event.target.value.length > MAX_LENGTH_RADIUS) { - event.target.value = event.target.value.slice(0, MAX_LENGTH_RADIUS); - } - } + elInfoToggle.onclick = toggleInfo; // create filter type dropdown checkboxes with label and icon for each type elFilterTypeSelectBox.onclick = toggleTypeDropdown; @@ -225,9 +222,10 @@ function loaded() { let elInput = document.createElement('input'); elInput.setAttribute("type", "checkbox"); elInput.setAttribute("id", typeFilterID); + elInput.setAttribute("filterType", type); elInput.checked = true; // all types are checked initially - toggleTypeFilter(type, false); // add all types to the initial types filter - elInput.onclick = onToggleTypeFilter(type); + toggleTypeFilter(elInput, false); // add all types to the initial types filter + elInput.onclick = onToggleTypeFilter; elDiv.appendChild(elInput); elLabel.insertBefore(elSpan, elLabel.childNodes[0]); elDiv.appendChild(elLabel); @@ -642,6 +640,7 @@ function loaded() { } function onRadiusChange() { + elFilterRadius.value = elFilterRadius.value.replace(/[^0-9]/g, ''); elFilterRadius.value = Math.max(elFilterRadius.value, 0); EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); @@ -655,11 +654,14 @@ function loaded() { elFilterTypeCheckboxes.style.display = isTypeDropdownVisible() ? "none" : "block"; } - function toggleTypeFilter(type, refresh) { + function toggleTypeFilter(elInput, refresh) { + let type = elInput.getAttribute("filterType"); + let typeChecked = elInput.checked; + let typeFilterIndex = typeFilters.indexOf(type); - if (typeFilterIndex > -1) { + if (!typeChecked && typeFilterIndex > -1) { typeFilters.splice(typeFilterIndex, 1); - } else { + } else if (typeChecked && typeFilterIndex === -1) { typeFilters.push(type); } @@ -676,10 +678,8 @@ function loaded() { } } - function onToggleTypeFilter(type) { - return function() { - toggleTypeFilter(type, true); - }; + function onToggleTypeFilter(event) { + toggleTypeFilter(this, true); } function onBodyClick(event) { From e5caf016818854b7b68be61ab2fde98837145d52 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 22 Oct 2018 14:51:54 -0700 Subject: [PATCH 083/131] CR change for body click event --- scripts/system/html/js/entityList.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 2d248e48e6..a4e4c0dcd1 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -200,15 +200,16 @@ function loaded() { elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); } + elFilterTypeSelectBox.onclick = onToggleTypeDropdown; elFilterSearch.onkeyup = refreshEntityList; elFilterSearch.onsearch = refreshEntityList; elFilterInView.onclick = toggleFilterInView; elFilterRadius.onkeyup = onRadiusChange; elFilterRadius.onchange = onRadiusChange; + elFilterRadius.onclick = onRadiusChange; elInfoToggle.onclick = toggleInfo; // create filter type dropdown checkboxes with label and icon for each type - elFilterTypeSelectBox.onclick = toggleTypeDropdown; for (let i = 0; i < FILTER_TYPES.length; ++i) { let type = FILTER_TYPES[i]; let typeFilterID = "filter-type-" + type; @@ -225,11 +226,11 @@ function loaded() { elInput.setAttribute("filterType", type); elInput.checked = true; // all types are checked initially toggleTypeFilter(elInput, false); // add all types to the initial types filter - elInput.onclick = onToggleTypeFilter; elDiv.appendChild(elInput); elLabel.insertBefore(elSpan, elLabel.childNodes[0]); elDiv.appendChild(elLabel); elFilterTypeCheckboxes.appendChild(elDiv); + elDiv.onclick = onToggleTypeFilter; } entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, @@ -654,6 +655,11 @@ function loaded() { elFilterTypeCheckboxes.style.display = isTypeDropdownVisible() ? "none" : "block"; } + function onToggleTypeDropdown(event) { + toggleTypeDropdown(); + event.stopPropagation(); + } + function toggleTypeFilter(elInput, refresh) { let type = elInput.getAttribute("filterType"); let typeChecked = elInput.checked; @@ -679,14 +685,17 @@ function loaded() { } function onToggleTypeFilter(event) { - toggleTypeFilter(this, true); + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + toggleTypeFilter(elTarget, true); + } + event.stopPropagation(); } function onBodyClick(event) { - // if clicking anywhere outside of the type filter dropdown and it's open then close it - let elTarget = event.target; - if (isTypeDropdownVisible() && !elFilterTypeSelectBox.contains(elTarget) && - !elFilterTypeCheckboxes.contains(elTarget)) { + // if clicking anywhere outside of the type filter dropdown (since click event bubbled up to onBodyClick and + // propagation wasn't stopped by onToggleTypeFilter or onToggleTypeDropdown) and the dropdown is open then close it + if (isTypeDropdownVisible()) { toggleTypeDropdown(); } } From 3cc78896ac67357638e9f0e670a9adff76bf10f0 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Mon, 22 Oct 2018 15:39:23 -0700 Subject: [PATCH 084/131] Removed duplicate speech control menu item --- interface/src/Menu.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 1464ec6826..91019975cd 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -788,18 +788,6 @@ Menu::Menu() { // Developer > Show Animation Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats); - // Developer > Scripting > Enable Speech Control API -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) - auto speechRecognizer = DependencyManager::get(); - QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::ControlWithSpeech, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, - speechRecognizer->getEnabled(), - speechRecognizer.data(), - SLOT(setEnabled(bool)), - UNSPECIFIED_POSITION); - connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); -#endif - // Developer > Scripting > Console... addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, DependencyManager::get().data(), From 91f92276864161e9ea93f73014b371b663a1d0b6 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Mon, 22 Oct 2018 15:44:16 -0700 Subject: [PATCH 085/131] Case 19446 - Update via Sandbox Popup -- opens Old Interface --- server-console/src/main.js | 4 ---- server-console/src/modules/hf-notifications.js | 12 ++++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/server-console/src/main.js b/server-console/src/main.js index 5c7913d775..0263f8fd65 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -877,10 +877,6 @@ function onContentLoaded() { hasShownUpdateNotification = true; } }); - notifier.on('click', function(notifierObject, options) { - log.debug("Got click", options.url); - shell.openExternal(options.url); - }); } deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX); diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index b1f337bbc3..e1ea508e47 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -5,6 +5,8 @@ const process = require('process'); const hfApp = require('./hf-app'); const path = require('path'); const AccountInfo = require('./hf-acctinfo').AccountInfo; +const url = require('url'); +const shell = require('electron').shell; const GetBuildInfo = hfApp.getBuildInfo; const buildInfo = GetBuildInfo(); const osType = os.type(); @@ -154,8 +156,14 @@ function HifiNotifications(config, menuNotificationCallback) { var _menuNotificationCallback = menuNotificationCallback; notifier.on('click', function (notifierObject, options) { - StartInterface(options.url); - _menuNotificationCallback(options.notificationType, false); + const optUrl = url.parse(options.url); + if ((optUrl.protocol === "hifi") || (optUrl.protocol === "hifiapp")) { + StartInterface(options.url); + _menuNotificationCallback(options.notificationType, false); + } + else { + shell.openExternal(options.url); + } }); } From d7dce456b269d9bc83c570292d1fb7f35445ff27 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 22 Oct 2018 18:29:27 -0700 Subject: [PATCH 086/131] Fix incorrect default entity properties in Create --- scripts/system/edit.js | 110 +++++++++-------------------------------- 1 file changed, 24 insertions(+), 86 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 71b4b2c54d..a1e8c52d1f 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -299,7 +299,6 @@ function checkEditPermissionsAndUpdate() { const DEFAULT_ENTITY_PROPERTIES = { All: { - collisionless: true, description: "", rotation: { x: 0, y: 0, z: 0, w: 1 }, collidesWith: "static,dynamic,kinematic,otherAvatar", @@ -344,12 +343,22 @@ const DEFAULT_ENTITY_PROPERTIES = { }, Text: { text: "Text", + dimensions: { + x: 0.65, + y: 0.3, + z: 0.01 + }, textColor: { red: 255, green: 255, blue: 255 }, backgroundColor: { red: 0, green: 0, blue: 0 }, lineHeight: 0.06, faceCamera: false, }, Zone: { + dimensions: { + x: 10, + y: 10, + z: 10 + }, flyingAllowed: true, ghostingAllowed: true, filter: "", @@ -359,7 +368,8 @@ const DEFAULT_ENTITY_PROPERTIES = { intensity: 1.0, direction: { x: 0.0, - y: Math.PI / 4, + y: -0.707106769084930, // 45 degrees + z: 0.7071067690849304 }, castShadows: true }, @@ -376,7 +386,7 @@ const DEFAULT_ENTITY_PROPERTIES = { hazeColor: { red: 128, green: 154, - blue: 129 + blue: 179 }, hazeBackgroundBlend: 0, hazeEnableGlare: false, @@ -389,7 +399,6 @@ const DEFAULT_ENTITY_PROPERTIES = { bloomMode: "inherit" }, Model: { - modelURL: "", collisionShape: "none", compoundShapeURL: "", animation: { @@ -413,9 +422,14 @@ const DEFAULT_ENTITY_PROPERTIES = { shapeType: "box", collisionless: true, modelURL: IMAGE_MODEL, - textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }) + textures: JSON.stringify({ "tex.picture": "" }) }, Web: { + dimensions: { + x: 1.6, + y: 0.9, + z: 0.01 + }, sourceUrl: "https://highfidelity.com/", dpi: 30, }, @@ -462,14 +476,15 @@ const DEFAULT_ENTITY_PROPERTIES = { spinFinish: 0, spinSpread: 0, rotateWithEntity: false, - azimuthStart: 0, - azimuthFinish: 0, - polarStart: -Math.PI, - polarFinish: Math.PI + polarStart: 0, + polarFinish: 0, + azimuthStart: -Math.PI, + azimuthFinish: Math.PI }, Light: { color: { red: 255, green: 255, blue: 255 }, intensity: 5.0, + dimensions: DEFAULT_LIGHT_DIMENSIONS, falloffRadius: 1.0, isSpotlight: false, exponent: 1.0, @@ -874,14 +889,12 @@ var toolBar = (function () { addButton("newLightButton", function () { createNewEntity({ type: "Light", - dimensions: DEFAULT_LIGHT_DIMENSIONS, isSpotlight: false, color: { red: 150, green: 150, blue: 150 }, - constantAttenuation: 1, linearAttenuation: 0, quadraticAttenuation: 0, @@ -893,23 +906,6 @@ var toolBar = (function () { addButton("newTextButton", function () { createNewEntity({ type: "Text", - dimensions: { - x: 0.65, - y: 0.3, - z: 0.01 - }, - backgroundColor: { - red: 64, - green: 64, - blue: 64 - }, - textColor: { - red: 255, - green: 255, - blue: 255 - }, - text: "some text", - lineHeight: 0.06 }); }); @@ -922,76 +918,18 @@ var toolBar = (function () { addButton("newWebButton", function () { createNewEntity({ type: "Web", - dimensions: { - x: 1.6, - y: 0.9, - z: 0.01 - }, - sourceUrl: "https://highfidelity.com/" }); }); addButton("newZoneButton", function () { createNewEntity({ type: "Zone", - dimensions: { - x: 10, - y: 10, - z: 10 - } }); }); addButton("newParticleButton", function () { createNewEntity({ type: "ParticleEffect", - isEmitting: true, - emitterShouldTrail: true, - color: { - red: 200, - green: 200, - blue: 200 - }, - colorSpread: { - red: 0, - green: 0, - blue: 0 - }, - colorStart: { - red: 200, - green: 200, - blue: 200 - }, - colorFinish: { - red: 0, - green: 0, - blue: 0 - }, - emitAcceleration: { - x: -0.5, - y: 2.5, - z: -0.5 - }, - accelerationSpread: { - x: 0.5, - y: 1, - z: 0.5 - }, - emitRate: 5.5, - emitSpeed: 0, - speedSpread: 0, - lifespan: 1.5, - maxParticles: 10, - particleRadius: 0.25, - radiusStart: 0, - radiusFinish: 0.1, - radiusSpread: 0, - alpha: 0, - alphaStart: 1, - alphaFinish: 0, - polarStart: 0, - polarFinish: 0, - textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png" }); }); From d5dc93e8002d0349e6248576e841bf5f7dcd3f0f Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 22 Oct 2018 18:31:04 -0700 Subject: [PATCH 087/131] QA fixes and reset server script status --- scripts/system/html/js/entityProperties.js | 95 ++++++++++++---------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d3e31e4b9b..d4202bd6bc 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -52,6 +52,11 @@ const GROUPS = [ propertyID: "id", readOnly: true, }, + { + label: "Description", + type: "string", + propertyID: "description", + }, { label: "Parent", type: "string", @@ -174,21 +179,21 @@ const GROUPS = [ showPropertyRule: { "keyLightMode": "enabled" }, }, { - label: "Light Altitude", - type: "number", - decimals: 2, - unit: "deg", - propertyID: "keyLight.direction.y", - showPropertyRule: { "keyLightMode": "enabled" }, - }, - { - label: "Light Azimuth", + label: "Light Horizontal Angle", type: "number", decimals: 2, unit: "deg", propertyID: "keyLight.direction.x", showPropertyRule: { "keyLightMode": "enabled" }, }, + { + label: "Light Vertical Angle", + type: "number", + decimals: 2, + unit: "deg", + propertyID: "keyLight.direction.y", + showPropertyRule: { "keyLightMode": "enabled" }, + }, { label: "Cast Shadows", type: "bool", @@ -208,18 +213,11 @@ const GROUPS = [ showPropertyRule: { "skyboxMode": "enabled" }, }, { - label: "Skybox URL", + label: "Skybox Source", type: "string", propertyID: "skybox.url", showPropertyRule: { "skyboxMode": "enabled" }, }, - { - type: "buttons", - buttons: [ { id: "copy", label: "Copy URL To Ambient", - className: "black", onClick: copySkyboxURLToAmbientURL } ], - propertyID: "copyURLToAmbient", - showPropertyRule: { "skyboxMode": "enabled" }, - }, { label: "Ambient Light", type: "dropdown", @@ -237,11 +235,18 @@ const GROUPS = [ showPropertyRule: { "ambientLightMode": "enabled" }, }, { - label: "Ambient URL", + label: "Ambient Source", type: "string", propertyID: "ambientLight.ambientURL", showPropertyRule: { "ambientLightMode": "enabled" }, }, + { + type: "buttons", + buttons: [ { id: "copy", label: "Copy from Skybox", + className: "black", onClick: copySkyboxURLToAmbientURL } ], + propertyID: "copyURLToAmbient", + showPropertyRule: { "skyboxMode": "enabled" }, + }, { label: "Haze", type: "dropdown", @@ -395,16 +400,16 @@ const GROUPS = [ type: "bool", propertyID: "animation.running", }, - { - label: "Allow Transition", - type: "bool", - propertyID: "animation.allowTranslation", - }, { label: "Loop", type: "bool", propertyID: "animation.loop", }, + { + label: "Allow Transition", + type: "bool", + propertyID: "animation.allowTranslation", + }, { label: "Hold", type: "bool", @@ -1109,7 +1114,7 @@ const GROUPS = [ column: 1, }, { // below properties having no column number means place them after two columns div - label: "Can cast shadow", + label: "Cast shadows", type: "bool", propertyID: "canCastShadow", }, @@ -1216,18 +1221,18 @@ const GROUPS = [ ]; const GROUPS_PER_TYPE = { - None: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], - Shape: [ 'base', 'shape', 'spatial', 'collision', 'behavior', 'physics' ], - Text: [ 'base', 'text', 'spatial', 'collision', 'behavior', 'physics' ], - Zone: [ 'base', 'zone', 'spatial', 'collision', 'behavior', 'physics' ], - Model: [ 'base', 'model', 'spatial', 'collision', 'behavior', 'physics' ], - Image: [ 'base', 'image', 'spatial', 'collision', 'behavior', 'physics' ], - Web: [ 'base', 'web', 'spatial', 'collision', 'behavior', 'physics' ], - Light: [ 'base', 'light', 'spatial', 'collision', 'behavior', 'physics' ], + None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], + Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'behavior', 'collision', 'physics' ], + Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], + Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], + Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], + Light: [ 'base', 'light', 'spatial', 'behavior', 'collision', 'physics' ], Material: [ 'base', 'material', 'spatial', 'behavior' ], ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], - Multiple: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], + Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], }; const EDITOR_TIMEOUT_DURATION = 1500; @@ -1242,6 +1247,15 @@ const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MATERIAL_PREFIX_STRING = "mat::"; const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; +const NOT_RUNNING_SCRIPT_STATUS = "Not running"; +const ENTITY_SCRIPT_STATUS = { + pending: "Pending", + loading: "Loading", + error_loading_script: "Error loading script", // eslint-disable-line camelcase + error_running_script: "Error running script", // eslint-disable-line camelcase + running: "Running", + unloaded: "Unloaded" +}; const PROPERTY_NAME_DIVISION = { GROUP: 0, @@ -1441,6 +1455,11 @@ function resetProperties() { } } } + + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + elServerScriptError.parentElement.style.display = "none"; + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; } function showGroupsForType(type) { @@ -2796,17 +2815,9 @@ function loaded() { if (data.statusRetrieved === false) { elServerScriptStatus.innerText = "Failed to retrieve status"; } else if (data.isRunning) { - var ENTITY_SCRIPT_STATUS = { - pending: "Pending", - loading: "Loading", - error_loading_script: "Error loading script", // eslint-disable-line camelcase - error_running_script: "Error running script", // eslint-disable-line camelcase - running: "Running", - unloaded: "Unloaded" - }; elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status; } else { - elServerScriptStatus.innerText = "Not running"; + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; } } else if (data.type === "update" && data.selections) { if (data.selections.length === 0) { From 9afb111fbeb5232c56dcc438ea25e7779ea8f71f Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 22 Oct 2018 18:42:06 -0700 Subject: [PATCH 088/131] add link, fix group order --- scripts/system/html/js/entityProperties.js | 187 +++++++++++---------- 1 file changed, 96 insertions(+), 91 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d4202bd6bc..e5bb80ef2c 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -969,6 +969,102 @@ const GROUPS = [ }, ] }, + { + id: "behavior", + label: "BEHAVIOR", + twoColumn: true, + properties: [ + { + label: "Grabbable", + type: "bool", + propertyID: "grab.grabbable", + column: 1, + }, + { + label: "Triggerable", + type: "bool", + propertyID: "grab.triggerable", + column: 2, + }, + { + label: "Cloneable", + type: "bool", + propertyID: "cloneable", + column: 1, + }, + { + label: "Follow Controller", + type: "bool", + propertyID: "grab.grabFollowsController", + column: 2, + }, + { + label: "Clone Lifetime", + type: "number", + unit: "s", + propertyID: "cloneLifetime", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Limit", + type: "number", + propertyID: "cloneLimit", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Dynamic", + type: "bool", + propertyID: "cloneDynamic", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Avatar Entity", + type: "bool", + propertyID: "cloneAvatarEntity", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { // below properties having no column number means place them after two columns div + label: "Cast shadows", + type: "bool", + propertyID: "canCastShadow", + }, + { + label: "Link", + type: "string", + propertyID: "href", + }, + { + label: "Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + propertyID: "script", + }, + { + label: "Server Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + propertyID: "serverScripts", + }, + { + label: "Lifetime", + type: "number", + unit: "s", + propertyID: "lifetime", + }, + { + label: "User Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + propertyID: "userData", + }, + ] + }, { id: "collision", label: "COLLISION", @@ -1055,97 +1151,6 @@ const GROUPS = [ }, ] }, - { - id: "behavior", - label: "BEHAVIOR", - twoColumn: true, - properties: [ - { - label: "Grabbable", - type: "bool", - propertyID: "grab.grabbable", - column: 1, - }, - { - label: "Triggerable", - type: "bool", - propertyID: "grab.triggerable", - column: 2, - }, - { - label: "Cloneable", - type: "bool", - propertyID: "cloneable", - column: 1, - }, - { - label: "Follow Controller", - type: "bool", - propertyID: "grab.grabFollowsController", - column: 2, - }, - { - label: "Clone Lifetime", - type: "number", - unit: "s", - propertyID: "cloneLifetime", - showPropertyRule: { "cloneable": "true" }, - column: 1, - }, - { - label: "Clone Limit", - type: "number", - propertyID: "cloneLimit", - showPropertyRule: { "cloneable": "true" }, - column: 1, - }, - { - label: "Clone Dynamic", - type: "bool", - propertyID: "cloneDynamic", - showPropertyRule: { "cloneable": "true" }, - column: 1, - }, - { - label: "Clone Avatar Entity", - type: "bool", - propertyID: "cloneAvatarEntity", - showPropertyRule: { "cloneable": "true" }, - column: 1, - }, - { // below properties having no column number means place them after two columns div - label: "Cast shadows", - type: "bool", - propertyID: "canCastShadow", - }, - { - label: "Script", - type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], - propertyID: "script", - }, - { - label: "Server Script", - type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], - propertyID: "serverScripts", - }, - { - label: "Lifetime", - type: "number", - unit: "s", - propertyID: "lifetime", - }, - { - label: "User Data", - type: "textarea", - buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, - { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], - propertyID: "userData", - }, - ] - }, { id: "physics", label: "PHYSICS", From fa67e1b269fa2c569e478fc68b42b0c8f7377dc6 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 23 Oct 2018 11:47:50 -0700 Subject: [PATCH 089/131] Remove logs and magic numbers --- interface/src/avatar/AvatarManager.cpp | 4 ---- libraries/animation/src/Rig.cpp | 5 +++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e12ea67230..dad4c44f4b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -127,19 +127,15 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { switch (status) { case AvatarTransit::Status::STARTED: - qDebug() << "START_FRAME"; _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("preTransitAnim"); break; case AvatarTransit::Status::START_TRANSIT: - qDebug() << "START_TRANSIT"; _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("transitAnim"); break; case AvatarTransit::Status::END_TRANSIT: - qDebug() << "END_TRANSIT"; _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("postTransitAnim"); break; case AvatarTransit::Status::ENDED: - qDebug() << "END_FRAME"; _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("idleAnim"); break; case AvatarTransit::Status::PRE_TRANSIT: diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d198550d9a..83d375b55a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1124,17 +1124,18 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); if (_networkNode) { _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + const float NETWORK_ANIMATION_BLEND_FRAMES = 6.0f; float alpha = 1.0f; std::shared_ptr clip; if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { clip = std::dynamic_pointer_cast(_networkNode->findByName("preTransitAnim")); if (clip) { - alpha = (clip->getFrame() - clip->getStartFrame()) / 6.0f; + alpha = (clip->getFrame() - clip->getStartFrame()) / NETWORK_ANIMATION_BLEND_FRAMES; } } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { clip = std::dynamic_pointer_cast(_networkNode->findByName("postTransitAnim")); if (clip) { - alpha = (clip->getEndFrame() - clip->getFrame()) / 6.0f; + alpha = (clip->getEndFrame() - clip->getFrame()) / NETWORK_ANIMATION_BLEND_FRAMES; } } if (_sendNetworkNode) { From 178b8ef37b9dfbccecbb56913378128508277d21 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Tue, 23 Oct 2018 12:55:02 -0700 Subject: [PATCH 090/131] Removed duplicate menu items Removed duplicates: Console... API Debugger --- interface/src/Menu.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 91019975cd..5e1c9412e8 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -788,21 +788,6 @@ Menu::Menu() { // Developer > Show Animation Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats); - // Developer > Scripting > Console... - addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, - DependencyManager::get().data(), - SLOT(toggleConsole()), - QAction::NoRole, - UNSPECIFIED_POSITION); - - // Developer > Scripting > API Debugger - action = addActionToQMenuAndActionHash(scriptingOptionsMenu, "API Debugger"); - connect(action, &QAction::triggered, [] { - QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); - DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); - }); - // Developer > Log addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, qApp, SLOT(toggleLogDialog())); From be7a947ddd85689e67374d6301076e4a2c814044 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Tue, 23 Oct 2018 13:25:59 -0700 Subject: [PATCH 091/131] Fix coding-standards issue --- server-console/src/modules/hf-notifications.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index e1ea508e47..3ee2bd13a4 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -160,8 +160,7 @@ function HifiNotifications(config, menuNotificationCallback) { if ((optUrl.protocol === "hifi") || (optUrl.protocol === "hifiapp")) { StartInterface(options.url); _menuNotificationCallback(options.notificationType, false); - } - else { + } else { shell.openExternal(options.url); } }); From 8807b90474cbb79122de3a4a1d3e720306cd4f9e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 23 Oct 2018 13:33:14 -0700 Subject: [PATCH 092/131] fix emitRadiusStart --- .../entities/src/ParticleEffectEntityItem.cpp | 27 ++++++++++--------- .../entities/src/ParticleEffectEntityItem.h | 3 ++- .../particle_explorer/particleExplorer.js | 7 +++++ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 46dcd1b006..acdeac0e93 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -90,10 +90,10 @@ bool operator==(const Properties& a, const Properties& b) { return (a.color == b.color) && (a.alpha == b.alpha) && + (a.radiusStart == b.radiusStart) && (a.radius == b.radius) && (a.spin == b.spin) && (a.rotateWithEntity == b.rotateWithEntity) && - (a.radiusStart == b.radiusStart) && (a.lifespan == b.lifespan) && (a.maxParticles == b.maxParticles) && (a.emission == b.emission) && @@ -117,18 +117,7 @@ bool Properties::valid() const { (alpha.range.start == glm::clamp(alpha.range.start, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && (alpha.range.finish == glm::clamp(alpha.range.finish, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && (alpha.gradient.spread == glm::clamp(alpha.gradient.spread, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && - (lifespan == glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN)) && - (emission.rate == glm::clamp(emission.rate, MINIMUM_EMIT_RATE, MAXIMUM_EMIT_RATE)) && - (emission.speed.target == glm::clamp(emission.speed.target, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && - (emission.speed.spread == glm::clamp(emission.speed.spread, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && - (emission.dimensions == glm::clamp(emission.dimensions, vec3(MINIMUM_EMIT_DIMENSION), vec3(MAXIMUM_EMIT_DIMENSION))) && (radiusStart == glm::clamp(radiusStart, MINIMUM_EMIT_RADIUS_START, MAXIMUM_EMIT_RADIUS_START)) && - (polar.start == glm::clamp(polar.start, MINIMUM_POLAR, MAXIMUM_POLAR)) && - (polar.finish == glm::clamp(polar.finish, MINIMUM_POLAR, MAXIMUM_POLAR)) && - (azimuth.start == glm::clamp(azimuth.start, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && - (azimuth.finish == glm::clamp(azimuth.finish, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && - (emission.acceleration.target == glm::clamp(emission.acceleration.target, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION))) && - (emission.acceleration.spread == glm::clamp(emission.acceleration.spread, vec3(MINIMUM_ACCELERATION_SPREAD), vec3(MAXIMUM_ACCELERATION_SPREAD))) && (radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && @@ -136,7 +125,19 @@ bool Properties::valid() const { (spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && (spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && (spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && - (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)); + (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (lifespan == glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN)) && + (maxParticles == glm::clamp(maxParticles, MINIMUM_MAX_PARTICLES, MAXIMUM_MAX_PARTICLES)) && + (emission.rate == glm::clamp(emission.rate, MINIMUM_EMIT_RATE, MAXIMUM_EMIT_RATE)) && + (emission.speed.target == glm::clamp(emission.speed.target, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && + (emission.speed.spread == glm::clamp(emission.speed.spread, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && + (emission.acceleration.target == glm::clamp(emission.acceleration.target, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION))) && + (emission.acceleration.spread == glm::clamp(emission.acceleration.spread, vec3(MINIMUM_ACCELERATION_SPREAD), vec3(MAXIMUM_ACCELERATION_SPREAD)) && + (emission.dimensions == glm::clamp(emission.dimensions, vec3(MINIMUM_EMIT_DIMENSION), vec3(MAXIMUM_EMIT_DIMENSION))) && + (polar.start == glm::clamp(polar.start, MINIMUM_POLAR, MAXIMUM_POLAR)) && + (polar.finish == glm::clamp(polar.finish, MINIMUM_POLAR, MAXIMUM_POLAR)) && + (azimuth.start == glm::clamp(azimuth.start, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && + (azimuth.finish == glm::clamp(azimuth.finish, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH))); } bool Properties::emitting() const { diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index a89d7afc06..89f1e834ea 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -177,9 +177,10 @@ namespace particle { Properties& operator =(const Properties& other) { color = other.color; alpha = other.alpha; + radiusStart = other.radiusStart; + radius = other.radius; spin = other.spin; rotateWithEntity = other.rotateWithEntity; - radius = other.radius; lifespan = other.lifespan; maxParticles = other.maxParticles; emission = other.emission; diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index f1b7c8600f..b4b888122c 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -170,6 +170,13 @@ step: 0.01 } }, + { + id: "emitRadiusStart", + name: "Emit Radius Start", + type: "SliderFloat", + max: 1, + min: 0 + }, { type: "Row" }, From 4c3f5b3bbcd0326a68cdff02b07593fa9242aa88 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 11 Oct 2018 18:43:56 +0200 Subject: [PATCH 093/131] Entity List Context Menu --- scripts/system/html/css/edit-style.css | 39 +++++ scripts/system/html/entityList.html | 1 + scripts/system/html/js/entityList.js | 94 +++++++++++- .../system/html/js/entityListContextMenu.js | 143 ++++++++++++++++++ scripts/system/libraries/entityList.js | 14 +- 5 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 scripts/system/html/js/entityListContextMenu.js diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8e7b3f1ad5..e7e577a13b 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1801,3 +1801,42 @@ input[type=button]#export { body#entity-list-body { padding-bottom: 0; } + +.context-menu { + display: none; + position: fixed; + color: #000000; + background-color: #afafaf; + padding: 5px 0 5px 0; + cursor: default; +} +.context-menu li { + list-style-type: none; + padding: 0 5px 0 5px; + margin: 0; + white-space: nowrap; +} +.context-menu li:hover { + background-color: #e3e3e3; +} +.context-menu li.separator { + border-top: 1px solid #000000; + margin: 5px 5px; +} +.context-menu li.disabled { + color: #333333; +} +.context-menu li.separator:hover, .context-menu li.disabled:hover { + background-color: #afafaf; +} + +input.rename-entity { + height: 100%; + width: 100%; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; + /* need this to show the text cursor when the input field is empty */ + padding-left: 2px; +} + diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index c62c785c99..ae994b56a4 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -16,6 +16,7 @@ + diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0ced016d26..67187e6a3e 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -70,6 +70,8 @@ var selectedEntities = []; var entityList = null; // The ListView +var entityListContextMenu = new EntityListContextMenu(); + var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; var isFilterInView = false; @@ -184,7 +186,83 @@ function loaded() { entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT); - + + entityListContextMenu.initialize(); + + + function startRenamingEntity(entityID) { + if (!entitiesByID[entityID] || !entitiesByID[entityID].elRow) { + return; + } + + let elCell = entitiesByID[entityID].elRow.childNodes[COLUMN_INDEX.NAME]; + let elRenameInput = document.createElement("input"); + elRenameInput.setAttribute('class', 'rename-entity'); + elRenameInput.value = entitiesByID[entityID].name; + elRenameInput.onclick = function(event) { + event.stopPropagation(); + }; + elRenameInput.onkeyup = function(keyEvent) { + if (keyEvent.key === "Enter") { + elRenameInput.blur(); + } + }; + + elRenameInput.onblur = function(event) { + let value = elRenameInput.value; + EventBridge.emitWebEvent(JSON.stringify({ + type: 'rename', + entityID: entityID, + name: value + })); + entitiesByID[entityID].name = value; + elCell.innerText = value; + }; + + elCell.innerHTML = ""; + elCell.appendChild(elRenameInput); + + elRenameInput.select(); + } + + entityListContextMenu.setCallback(function(optionName, selectedEntityID) { + switch (optionName) { + case "Copy": + EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' })); + break; + case "Paste": + EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' })); + break; + case "Rename": + startRenamingEntity(selectedEntityID); + break; + case "Duplicate": + EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' })); + break; + case "Delete": + EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); + break; + } + }); + + function onRowContextMenu(clickEvent) { + let entityID = this.dataset.entityID; + + if (!selectedEntities.includes(entityID)) { + let selection = [entityID]; + updateSelectedEntities(selection); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + + refreshFooter(); + } + entityListContextMenu.open(clickEvent, entityID); + } + function onRowClicked(clickEvent) { let entityID = this.dataset.entityID; let selection = [entityID]; @@ -221,9 +299,9 @@ function loaded() { } } } else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) { - // if reselecting the same entity then deselect it + // if reselecting the same entity then start renaming it if (selectedEntities[0] === entityID) { - selection = []; + startRenamingEntity(entityID); } } @@ -502,6 +580,7 @@ function loaded() { } row.appendChild(column); } + row.oncontextmenu = onRowContextMenu; row.onclick = onRowClicked; row.ondblclick = onRowDoubleClicked; return row; @@ -672,8 +751,15 @@ function loaded() { augmentSpinButtons(); - // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function (event) { + entityListContextMenu.close.call(entityListContextMenu); + + // Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked event.preventDefault(); }, false); + + // close context menu when switching focus to another window + $(window).blur(function(){ + entityListContextMenu.close.call(entityListContextMenu); + }); } diff --git a/scripts/system/html/js/entityListContextMenu.js b/scripts/system/html/js/entityListContextMenu.js new file mode 100644 index 0000000000..59ae2f1f73 --- /dev/null +++ b/scripts/system/html/js/entityListContextMenu.js @@ -0,0 +1,143 @@ +// +// entityListContextMenu.js +// +// exampleContextMenus.js was originally created by David Rowe on 22 Aug 2018. +// Modified to entityListContextMenu.js by Thijs Wenker on 10 Oct 2018 +// Copyright 2018 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 +// + +/* eslint-env browser */ +const CONTEXT_MENU_CLASS = "context-menu"; + +/** + * ContextMenu class for EntityList + * @constructor + */ +function EntityListContextMenu() { + this._elContextMenu = null; + this._callback = null; +} + +EntityListContextMenu.prototype = { + + /** + * @private + */ + _elContextMenu: null, + + /** + * @private + */ + _callback: null, + + /** + * @private + */ + _selectedEntityID: null, + + /** + * Close the context menu + */ + close: function() { + if (this.isContextMenuOpen()) { + this._elContextMenu.style.display = "none"; + } + }, + + isContextMenuOpen: function() { + return this._elContextMenu.style.display === "block"; + }, + + /** + * Open the context menu + * @param clickEvent + * @param selectedEntityID + */ + open: function(clickEvent, selectedEntityID) { + this._selectedEntityID = selectedEntityID; + // If the right-clicked item has a context menu open it. + this._elContextMenu.style.display = "block"; + this._elContextMenu.style.left + = Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px"; + this._elContextMenu.style.top + = Math.min(clickEvent.pageY, document.body.clientHeight - this._elContextMenu.offsetHeight).toString() + "px"; + clickEvent.stopPropagation(); + }, + + /** + * Set the event callback + * @param callback + */ + setCallback: function(callback) { + this._callback = callback; + }, + + /** + * Add a labeled item to the context menu + * @param itemLabel + * @param isEnabled + * @private + */ + _addListItem: function(itemLabel, isEnabled) { + let elListItem = document.createElement("li"); + elListItem.innerText = itemLabel; + + if (isEnabled === undefined || isEnabled) { + elListItem.addEventListener("click", function () { + if (this._callback) { + this._callback.call(this, itemLabel, this._selectedEntityID); + } + }.bind(this), false); + } else { + elListItem.setAttribute('class', 'disabled'); + } + this._elContextMenu.appendChild(elListItem); + }, + + /** + * Add a separator item to the context menu + * @private + */ + _addListSeparator: function() { + let elListItem = document.createElement("li"); + elListItem.setAttribute('class', 'separator'); + this._elContextMenu.appendChild(elListItem); + }, + + /** + * Initialize the context menu. + */ + initialize: function() { + this._elContextMenu = document.createElement("ul"); + this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); + document.body.appendChild(this._elContextMenu); + + // TODO: enable Copy, Paste and Duplicate items once implemented + this._addListItem("Copy", false); + this._addListItem("Paste", false); + this._addListSeparator(); + this._addListItem("Rename"); + this._addListItem("Duplicate", false); + this._addListItem("Delete"); + + // Ignore clicks on context menu background or separator. + this._elContextMenu.addEventListener("click", function(event) { + // Sink clicks on context menu background or separator but let context menu item clicks through. + if (event.target.classList.contains(CONTEXT_MENU_CLASS)) { + event.stopPropagation(); + } + }); + + // Provide means to close context menu without clicking menu item. + document.body.addEventListener("click", this.close.bind(this)); + document.body.addEventListener("keydown", function(event) { + // Close context menu with Esc key. + if (this.isContextMenuOpen() && event.key === "Escape") { + this.close(); + } + }.bind(this)); + } +}; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 30e952723f..d3d1d76725 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -15,7 +15,7 @@ var PROFILING_ENABLED = false; var profileIndent = ''; const PROFILE_NOOP = function(_name, fn, args) { fn.apply(this, args); -} ; +}; PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); var previousIndent = profileIndent; @@ -245,7 +245,7 @@ EntityListTool = function(shouldUseEditTabletApp) { Window.saveAsync("Select Where to Save", "", "*.json"); } } else if (data.type === "pal") { - var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates. + var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates. selectionManager.selections.forEach(function (id) { var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy; if (lastEditedBy) { @@ -271,6 +271,16 @@ EntityListTool = function(shouldUseEditTabletApp) { filterInView = data.filterInView === true; } else if (data.type === "radius") { searchRadius = data.radius; + } else if (data.type === "copy") { + Window.alert("Copy is not yet implemented."); + } else if (data.type === "paste") { + Window.alert("Paste is not yet implemented."); + } else if (data.type === "duplicate") { + Window.alert("Duplicate is not yet implemented."); + } else if (data.type === "rename") { + Entities.editEntity(data.entityID, {name: data.name}); + // make sure that the name also gets updated in the properties window + SelectionManager._update(); } }; From e5ef589a3cc24d0f18633ce7302ea7127209acf3 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 12 Oct 2018 21:15:25 +0200 Subject: [PATCH 094/131] style --- scripts/system/html/css/edit-style.css | 5 ++- scripts/system/html/js/entityList.js | 33 +++++++++----- .../system/html/js/entityListContextMenu.js | 45 ++++++++++++++----- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index e7e577a13b..956ae529ab 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1812,7 +1812,7 @@ body#entity-list-body { } .context-menu li { list-style-type: none; - padding: 0 5px 0 5px; + padding: 4px 18px 4px 18px; margin: 0; white-space: nowrap; } @@ -1820,8 +1820,9 @@ body#entity-list-body { background-color: #e3e3e3; } .context-menu li.separator { - border-top: 1px solid #000000; + border-top: 1px solid #333333; margin: 5px 5px; + padding: 0 0; } .context-menu li.disabled { color: #333333; diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 67187e6a3e..749f750ecb 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -70,7 +70,10 @@ var selectedEntities = []; var entityList = null; // The ListView -var entityListContextMenu = new EntityListContextMenu(); +/** + * @type EntityListContextMenu + */ +var entityListContextMenu = null; var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; @@ -187,21 +190,24 @@ function loaded() { entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT); - entityListContextMenu.initialize(); + entityListContextMenu = new EntityListContextMenu(); function startRenamingEntity(entityID) { - if (!entitiesByID[entityID] || !entitiesByID[entityID].elRow) { + let entity = entitiesByID[entityID]; + if (!entity || entity.locked || !entity.elRow) { return; } - let elCell = entitiesByID[entityID].elRow.childNodes[COLUMN_INDEX.NAME]; + let elCell = entity.elRow.childNodes[COLUMN_INDEX.NAME]; let elRenameInput = document.createElement("input"); elRenameInput.setAttribute('class', 'rename-entity'); - elRenameInput.value = entitiesByID[entityID].name; - elRenameInput.onclick = function(event) { + elRenameInput.value = entity.name; + let ignoreClicks = function(event) { event.stopPropagation(); }; + elRenameInput.onclick = ignoreClicks; + elRenameInput.ondblclick = ignoreClicks; elRenameInput.onkeyup = function(keyEvent) { if (keyEvent.key === "Enter") { elRenameInput.blur(); @@ -215,7 +221,7 @@ function loaded() { entityID: entityID, name: value })); - entitiesByID[entityID].name = value; + entity.name = value; elCell.innerText = value; }; @@ -260,13 +266,20 @@ function loaded() { refreshFooter(); } - entityListContextMenu.open(clickEvent, entityID); + + let enabledContextMenuItems = []; + if (entitiesByID[entityID] && !entitiesByID[entityID].locked) { + enabledContextMenuItems.push('Rename'); + enabledContextMenuItems.push('Delete'); + } + + entityListContextMenu.open(clickEvent, entityID, enabledContextMenuItems); } function onRowClicked(clickEvent) { let entityID = this.dataset.entityID; let selection = [entityID]; - + if (clickEvent.ctrlKey) { let selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { @@ -759,7 +772,7 @@ function loaded() { }, false); // close context menu when switching focus to another window - $(window).blur(function(){ + $(window).blur(function() { entityListContextMenu.close.call(entityListContextMenu); }); } diff --git a/scripts/system/html/js/entityListContextMenu.js b/scripts/system/html/js/entityListContextMenu.js index 59ae2f1f73..62fe951ebe 100644 --- a/scripts/system/html/js/entityListContextMenu.js +++ b/scripts/system/html/js/entityListContextMenu.js @@ -19,6 +19,8 @@ const CONTEXT_MENU_CLASS = "context-menu"; function EntityListContextMenu() { this._elContextMenu = null; this._callback = null; + this._listItems = []; + this._initialize(); } EntityListContextMenu.prototype = { @@ -38,6 +40,11 @@ EntityListContextMenu.prototype = { */ _selectedEntityID: null, + /** + * @private + */ + _listItems: null, + /** * Close the context menu */ @@ -55,10 +62,17 @@ EntityListContextMenu.prototype = { * Open the context menu * @param clickEvent * @param selectedEntityID + * @param enabledOptions */ - open: function(clickEvent, selectedEntityID) { + open: function(clickEvent, selectedEntityID, enabledOptions) { this._selectedEntityID = selectedEntityID; - // If the right-clicked item has a context menu open it. + + this._listItems.forEach(function(listItem) { + let enabled = enabledOptions.includes(listItem.label); + listItem.enabled = enabled; + listItem.element.setAttribute('class', enabled ? '' : 'disabled'); + }); + this._elContextMenu.style.display = "block"; this._elContextMenu.style.left = Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px"; @@ -85,15 +99,21 @@ EntityListContextMenu.prototype = { let elListItem = document.createElement("li"); elListItem.innerText = itemLabel; - if (isEnabled === undefined || isEnabled) { - elListItem.addEventListener("click", function () { - if (this._callback) { - this._callback.call(this, itemLabel, this._selectedEntityID); - } - }.bind(this), false); - } else { - elListItem.setAttribute('class', 'disabled'); - } + let listItem = { + label: itemLabel, + element: elListItem, + enabled: false + }; + + elListItem.addEventListener("click", function () { + if (listItem.enabled && this._callback) { + this._callback.call(this, itemLabel, this._selectedEntityID); + } + }.bind(this), false); + + elListItem.setAttribute('class', 'disabled'); + + this._listItems.push(listItem); this._elContextMenu.appendChild(elListItem); }, @@ -109,8 +129,9 @@ EntityListContextMenu.prototype = { /** * Initialize the context menu. + * @private */ - initialize: function() { + _initialize: function() { this._elContextMenu = document.createElement("ul"); this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); document.body.appendChild(this._elContextMenu); From 4069ca2e0c39a51d1b3eb911d62a19439c3c0b9d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 20 Oct 2018 02:43:41 +0200 Subject: [PATCH 095/131] CR feedback --- scripts/system/html/js/entityList.js | 11 ++++---- .../system/html/js/entityListContextMenu.js | 27 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 749f750ecb..663acb758c 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -231,8 +231,11 @@ function loaded() { elRenameInput.select(); } - entityListContextMenu.setCallback(function(optionName, selectedEntityID) { + entityListContextMenu.setOnSelectedCallback(function(optionName, selectedEntityID) { switch (optionName) { + case "Cut": + EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' })); + break; case "Copy": EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' })); break; @@ -263,8 +266,6 @@ function loaded() { focus: false, entityIds: selection, })); - - refreshFooter(); } let enabledContextMenuItems = []; @@ -765,7 +766,7 @@ function loaded() { augmentSpinButtons(); document.addEventListener("contextmenu", function (event) { - entityListContextMenu.close.call(entityListContextMenu); + entityListContextMenu.close(); // Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked event.preventDefault(); @@ -773,6 +774,6 @@ function loaded() { // close context menu when switching focus to another window $(window).blur(function() { - entityListContextMenu.close.call(entityListContextMenu); + entityListContextMenu.close(); }); } diff --git a/scripts/system/html/js/entityListContextMenu.js b/scripts/system/html/js/entityListContextMenu.js index 62fe951ebe..d71719f252 100644 --- a/scripts/system/html/js/entityListContextMenu.js +++ b/scripts/system/html/js/entityListContextMenu.js @@ -18,7 +18,7 @@ const CONTEXT_MENU_CLASS = "context-menu"; */ function EntityListContextMenu() { this._elContextMenu = null; - this._callback = null; + this._onSelectedCallback = null; this._listItems = []; this._initialize(); } @@ -33,7 +33,7 @@ EntityListContextMenu.prototype = { /** * @private */ - _callback: null, + _onSelectedCallback: null, /** * @private @@ -82,20 +82,19 @@ EntityListContextMenu.prototype = { }, /** - * Set the event callback - * @param callback + * Set the callback for when a menu item is selected + * @param onSelectedCallback */ - setCallback: function(callback) { - this._callback = callback; + setOnSelectedCallback: function(onSelectedCallback) { + this._onSelectedCallback = onSelectedCallback; }, /** * Add a labeled item to the context menu * @param itemLabel - * @param isEnabled * @private */ - _addListItem: function(itemLabel, isEnabled) { + _addListItem: function(itemLabel) { let elListItem = document.createElement("li"); elListItem.innerText = itemLabel; @@ -106,8 +105,8 @@ EntityListContextMenu.prototype = { }; elListItem.addEventListener("click", function () { - if (listItem.enabled && this._callback) { - this._callback.call(this, itemLabel, this._selectedEntityID); + if (listItem.enabled && this._onSelectedCallback) { + this._onSelectedCallback.call(this, itemLabel, this._selectedEntityID); } }.bind(this), false); @@ -136,12 +135,12 @@ EntityListContextMenu.prototype = { this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); document.body.appendChild(this._elContextMenu); - // TODO: enable Copy, Paste and Duplicate items once implemented - this._addListItem("Copy", false); - this._addListItem("Paste", false); + this._addListItem("Cut"); + this._addListItem("Copy"); + this._addListItem("Paste"); this._addListSeparator(); this._addListItem("Rename"); - this._addListItem("Duplicate", false); + this._addListItem("Duplicate"); this._addListItem("Delete"); // Ignore clicks on context menu background or separator. From 3fa4ee0e1ddede8e7905200a179645c750f8cfc8 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 20 Oct 2018 03:25:52 +0200 Subject: [PATCH 096/131] enable duplicate copy cut paste functionality of context menu --- scripts/system/html/js/entityList.js | 3 ++- scripts/system/libraries/entityList.js | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 663acb758c..d01e68cff2 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -268,8 +268,9 @@ function loaded() { })); } - let enabledContextMenuItems = []; + let enabledContextMenuItems = ['Copy', 'Paste', 'Duplicate']; if (entitiesByID[entityID] && !entitiesByID[entityID].locked) { + enabledContextMenuItems.push('Cut'); enabledContextMenuItems.push('Rename'); enabledContextMenuItems.push('Delete'); } diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index d3d1d76725..3d73f2f115 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -271,12 +271,15 @@ EntityListTool = function(shouldUseEditTabletApp) { filterInView = data.filterInView === true; } else if (data.type === "radius") { searchRadius = data.radius; + } else if (data.type === "cut") { + cutSelectedEntities(); } else if (data.type === "copy") { - Window.alert("Copy is not yet implemented."); + copySelectedEntities(); } else if (data.type === "paste") { - Window.alert("Paste is not yet implemented."); + pasteEntities(); } else if (data.type === "duplicate") { - Window.alert("Duplicate is not yet implemented."); + SelectionManager.duplicateSelection(); + that.sendUpdate(); } else if (data.type === "rename") { Entities.editEntity(data.entityID, {name: data.name}); // make sure that the name also gets updated in the properties window From 4e9f9b3edf9960af9ea82eb784c7cecd631c94e2 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Oct 2018 22:39:40 +0200 Subject: [PATCH 097/131] comply with copy/paste update --- scripts/system/libraries/entityList.js | 6 +++--- scripts/system/libraries/entitySelectionTool.js | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 3d73f2f115..da31ef8463 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -272,11 +272,11 @@ EntityListTool = function(shouldUseEditTabletApp) { } else if (data.type === "radius") { searchRadius = data.radius; } else if (data.type === "cut") { - cutSelectedEntities(); + SelectionManager.cutSelectedEntities(); } else if (data.type === "copy") { - copySelectedEntities(); + SelectionManager.copySelectedEntities(); } else if (data.type === "paste") { - pasteEntities(); + SelectionManager.pasteEntities(); } else if (data.type === "duplicate") { SelectionManager.duplicateSelection(); that.sendUpdate(); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 843d3e986f..a117df6fc7 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -353,12 +353,12 @@ SelectionManager = (function() { } return createdEntityIDs; - } + }; that.cutSelectedEntities = function() { - copySelectedEntities(); + that.copySelectedEntities(); deleteSelectedEntities(); - } + }; that.copySelectedEntities = function() { var entityProperties = Entities.getMultipleEntityProperties(that.selections); @@ -434,7 +434,7 @@ SelectionManager = (function() { z: brn.z + entityClipboard.dimensions.z / 2 }; } - } + }; that.pasteEntities = function() { var dimensions = entityClipboard.dimensions; @@ -442,7 +442,7 @@ SelectionManager = (function() { var pastePosition = getPositionToCreateEntity(maxDimension); var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); - var copiedProperties = [] + var copiedProperties = []; var ids = []; entityClipboard.entities.forEach(function(originalProperties) { var properties = deepCopy(originalProperties); @@ -475,7 +475,7 @@ SelectionManager = (function() { redo(copiedProperties); undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); - } + }; that._update = function(selectionUpdated) { var properties = null; From 68ab5475feb10472c8d1e8ab612d492e72b077c2 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Tue, 23 Oct 2018 14:13:09 -0700 Subject: [PATCH 098/131] Remove duplicate display crash options --- interface/src/Menu.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 5e1c9412e8..c4835f9741 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -742,9 +742,6 @@ Menu::Menu() { // Developer > Crash >>> MenuWrapper* crashMenu = developerMenu->addMenu("Crash"); - - // Developer > Crash > Display Crash Options - addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true); // Developer > Crash > Display Crash Options addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true); From 10ee5ac5b422144a8ac443233f1dcb3ff75f6b02 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Tue, 23 Oct 2018 14:46:13 -0700 Subject: [PATCH 099/131] URL protocol returns the protocol string with the ':' attached (how did this work before?!) --- server-console/src/modules/hf-notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index 3ee2bd13a4..8a812625b4 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -157,7 +157,7 @@ function HifiNotifications(config, menuNotificationCallback) { var _menuNotificationCallback = menuNotificationCallback; notifier.on('click', function (notifierObject, options) { const optUrl = url.parse(options.url); - if ((optUrl.protocol === "hifi") || (optUrl.protocol === "hifiapp")) { + if ((optUrl.protocol === "hifi:") || (optUrl.protocol === "hifiapp:")) { StartInterface(options.url); _menuNotificationCallback(options.notificationType, false); } else { From 2b9dcd0875324a53b67d3cb1b1c2224f6938c7ee Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 23 Oct 2018 18:38:05 -0700 Subject: [PATCH 100/131] fix particle properties and keylight direction, remove columns, fix orderings, fix sliders --- scripts/system/edit.js | 31 +++- scripts/system/html/css/edit-style.css | 93 +++++----- scripts/system/html/js/entityProperties.js | 197 ++++++++++++--------- 3 files changed, 186 insertions(+), 135 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 53df4176fe..739229e7a7 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2182,10 +2182,14 @@ var PropertiesTool = function (opts) { if (entity.properties.rotation !== undefined) { entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); } + if (entity.properties.emitOrientation !== undefined) { + entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); + } if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { - entity.properties.keyLight.direction = Vec3.multiply(RADIANS_TO_DEGREES, - Vec3.toPolar(entity.properties.keyLight.direction)); + print("DBACK TEST entity.properties.keyLight.direction pre " + entity.properties.keyLight.direction.x + " " + entity.properties.keyLight.direction.y + " " + entity.properties.keyLight.direction.z); + entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); entity.properties.keyLight.direction.z = 0.0; + print("DBACK TEST entity.properties.keyLight.direction post " + entity.properties.keyLight.direction.x + " " + entity.properties.keyLight.direction.y + " " + entity.properties.keyLight.direction.z); } selections.push(entity); } @@ -2220,18 +2224,27 @@ var PropertiesTool = function (opts) { data.properties.angularVelocity = Vec3.ZERO; } if (data.properties.rotation !== undefined) { - var rotation = data.properties.rotation; - data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z); + data.properties.rotation = Quat.fromVec3Degrees(data.properties.rotation); + } + if (data.properties.emitOrientation !== undefined) { + data.properties.emitOrientation = Quat.fromVec3Degrees(data.properties.emitOrientation); } if (data.properties.keyLight !== undefined && data.properties.keyLight.direction !== undefined) { - data.properties.keyLight.direction = Vec3.fromPolar( - data.properties.keyLight.direction.x * DEGREES_TO_RADIANS, - data.properties.keyLight.direction.y * DEGREES_TO_RADIANS - ); + var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); + print("DBACK TEST data.properties.keyLight.direction pre pre " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z + " currentKeyLightDirection " + currentKeyLightDirection.x + " " + currentKeyLightDirection.y + " " + currentKeyLightDirection.z); + if (data.properties.keyLight.direction.x === undefined) { + data.properties.keyLight.direction.x = currentKeyLightDirection.x; + } + if (data.properties.keyLight.direction.y === undefined) { + data.properties.keyLight.direction.y = currentKeyLightDirection.y; + } + print("DBACK TEST data.properties.keyLight.direction pre " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z); + data.properties.keyLight.direction = Vec3.fromPolar(data.properties.keyLight.direction.x, data.properties.keyLight.direction.y); + print("DBACK TEST data.properties.keyLight.direction post " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z); } Entities.editEntity(selectionManager.selections[0], data.properties); if (data.properties.name !== undefined || data.properties.modelURL !== undefined || data.properties.materialURL !== undefined || - data.properties.visible !== undefined || data.properties.locked !== undefined) { + data.properties.visible !== undefined || data.properties.locked !== undefined) { entityListTool.sendUpdate(); } } diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index c4ab00e689..339b2498f9 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -470,6 +470,11 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } +#properties-list { + display: flex; + flex-direction: column; +} + #properties-list fieldset { position: relative; /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ @@ -551,6 +556,16 @@ div.section-header:first-child { margin-bottom: -21px; } +#properties-list .sub-section-header { + border-top: none; + box-shadow: none; + margin-top: 8px; +} + +.sub-section-header + .property { + margin-top: 0; +} + hr { border: none; padding-top: 2px; @@ -634,7 +649,7 @@ hr { height: 1.8rem; } -.text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label,.pyr label, .dropdown label, .gen label { +.text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label { float: left; margin-left: 1px; margin-bottom: 3px; @@ -648,6 +663,10 @@ hr { margin-top: -2px; } +.xy > div, .wh > div, .xyz > div, .pyr > div, .gen > div { + clear: both; +} + .number > input { clear: both; float: left; @@ -656,9 +675,6 @@ hr { clear: both; float: left; } -.xy > div, .wh > div, .xyz > div, .pyr > div, .gen > div { - clear: both; -} .dropdown { position: relative; @@ -1018,6 +1034,10 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { } +body#entity-list-body { + padding-bottom: 0; +} + #entity-list-header { margin-bottom: 36px; } @@ -1054,16 +1074,6 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; /* New positioning context. */ } -#footer-text { - float: right; - padding-top: 12px; - padding-right: 22px; -} - -#entity-list-footer { - padding-top: 9px; -} - #search-area { padding-right: 168px; padding-bottom: 24px; @@ -1091,6 +1101,32 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 120px; } +#entity-list-footer { + padding-top: 9px; +} + +#footer-text { + float: right; + padding-top: 12px; + padding-right: 22px; +} + +input[type=button]#export { + height: 38px; + width: 180px; +} + +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + #entity-table-scroll { /* Height is set by JavaScript. */ width: 100%; @@ -1418,33 +1454,6 @@ input#property-scale-button-reset { right: -20px; } -#properties-list #collision-info > fieldset:first-of-type { - border-top: none !important; - box-shadow: none; +#div-property-collisionSoundURL[style*="display: none"] + .property { margin-top: 0; } - -#properties-list { - display: flex; - flex-direction: column; -} - -#no-entities { - display: none; - position: absolute; - top: 80px; - padding: 12px; - font-family: FiraSans-SemiBold; - font-size: 15px; - font-style: italic; - color: #afafaf; -} - -input[type=button]#export { - height: 38px; - width: 180px; -} - -body#entity-list-body { - padding-bottom: 0; -} diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index e5bb80ef2c..a3f986ad53 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -181,6 +181,7 @@ const GROUPS = [ { label: "Light Horizontal Angle", type: "number", + multiplier: DEGREES_TO_RADIANS, decimals: 2, unit: "deg", propertyID: "keyLight.direction.x", @@ -189,6 +190,7 @@ const GROUPS = [ { label: "Light Vertical Angle", type: "number", + multiplier: DEGREES_TO_RADIANS, decimals: 2, unit: "deg", propertyID: "keyLight.direction.y", @@ -301,8 +303,8 @@ const GROUPS = [ { label: "Background Blend", type: "slider", - min: 0.0, - max: 1.0, + min: 0, + max: 1, step: 0.01, decimals: 2, propertyID: "haze.hazeBackgroundBlend", @@ -610,6 +612,7 @@ const GROUPS = [ min: 0.01, max: 10, step: 0.01, + decimals: 2, propertyID: "lifespan", }, { @@ -646,6 +649,7 @@ const GROUPS = [ min: 0, max: 5, step: 0.01, + decimals: 2, propertyID: "emitSpeed", }, { @@ -654,6 +658,7 @@ const GROUPS = [ min: 0, max: 5, step: 0.01, + decimals: 2, propertyID: "speedSpread", }, { @@ -662,6 +667,7 @@ const GROUPS = [ vec3Type: "xyz", min: 0, step: 0.01, + round: 100, subLabels: [ "x", "y", "z" ], propertyID: "emitDimensions", }, @@ -669,8 +675,8 @@ const GROUPS = [ label: "Emit Orientation", type: "vec3", vec3Type: "pyr", - min: 0, step: 0.01, + round: 100, subLabels: [ "pitch", "yaw", "roll" ], unit: "deg", propertyID: "emitOrientation", @@ -689,30 +695,38 @@ const GROUPS = [ { label: "Size", type: "slider", + min: 0, max: 4, step: 0.01, + decimals: 2, propertyID: "particleRadius", }, { label: "Size Spread", type: "slider", + min: 0, max: 4, step: 0.01, + decimals: 2, propertyID: "radiusSpread", }, { label: "Size Start", type: "slider", + min: 0, max: 4, step: 0.01, + decimals: 2, propertyID: "radiusStart", fallbackProperty: "particleRadius", }, { label: "Size Finish", type: "slider", + min: 0, max: 4, step: 0.01, + decimals: 2, propertyID: "radiusFinish", fallbackProperty: "particleRadius", }, @@ -757,6 +771,7 @@ const GROUPS = [ min: 0, max: 1, step: 0.01, + decimals: 2, propertyID: "alpha", }, { @@ -765,6 +780,7 @@ const GROUPS = [ min: 0, max: 1, step: 0.01, + decimals: 2, propertyID: "alphaSpread", }, { @@ -773,6 +789,7 @@ const GROUPS = [ min: 0, max: 1, step: 0.01, + decimals: 2, propertyID: "alphaStart", fallbackProperty: "alpha", }, @@ -782,6 +799,7 @@ const GROUPS = [ min: 0, max: 1, step: 0.01, + decimals: 2, propertyID: "alphaFinish", fallbackProperty: "alpha", }, @@ -796,6 +814,7 @@ const GROUPS = [ type: "vec3", vec3Type: "xyz", step: 0.01, + round: 100, subLabels: [ "x", "y", "z" ], propertyID: "emitAcceleration", }, @@ -804,6 +823,7 @@ const GROUPS = [ type: "vec3", vec3Type: "xyz", step: 0.01, + round: 100, subLabels: [ "x", "y", "z" ], propertyID: "accelerationSpread", }, @@ -819,6 +839,7 @@ const GROUPS = [ min: -360, max: 360, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "particleSpin", @@ -829,6 +850,7 @@ const GROUPS = [ min: 0, max: 360, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinSpread", @@ -839,6 +861,7 @@ const GROUPS = [ min: -360, max: 360, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinStart", @@ -850,6 +873,7 @@ const GROUPS = [ min: -360, max: 360, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinFinish", @@ -869,9 +893,10 @@ const GROUPS = [ { label: "Horizontal Angle Start", type: "slider", - min: 0, - max: 180, + min: -180, + max: 0, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "azimuthStart", @@ -879,9 +904,10 @@ const GROUPS = [ { label: "Horizontal Angle Finish", type: "slider", - min: -180, - max: 0, + min: 0, + max: 180, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "azimuthFinish", @@ -892,6 +918,7 @@ const GROUPS = [ min: 0, max: 180, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "polarStart", @@ -902,6 +929,7 @@ const GROUPS = [ min: 0, max: 180, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "polarFinish", @@ -935,6 +963,7 @@ const GROUPS = [ label: "Dimension", type: "vec3", vec3Type: "xyz", + min: 0, step: 0.1, decimals: 4, subLabels: [ "x", "y", "z" ], @@ -972,31 +1001,16 @@ const GROUPS = [ { id: "behavior", label: "BEHAVIOR", - twoColumn: true, properties: [ { label: "Grabbable", type: "bool", propertyID: "grab.grabbable", - column: 1, - }, - { - label: "Triggerable", - type: "bool", - propertyID: "grab.triggerable", - column: 2, }, { label: "Cloneable", type: "bool", propertyID: "cloneable", - column: 1, - }, - { - label: "Follow Controller", - type: "bool", - propertyID: "grab.grabFollowsController", - column: 2, }, { label: "Clone Lifetime", @@ -1004,30 +1018,36 @@ const GROUPS = [ unit: "s", propertyID: "cloneLifetime", showPropertyRule: { "cloneable": "true" }, - column: 1, }, { label: "Clone Limit", type: "number", propertyID: "cloneLimit", showPropertyRule: { "cloneable": "true" }, - column: 1, }, { label: "Clone Dynamic", type: "bool", propertyID: "cloneDynamic", showPropertyRule: { "cloneable": "true" }, - column: 1, }, { label: "Clone Avatar Entity", type: "bool", propertyID: "cloneAvatarEntity", showPropertyRule: { "cloneable": "true" }, - column: 1, }, - { // below properties having no column number means place them after two columns div + { + label: "Triggerable", + type: "bool", + propertyID: "grab.triggerable", + }, + { + label: "Follow Controller", + type: "bool", + propertyID: "grab.grabFollowsController", + }, + { label: "Cast shadows", type: "bool", propertyID: "canCastShadow", @@ -1068,34 +1088,18 @@ const GROUPS = [ { id: "collision", label: "COLLISION", - twoColumn: true, properties: [ { label: "Collides", type: "bool", inverse: true, propertyID: "collisionless", - column: -1, // before two columns div - }, - { - label: "Dynamic", - type: "bool", - propertyID: "dynamic", - column: -1, // before two columns div }, { label: "Collides With", type: "sub-header", propertyID: "collidesWithHeader", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, - column: 1, - }, - { - label: "", - type: "sub-header", - propertyID: "collidesWithHeaderHelper", // not actually a property but used for naming/storing this element - showPropertyRule: { "collisionless": "false" }, - column: 2, }, { label: "Static Entities", @@ -1104,16 +1108,6 @@ const GROUPS = [ propertyName: "static", // actual subProperty name subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, - column: 1, - }, - { - label: "Dynamic Entities", - type: "bool", - propertyID: "collidesWithDynamic", - propertyName: "dynamic", // actual subProperty name - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 2, }, { label: "Kinematic Entities", @@ -1122,7 +1116,14 @@ const GROUPS = [ propertyName: "kinematic", // actual subProperty name subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, - column: 1, + }, + { + label: "Dynamic Entities", + type: "bool", + propertyID: "collidesWithDynamic", + propertyName: "dynamic", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, }, { label: "My Avatar", @@ -1131,7 +1132,6 @@ const GROUPS = [ propertyName: "myAvatar", // actual subProperty name subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, - column: 2, }, { label: "Other Avatars", @@ -1140,14 +1140,17 @@ const GROUPS = [ propertyName: "otherAvatar", // actual subProperty name subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, - column: 1, }, { label: "Collision sound URL", type: "string", propertyID: "collisionSoundURL", showPropertyRule: { "collisionless": "false" }, - // having no column number means place this after two columns div + }, + { + label: "Dynamic", + type: "bool", + propertyID: "dynamic", }, ] }, @@ -1555,15 +1558,12 @@ function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { }; } -function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, decimals) { +function createEmitNumberPropertyUpdateFunction(propertyName, multiplier) { return function() { - let value = parseFloat(this.value); - if (multiplier !== undefined) { - value *= multiplier; - } - if (decimals !== undefined) { - value = value.toFixed(decimals); + if (multiplier === undefined) { + multiplier = 1; } + let value = parseFloat(this.value) * multiplier; updateProperty(propertyName, value); }; } @@ -1720,7 +1720,7 @@ function createNumberProperty(property, elProperty, elLabel) { elInput.value = defaultValue; } - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.muliplier, propertyData.decimals)); + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals)); elProperty.appendChild(elLabel); elProperty.appendChild(elInput); @@ -1760,24 +1760,31 @@ function createSliderProperty(property, elProperty, elLabel) { elSlider.setAttribute("step", propertyData.step); } - elInput.oninput = function (event) { - let value = event.target.value; - elSlider.value = value; + elInput.onchange = function (event) { + let inputValue = event.target.value; + elSlider.value = inputValue; if (propertyData.multiplier !== undefined) { - value *= propertyData.multiplier; + inputValue *= propertyData.multiplier; } - updateProperty(property.name, value); + updateProperty(property.name, inputValue); }; - elInput.onchange = elInput.oninput; elSlider.oninput = function (event) { - let value = event.target.value; - elInput.value = value; - if (propertyData.multiplier !== undefined) { - value *= propertyData.multiplier; + let sliderValue = event.target.value; + if (propertyData.step === 1) { + if (sliderValue > 0) { + elInput.value = Math.floor(sliderValue); + } else { + elInput.value = Math.ceil(sliderValue); + } + } else { + elInput.value = sliderValue; } - updateProperty(property.name, value); + if (propertyData.multiplier !== undefined) { + sliderValue *= propertyData.multiplier; + } + updateProperty(property.name, sliderValue); }; - + elDiv.appendChild(elLabel); elDiv.appendChild(elSlider); elDiv.appendChild(elInput); @@ -2952,8 +2959,15 @@ function loaded() { case 'number': case 'slider': { let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; - let decimals = propertyData.decimals !== undefined ? propertyData.decimals : 0; - property.elInput.value = (propertyValue / multiplier).toFixed(decimals); + let value = propertyValue / multiplier; + if (propertyData.round !== undefined) { + value = Math.round(value.round) / propertyData.round; + } + if (propertyData.decimals !== undefined) { + property.elInput.value = value.toFixed(propertyData.decimals); + } else { + property.elInput.value = value; + } if (property.elSlider !== undefined) { property.elSlider.value = property.elInput.value; } @@ -2962,11 +2976,26 @@ function loaded() { case 'vec3': case 'vec2': { let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; - let decimals = propertyData.decimals !== undefined ? propertyData.decimals : 0; - property.elInputX.value = (propertyValue.x / multiplier).toFixed(decimals); - property.elInputY.value = (propertyValue.y / multiplier).toFixed(decimals); - if (property.elInputZ !== undefined) { - property.elInputZ.value = (propertyValue.z / multiplier).toFixed(decimals); + let valueX = propertyValue.x / multiplier; + let valueY = propertyValue.y / multiplier; + let valueZ = propertyValue.z / multiplier; + if (propertyData.round !== undefined) { + valueX = Math.round(valueX * propertyData.round) / propertyData.round; + valueY = Math.round(valueY * propertyData.round) / propertyData.round; + valueZ = Math.round(valueZ * propertyData.round) / propertyData.round; + } + if (propertyData.decimals !== undefined) { + property.elInputX.value = valueX.toFixed(propertyData.decimals); + property.elInputY.value = valueY.toFixed(propertyData.decimals); + if (property.elInputZ !== undefined) { + property.elInputZ.value = valueZ.toFixed(propertyData.decimals); + } + } else { + property.elInputX.value = valueX; + property.elInputY.value = valueY; + if (property.elInputZ !== undefined) { + property.elInputZ.value = valueZ; + } } break; } From c7ba386be1c85ccbf266484ab81561057aae216d Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 23 Oct 2018 20:56:53 -0700 Subject: [PATCH 101/131] darn prints somehow slipping past me --- scripts/system/edit.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 739229e7a7..287df1fdca 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2186,10 +2186,8 @@ var PropertiesTool = function (opts) { entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); } if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { - print("DBACK TEST entity.properties.keyLight.direction pre " + entity.properties.keyLight.direction.x + " " + entity.properties.keyLight.direction.y + " " + entity.properties.keyLight.direction.z); entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); entity.properties.keyLight.direction.z = 0.0; - print("DBACK TEST entity.properties.keyLight.direction post " + entity.properties.keyLight.direction.x + " " + entity.properties.keyLight.direction.y + " " + entity.properties.keyLight.direction.z); } selections.push(entity); } @@ -2231,16 +2229,13 @@ var PropertiesTool = function (opts) { } if (data.properties.keyLight !== undefined && data.properties.keyLight.direction !== undefined) { var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); - print("DBACK TEST data.properties.keyLight.direction pre pre " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z + " currentKeyLightDirection " + currentKeyLightDirection.x + " " + currentKeyLightDirection.y + " " + currentKeyLightDirection.z); if (data.properties.keyLight.direction.x === undefined) { data.properties.keyLight.direction.x = currentKeyLightDirection.x; } if (data.properties.keyLight.direction.y === undefined) { data.properties.keyLight.direction.y = currentKeyLightDirection.y; } - print("DBACK TEST data.properties.keyLight.direction pre " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z); data.properties.keyLight.direction = Vec3.fromPolar(data.properties.keyLight.direction.x, data.properties.keyLight.direction.y); - print("DBACK TEST data.properties.keyLight.direction post " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z); } Entities.editEntity(selectionManager.selections[0], data.properties); if (data.properties.name !== undefined || data.properties.modelURL !== undefined || data.properties.materialURL !== undefined || From 27e0c85b01d799ac4268b82fad863b21bcf57e88 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 5 Oct 2018 22:13:45 +0200 Subject: [PATCH 102/131] entity list auto scroll --- scripts/system/html/js/entityList.js | 35 +++++++++++++++---- scripts/system/html/js/listView.js | 18 ++++++++++ scripts/system/libraries/entityList.js | 7 ++-- .../system/libraries/entitySelectionTool.js | 8 ++--- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0ced016d26..0c87470f57 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -194,7 +194,7 @@ function loaded() { if (selectedIndex >= 0) { selection = []; selection = selection.concat(selectedEntities); - selection.splice(selectedIndex, 1) + selection.splice(selectedIndex, 1); } else { selection = selection.concat(selectedEntities); } @@ -453,7 +453,10 @@ function loaded() { } } - function updateSelectedEntities(selectedIDs) { + function updateSelectedEntities(selectedIDs, autoScroll) { + // force autoScroll to be a boolean + autoScroll = !!autoScroll; + let notFound = false; // reset all currently selected entities and their rows first @@ -482,6 +485,26 @@ function loaded() { } }); + if (autoScroll && selectedIDs.length > 0) { + let firstItem = Number.MAX_VALUE; + let lastItem = -1; + let itemFound = false; + visibleEntities.forEach(function(entity, index) { + if (selectedIDs.indexOf(entity.id) !== -1) { + if (firstItem > index) { + firstItem = index; + } + if (lastItem < index) { + lastItem = index; + } + itemFound = true; + } + }); + if (itemFound) { + entityList.scrollToRow(firstItem, lastItem); + } + } + refreshFooter(); return notFound; @@ -640,8 +663,8 @@ function loaded() { data = JSON.parse(data); if (data.type === "clearEntityList") { clearEntities(); - } else if (data.type === "selectionUpdate") { - let notFound = updateSelectedEntities(data.selectedIDs); + } else if (data.type === "selectionUpdate" && data.caller !== "entityList") { + let notFound = updateSelectedEntities(data.selectedIDs, true); if (notFound) { refreshEntities(); } @@ -653,13 +676,13 @@ function loaded() { clearEntities(); } else { updateEntityData(newEntities); - updateSelectedEntities(data.selectedIDs); + updateSelectedEntities(data.selectedIDs, true); } } }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { removeEntities(data.deletedIDs); - updateSelectedEntities(data.selectedIDs); + updateSelectedEntities(data.selectedIDs, true); } else if (data.type === "deleted" && data.ids) { removeEntities(data.ids); } diff --git a/scripts/system/html/js/listView.js b/scripts/system/html/js/listView.js index 10dc37efba..d11452e111 100644 --- a/scripts/system/html/js/listView.js +++ b/scripts/system/html/js/listView.js @@ -152,6 +152,24 @@ ListView.prototype = { this.refresh(); } }, + + scrollToRow(rowIndex, lastRowIndex) { + lastRowIndex = lastRowIndex ? lastRowIndex : rowIndex; + let boundingTop = rowIndex * this.rowHeight; + let boundingBottom = (lastRowIndex * this.rowHeight) + this.rowHeight; + if ((boundingBottom - boundingTop) > this.elTableScroll.clientHeight) { + boundingBottom = boundingTop + this.elTableScroll.clientHeight; + } + + let currentVisibleAreaTop = this.elTableScroll.scrollTop; + let currentVisibleAreaBottom = currentVisibleAreaTop + this.elTableScroll.clientHeight; + + if (boundingTop < currentVisibleAreaTop) { + this.elTableScroll.scrollTop = boundingTop; + } else if (boundingBottom > currentVisibleAreaBottom) { + this.elTableScroll.scrollTop = boundingBottom - (this.elTableScroll.clientHeight); + } + }, refresh: function() { // block refreshing before rows are initialized diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 30e952723f..04b19aa280 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -98,7 +98,7 @@ EntityListTool = function(shouldUseEditTabletApp) { that.setVisible(!visible); }; - selectionManager.addEventListener(function() { + selectionManager.addEventListener(function(isSelectionUpdate, caller) { var selectedIDs = []; for (var i = 0; i < selectionManager.selections.length; i++) { @@ -107,7 +107,8 @@ EntityListTool = function(shouldUseEditTabletApp) { emitJSONScriptEvent({ type: 'selectionUpdate', - selectedIDs: selectedIDs + selectedIDs: selectedIDs, + caller: caller }); }); @@ -224,7 +225,7 @@ EntityListTool = function(shouldUseEditTabletApp) { for (var i = 0; i < ids.length; i++) { entityIDs.push(ids[i]); } - selectionManager.setSelections(entityIDs); + selectionManager.setSelections(entityIDs, "entityList"); if (data.focus) { cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 843d3e986f..4302a531b5 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -136,7 +136,7 @@ SelectionManager = (function() { return that.selections.length > 0; }; - that.setSelections = function(entityIDs) { + that.setSelections = function(entityIDs, caller) { that.selections = []; for (var i = 0; i < entityIDs.length; i++) { var entityID = entityIDs[i]; @@ -144,7 +144,7 @@ SelectionManager = (function() { Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); } - that._update(true); + that._update(true, caller); }; that.addEntity = function(entityID, toggleSelection) { @@ -477,7 +477,7 @@ SelectionManager = (function() { undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); } - that._update = function(selectionUpdated) { + that._update = function(selectionUpdated, caller) { var properties = null; if (that.selections.length === 0) { that.localDimensions = null; @@ -542,7 +542,7 @@ SelectionManager = (function() { for (var j = 0; j < listeners.length; j++) { try { - listeners[j](selectionUpdated === true); + listeners[j](selectionUpdated === true, caller); } catch (e) { print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); } From 776b8b8fc7161060b037e12e661dedf122897235 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 9 Oct 2018 00:59:53 +0200 Subject: [PATCH 103/131] CR and style fixes --- scripts/system/edit.js | 42 +++++++++---------- scripts/system/html/js/entityList.js | 29 ++++++------- scripts/system/html/js/listView.js | 14 +++++-- scripts/system/libraries/entityList.js | 9 ++-- .../system/libraries/entitySelectionTool.js | 38 ++++++++--------- 5 files changed, 69 insertions(+), 63 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index b911541f79..0d63b92b63 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -385,7 +385,7 @@ var toolBar = (function () { Entities.editEntity(entityID, { position: position }); - selectionManager._update(); + selectionManager._update(false, this); } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); } @@ -397,9 +397,9 @@ var toolBar = (function () { properties.type + " would be out of bounds."); } - selectionManager.clearSelections(); + selectionManager.clearSelections(this); entityListTool.sendUpdate(); - selectionManager.setSelections([entityID]); + selectionManager.setSelections([entityID], this); Window.setFocus(); @@ -550,7 +550,7 @@ var toolBar = (function () { } deletedEntityTimer = Script.setTimeout(function () { if (entitiesToDelete.length > 0) { - selectionManager.removeEntities(entitiesToDelete); + selectionManager.removeEntities(entitiesToDelete, this); } entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); entitiesToDelete = []; @@ -866,7 +866,7 @@ var toolBar = (function () { gridTool.setVisible(false); grid.setEnabled(false); propertiesTool.setVisible(false); - selectionManager.clearSelections(); + selectionManager.clearSelections(this); cameraManager.disable(); selectionDisplay.disableTriggerMapping(); tablet.landscape = false; @@ -994,7 +994,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { var entity = entityIconOverlayManager.findEntity(data.overlayID); if (entity !== null) { - selectionManager.setSelections([entity]); + selectionManager.setSelections([entity], this); } } } @@ -1141,7 +1141,7 @@ function mouseClickEvent(event) { if (result === null || result === undefined) { if (!event.isShifted) { - selectionManager.clearSelections(); + selectionManager.clearSelections(this); } return; } @@ -1193,9 +1193,9 @@ function mouseClickEvent(event) { } if (!event.isShifted) { - selectionManager.setSelections([foundEntity]); + selectionManager.setSelections([foundEntity], this); } else { - selectionManager.addEntity(foundEntity, true); + selectionManager.addEntity(foundEntity, true, this); } if (wantDebug) { @@ -1493,7 +1493,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) { } } } - selectionManager.setSelections(entities); + selectionManager.setSelections(entities, this); } } @@ -1633,7 +1633,7 @@ function deleteSelectedEntities() { } if (savedProperties.length > 0) { - SelectionManager.clearSelections(); + SelectionManager.clearSelections(this); pushCommandForSelections([], savedProperties); entityListTool.deleteEntities(deletedIDs); } @@ -1650,7 +1650,7 @@ function toggleSelectedEntitiesLocked() { }); } entityListTool.sendUpdate(); - selectionManager._update(); + selectionManager._update(false, this); } } @@ -1664,7 +1664,7 @@ function toggleSelectedEntitiesVisible() { }); } entityListTool.sendUpdate(); - selectionManager._update(); + selectionManager._update(false, this); } } @@ -1861,7 +1861,7 @@ function importSVO(importURL) { } if (isActive) { - selectionManager.setSelections(pastedEntityIDs); + selectionManager.setSelections(pastedEntityIDs, this); } } else { Window.notifyEditError("Can't import entities: entities would be out of bounds."); @@ -1909,7 +1909,7 @@ function deleteKey(value) { } function deselectKey(value) { if (value === 0) { // on release - selectionManager.clearSelections(); + selectionManager.clearSelections(this); } } function toggleKey(value) { @@ -2069,7 +2069,7 @@ function applyEntityProperties(data) { // We might be getting an undo while edit.js is disabled. If that is the case, don't set // our selections, causing the edit widgets to display. if (isActive) { - selectionManager.setSelections(selectedEntityIDs); + selectionManager.setSelections(selectedEntityIDs, this); } } @@ -2272,7 +2272,7 @@ var PropertiesTool = function (opts) { } } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } else if (data.type === 'parent') { parentSelectedEntities(); } else if (data.type === 'unparent') { @@ -2301,7 +2301,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "moveAllToGrid") { if (selectionManager.hasSelection()) { @@ -2321,7 +2321,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "resetToNaturalDimensions") { if (selectionManager.hasSelection()) { @@ -2342,7 +2342,7 @@ var PropertiesTool = function (opts) { } } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "previewCamera") { if (selectionManager.hasSelection()) { @@ -2360,7 +2360,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "reloadClientScripts") { if (selectionManager.hasSelection()) { diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0c87470f57..a75bd7e4b9 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -13,7 +13,7 @@ const DESCENDING_STRING = '▾'; const LOCKED_GLYPH = ""; const VISIBLE_GLYPH = ""; const TRANSPARENCY_GLYPH = ""; -const BAKED_GLYPH = "" +const BAKED_GLYPH = ""; const SCRIPT_GLYPH = "k"; const BYTES_PER_MEGABYTE = 1024 * 1024; const IMAGE_MODEL_NAME = 'default-image-model.fbx'; @@ -54,10 +54,10 @@ const COMPARE_ASCENDING = function(a, b) { } return 1; -} +}; const COMPARE_DESCENDING = function(a, b) { return COMPARE_ASCENDING(b, a); -} +}; // List of all entities var entities = []; @@ -156,22 +156,22 @@ function loaded() { }; elRefresh.onclick = function() { refreshEntities(); - } + }; elToggleLocked.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' })); - } + }; elToggleVisible.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' })); - } + }; elExport.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); - } + }; elPal.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); - } + }; elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); - } + }; elFilter.onkeyup = refreshEntityList; elFilter.onpaste = refreshEntityList; elFilter.onchange = onFilterChange; @@ -227,7 +227,7 @@ function loaded() { } } - updateSelectedEntities(selection); + updateSelectedEntities(selection, false); EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", @@ -289,7 +289,7 @@ function loaded() { hasScript: entity.hasScript, elRow: null, // if this entity has a visible row element assigned to it selected: false // if this entity is selected for edit regardless of having a visible row - } + }; entities.push(entityData); entitiesByID[entityData.id] = entityData; @@ -418,7 +418,7 @@ function loaded() { isBaked: document.querySelector('#entity-isBaked .sort-order'), drawCalls: document.querySelector('#entity-drawCalls .sort-order'), hasScript: document.querySelector('#entity-hasScript .sort-order'), - } + }; function setSortColumn(column) { PROFILE("set-sort-column", function() { if (currentSortColumn === column) { @@ -454,9 +454,6 @@ function loaded() { } function updateSelectedEntities(selectedIDs, autoScroll) { - // force autoScroll to be a boolean - autoScroll = !!autoScroll; - let notFound = false; // reset all currently selected entities and their rows first @@ -663,7 +660,7 @@ function loaded() { data = JSON.parse(data); if (data.type === "clearEntityList") { clearEntities(); - } else if (data.type === "selectionUpdate" && data.caller !== "entityList") { + } else if (data.type === "selectionUpdate") { let notFound = updateSelectedEntities(data.selectedIDs, true); if (notFound) { refreshEntities(); diff --git a/scripts/system/html/js/listView.js b/scripts/system/html/js/listView.js index d11452e111..62163ffe16 100644 --- a/scripts/system/html/js/listView.js +++ b/scripts/system/html/js/listView.js @@ -38,7 +38,7 @@ function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunctio this.lastRowShiftScrollTop = 0; this.initialize(); -}; +} ListView.prototype = { getNumRows: function() { @@ -153,9 +153,15 @@ ListView.prototype = { } }, - scrollToRow(rowIndex, lastRowIndex) { - lastRowIndex = lastRowIndex ? lastRowIndex : rowIndex; - let boundingTop = rowIndex * this.rowHeight; + /** + * Scrolls firstRowIndex with least effort, also tries to make the window include the other selections in case lastRowIndex is set. + * In the case that firstRowIndex and lastRowIndex are already within the visible bounds then nothing will happen. + * @param {number} firstRowIndex - The row that will be scrolled to. + * @param {number} lastRowIndex - The last index of the bound. + */ + scrollToRow: function (firstRowIndex, lastRowIndex) { + lastRowIndex = lastRowIndex ? lastRowIndex : firstRowIndex; + let boundingTop = firstRowIndex * this.rowHeight; let boundingBottom = (lastRowIndex * this.rowHeight) + this.rowHeight; if ((boundingBottom - boundingTop) > this.elTableScroll.clientHeight) { boundingBottom = boundingTop + this.elTableScroll.clientHeight; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 04b19aa280..045990b99b 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -99,6 +99,10 @@ EntityListTool = function(shouldUseEditTabletApp) { }; selectionManager.addEventListener(function(isSelectionUpdate, caller) { + if (caller === that) { + // ignore events that we emitted from the entity list itself + return; + } var selectedIDs = []; for (var i = 0; i < selectionManager.selections.length; i++) { @@ -107,8 +111,7 @@ EntityListTool = function(shouldUseEditTabletApp) { emitJSONScriptEvent({ type: 'selectionUpdate', - selectedIDs: selectedIDs, - caller: caller + selectedIDs: selectedIDs }); }); @@ -225,7 +228,7 @@ EntityListTool = function(shouldUseEditTabletApp) { for (var i = 0; i < ids.length; i++) { entityIDs.push(ids[i]); } - selectionManager.setSelections(entityIDs, "entityList"); + selectionManager.setSelections(entityIDs, that); if (data.focus) { cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 4302a531b5..73d84cf293 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -40,7 +40,7 @@ SelectionManager = (function() { Messages.messageReceived.connect(handleEntitySelectionToolUpdates); } - // FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES + // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES function handleEntitySelectionToolUpdates(channel, message, sender) { if (channel !== 'entityToolUpdates') { return; @@ -63,7 +63,7 @@ SelectionManager = (function() { if (wantDebug) { print("setting selection to " + messageParsed.entityID); } - that.setSelections([messageParsed.entityID]); + that.setSelections([messageParsed.entityID], that); } } else if (messageParsed.method === "clearSelection") { if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { @@ -147,7 +147,7 @@ SelectionManager = (function() { that._update(true, caller); }; - that.addEntity = function(entityID, toggleSelection) { + that.addEntity = function(entityID, toggleSelection, caller) { if (entityID) { var idx = -1; for (var i = 0; i < that.selections.length; i++) { @@ -165,7 +165,7 @@ SelectionManager = (function() { } } - that._update(true); + that._update(true, caller); }; function removeEntityByID(entityID) { @@ -176,21 +176,21 @@ SelectionManager = (function() { } } - that.removeEntity = function (entityID) { + that.removeEntity = function (entityID, caller) { removeEntityByID(entityID); - that._update(true); + that._update(true, caller); }; - that.removeEntities = function(entityIDs) { + that.removeEntities = function(entityIDs, caller) { for (var i = 0, length = entityIDs.length; i < length; i++) { removeEntityByID(entityIDs[i]); } - that._update(true); + that._update(true, caller); }; - that.clearSelections = function() { + that.clearSelections = function(caller) { that.selections = []; - that._update(true); + that._update(true, caller); }; that.addChildrenEntities = function(parentEntityID, entityList) { @@ -985,7 +985,7 @@ SelectionDisplay = (function() { that.pressedHand = NO_HAND; that.triggered = function() { return that.triggeredHand !== NO_HAND; - } + }; function pointingAtDesktopWindowOrTablet(hand) { var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtDesktopWindowRight) || @@ -1032,7 +1032,7 @@ SelectionDisplay = (function() { that.disableTriggerMapping = function() { that.triggerClickMapping.disable(); that.triggerPressMapping.disable(); - } + }; Script.scriptEnding.connect(that.disableTriggerMapping); // FUNCTION DEF(s): Intersection Check Helpers @@ -1234,7 +1234,7 @@ SelectionDisplay = (function() { if (wantDebug) { print(" Trigger SelectionManager::update"); } - SelectionManager._update(); + SelectionManager._update(false, that); if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); @@ -1299,7 +1299,7 @@ SelectionDisplay = (function() { lastMouseEvent.isControl = event.isControl; lastMouseEvent.isAlt = event.isAlt; activeTool.onMove(lastMouseEvent); - SelectionManager._update(); + SelectionManager._update(false, this); } }; @@ -1315,7 +1315,7 @@ SelectionDisplay = (function() { lastMouseEvent.isControl = event.isControl; lastMouseEvent.isAlt = event.isAlt; activeTool.onMove(lastMouseEvent); - SelectionManager._update(); + SelectionManager._update(false, this); } }; @@ -2179,7 +2179,7 @@ SelectionDisplay = (function() { } } - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2301,7 +2301,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2488,7 +2488,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2599,7 +2599,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } From 8e1cfb5a067bde0d5676ba3d1024d53cc17ba4ec Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Oct 2018 23:03:26 +0200 Subject: [PATCH 104/131] fix double click selection --- scripts/system/html/js/entityList.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index a75bd7e4b9..55871533e2 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -239,11 +239,16 @@ function loaded() { } function onRowDoubleClicked() { + let selection = [this.dataset.entityID]; + updateSelectedEntities(selection, false); + EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: true, - entityIds: [this.dataset.entityID], + entityIds: selection, })); + + refreshFooter(); } function decimalMegabytes(number) { From 5f35a324805d68c97748f5ac65c8b7e97282e15d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Oct 2018 23:09:41 +0200 Subject: [PATCH 105/131] remove duplicate calls to refresh footer --- scripts/system/html/js/entityList.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 55871533e2..62c8d6ca6f 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -234,8 +234,6 @@ function loaded() { focus: false, entityIds: selection, })); - - refreshFooter(); } function onRowDoubleClicked() { @@ -247,8 +245,6 @@ function loaded() { focus: true, entityIds: selection, })); - - refreshFooter(); } function decimalMegabytes(number) { From 5c1972e3f411129691fe2a061c89f421933c0974 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 24 Oct 2018 12:40:44 -0700 Subject: [PATCH 106/131] fix fallback properties --- scripts/system/html/js/entityProperties.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a3f986ad53..db3424bbd9 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -2935,7 +2935,21 @@ function loaded() { continue; } - if (!propertyValue && propertyData.fallbackProperty !== undefined) { + let isPropertyNotNumber = false; + switch (propertyData.type) { + case 'number': + case 'slider': + isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; + break; + case 'vec3': + case 'vec2': + isPropertyNotNumber = isNaN(propertyValue.x) || propertyValue.x === null; + break; + case 'color': + isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null; + break; + } + if (isPropertyNotNumber && propertyData.fallbackProperty !== undefined) { propertyValue = getPropertyValue(propertyData.fallbackProperty); } @@ -3000,9 +3014,6 @@ function loaded() { break; } case 'color': { - if (!propertyValue.red && propertyData.fallbackProperty !== undefined) { - propertyValue = getPropertyValue(propertyData.fallbackProperty); - } property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; From a3c42363cc74d9e6d8dd41060e137de1f14013e1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 24 Oct 2018 12:43:01 -0700 Subject: [PATCH 107/131] please for the love of god --- .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 10 +++++----- .../avatars-renderer/src/avatars-renderer/Avatar.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 4a06a5252a..86f5bd69a5 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -707,10 +707,10 @@ static TextRenderer3D* textRenderer(TextRendererType type) { return displayNameRenderer; } -void Avatar::metaBlendshapeOperator(int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, - const render::ItemIDs& subItemIDs) { +void Avatar::metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets, + const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs) { render::Transaction transaction; - transaction.updateItem(_renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes, + transaction.updateItem(renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes, subItemIDs](AvatarData& avatar) { auto avatarPtr = dynamic_cast(&avatar); if (avatarPtr) { @@ -730,7 +730,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc _renderBound = getBounds(); transaction.resetItem(_renderItemID, avatarPayloadPointer); using namespace std::placeholders; - _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4)); + _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, _renderItemID, _1, _2, _3, _4)); _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); _skeletonModel->setCanCastShadow(true); @@ -954,7 +954,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) { _skeletonModel->removeFromScene(scene, transaction); using namespace std::placeholders; - _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4)); + _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, _renderItemID, _1, _2, _3, _4)); _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index ca063a7026..9a4b9bb6b6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -626,8 +626,8 @@ protected: LoadingStatus _loadingStatus { LoadingStatus::NoModel }; - void metaBlendshapeOperator(int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, - const render::ItemIDs& subItemIDs); + static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets, + const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs); }; #endif // hifi_Avatar_h From b6f259fc42aa7ed03b2001056e46c2b71bfc2d83 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 24 Oct 2018 13:17:45 -0700 Subject: [PATCH 108/131] Fix incorrect merge-resolution --- libraries/avatars/src/AvatarData.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e08f7fb6f8..7f42289a9b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -419,11 +419,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { auto startSection = destinationBuffer; - if (_overrideGlobalPosition) { - AVATAR_MEMCPY(_globalPositionOverride); - } else { - AVATAR_MEMCPY(_globalPosition); - } + AVATAR_MEMCPY(_globalPosition); int numBytes = destinationBuffer - startSection; From a88678fb70a124632ae29cbe36980ab3a2608e2d Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 24 Oct 2018 15:07:47 -0700 Subject: [PATCH 109/131] fix bad css merge, add particle property debounce --- scripts/system/html/css/edit-style.css | 2 +- scripts/system/html/js/entityProperties.js | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 5214dc0c3e..2db7fce065 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1107,7 +1107,7 @@ body#entity-list-body { position: relative; /* New positioning context. */ } -#search-area { +#filter-area { padding-right: 168px; padding-bottom: 24px; } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index db3424bbd9..676ecaf289 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1304,6 +1304,7 @@ const JSON_EDITOR_ROW_DIV_INDEX = 2; var elGroups = {}; var properties = {}; var colorPickers = {}; +var particlePropertyUpdates = {}; var selectedEntityProperties; var lastEntityID = null; @@ -1534,9 +1535,23 @@ function updateProperty(originalPropertyName, propertyValue) { } else { propertyUpdate[originalPropertyName] = propertyValue; } - updateProperties(propertyUpdate); + // queue up particle property changes with the debounced sync to avoid + // causing particle emitting to reset each frame when updating values + if (properties[originalPropertyName].isParticleProperty) { + Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) { + particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey]; + }); + particleSyncDebounce(); + } else { + updateProperties(propertyUpdate); + } } +var particleSyncDebounce = _.debounce(function () { + updateProperties(particlePropertyUpdates); + particlePropertyUpdates = {}; +}, DEBOUNCE_TIMEOUT); + function updateProperties(propertiesToUpdate) { EventBridge.emitWebEvent(JSON.stringify({ id: lastEntityID, @@ -2717,7 +2732,8 @@ function loaded() { let property = { data: propertyData, elementID: propertyElementID, - name: propertyName, + name: propertyName, + isParticleProperty: group.id.includes("particles"), elProperty: elProperty }; properties[propertyID] = property; From 5c5a5c9cd2a4c25d2c061819b838cee6d8da41f5 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 24 Oct 2018 16:14:05 -0700 Subject: [PATCH 110/131] Add optional default scripts log suppression --- scripts/developer/debugging/debugWindow.js | 36 +++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index 993ca49a40..84bd3c323c 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -19,6 +19,23 @@ if (scripts.length >= 2) { return; } +var SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME = "Developer" +var SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME = "Suppress messages from default scripts in Debug Window"; +var DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS = 'debugWindowSuppressDefaultScripts'; +var suppressDefaultScripts = Settings.getValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, false) +Menu.addMenuItem({ + menuName: SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME, + menuItemName: SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME, + isCheckable: true, + isChecked: suppressDefaultScripts +}); + +Menu.menuItemEvent.connect(function(menuItem) { + if (menuItem === SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME) { + suppressDefaultScripts = Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME); + } +}); + // Set up the qml ui var qml = Script.resolvePath('debugWindow.qml'); @@ -61,17 +78,24 @@ window.visibleChanged.connect(function() { window.closed.connect(function () { Script.stop(); }); +function shouldLogMessage(scriptFileName) { + return !suppressDefaultScripts + || (scriptFileName !== "defaultScripts.js" && scriptFileName != "controllerScripts.js"); +} + var getFormattedDate = function() { var date = new Date(); return date.getMonth() + "/" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); }; var sendToLogWindow = function(type, message, scriptFileName) { - var typeFormatted = ""; - if (type) { - typeFormatted = type + " - "; + if (shouldLogMessage(scriptFileName)) { + var typeFormatted = ""; + if (type) { + typeFormatted = type + " - "; + } + window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message); } - window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message); }; ScriptDiscoveryService.printedMessage.connect(function(message, scriptFileName) { @@ -95,6 +119,10 @@ ScriptDiscoveryService.clearDebugWindow.connect(function() { }); Script.scriptEnding.connect(function () { + Settings.setValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, + Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME)); + Menu.removeMenuItem(SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME, SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME); + var geometry = JSON.stringify({ x: window.position.x, y: window.position.y, From a3b874f9aa77c3c63b106054be3fee8bb6bfe391 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 18 Oct 2018 01:14:13 +0200 Subject: [PATCH 111/131] property tooltips --- .../system/assets/data/createAppTooltips.json | 458 ++++++++++++++++++ scripts/system/edit.js | 5 + scripts/system/html/css/edit-style.css | 22 + scripts/system/html/entityProperties.html | 1 + scripts/system/html/js/createAppTooltip.js | 84 ++++ scripts/system/html/js/entityProperties.js | 8 +- 6 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 scripts/system/assets/data/createAppTooltips.json create mode 100644 scripts/system/html/js/createAppTooltip.js diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json new file mode 100644 index 0000000000..f8c037297f --- /dev/null +++ b/scripts/system/assets/data/createAppTooltips.json @@ -0,0 +1,458 @@ +{ + "shape": { + "tooltip": "The shape of this entity's geometry." + }, + "color": { + "tooltip": "The RGB value of this entity." + }, + "materialURL": { + "tooltip": "The URL of a JSON file containing the material. Use this to change the entity's look. " + }, + "text": { + "tooltip": "The text to display on the entity." + }, + "textColor": { + "tooltip": "The color of the text." + }, + "backgroundColor": { + "tooltip": "The color of the background." + }, + "lineHeight": { + "tooltip": "The height of each line of text. This determines the size of the text." + }, + "faceCamera": { + "tooltip": "If enabled, the entity follows the camera of each user, creating a billboard effect." + }, + "flyingAllowed": { + "tooltip": "If enabled, users can fly in the zone." + }, + "ghostingAllowed": { + "tooltip": "If enabled, users with avatar collisions turned off will not collide with content in the zone." + }, + "filterURL": { + "tooltip": "The URL of a JS file that checks for changes to entity properties within the zone. Runs periodically." + }, + "keyLightMode": { + "tooltip": "Configures the key light in the zone. This light is directional." + }, + "keyLightColor": { + "tooltip": "The color of the key light." + }, + "keyLight.intensity": { + "tooltip": "The intensity of the key light." + }, + "keyLight.direction.y": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its y axis." + }, + "keyLight.direction.x": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its x axis." + }, + "keyLight.castShadows": { + "tooltip": "If enabled, shadows are cast. The entity or avatar casting the shadow must also have Cast Shadows enabled." + }, + "skyboxMode": { + "tooltip": "Configures the skybox in the zone. The skybox is a cube map image." + }, + "skybox.color": { + "tooltip": "If the URL is blank, this changes the color of the sky, otherwise it modifies the color of the skybox." + }, + "skybox.url": { + "tooltip": "A cube map image that is used to render the sky." + }, + "ambientLightMode": { + "tooltip": "Configures the ambient light in the zone. Use this if you want your skybox to reflect light on the content." + }, + "ambientLight.ambientIntensity": { + "tooltip": "The intensity of the ambient light." + }, + "ambientLight.ambientURL": { + "tooltip": "A cube map image that defines the color of the light coming from each direction." + }, + "hazeMode": { + "tooltip": "Configures the haze in the scene." + }, + "haze.hazeRange": { + "tooltip": "How far the haze extends out. This is measured in meters." + }, + "haze.hazeAltitudeEffect": { + "tooltip": "If enabled, this adjusts the haze intensity as it gets higher." + }, + "haze.hazeBaseRef": { + "tooltip": "The base of the altitude range. Measured in entity space." + }, + "haze.hazeCeiling": { + "tooltip": "The ceiling of the altitude range. Measured in entity space." + }, + "haze.hazeColor": { + "tooltip": "The color of the haze." + }, + "haze.hazeBackgroundBlend": { + "tooltip": "How much the skybox shows through the haze. The higher the value, the more it shows through." + }, + "haze.hazeEnableGlare": { + "tooltip": "If enabled, a glare is enabled on the skybox, based on the key light." + }, + "haze.hazeGlareColor": { + "tooltip": "The color of the glare based on the key light." + }, + "haze.hazeGlareAngle": { + "tooltip": "The angular size of the glare and how much it encompasses the skybox, based on the key light." + }, + "bloomMode": { + "tooltip": "Configures how much bright areas of the scene glow." + }, + "bloom.bloomIntensity": { + "tooltip": "The intensity, or brightness, of the bloom effect." + }, + "bloom.bloomThreshold": { + "tooltip": "The cutoff of the bloom. The higher the value, the more only bright areas of the scene will glow." + }, + "bloom.bloomSize": { + "tooltip": "The radius of bloom. The higher the value, the larger the bloom." + }, + "modelURL": { + "tooltip": "A mesh model from an FBX or OBJ file." + }, + "shapeType": { + "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." + }, + "compoundShapeURL": { + "tooltip": "The OBJ file to use for the compound shape if Collision Shape is \"compound\"." + }, + "animation.url": { + "tooltip": "An animation to play on the model." + }, + "animation.running": { + "tooltip": "If enabled, the animation on the model will play automatically." + }, + "animation.allowTranslation": { + "tooltip": "If enabled, this allows an entity to move in space during an animation." + }, + "animation.loop": { + "tooltip": "If enabled, then the animation will continuously repeat." + }, + "animation.hold": { + "tooltip": "If enabled, then rotations and translations of the last frame played are maintained when the animation stops." + }, + "animation.currentFrame": { + "tooltip": "The current frame being played in the animation." + }, + "animation.firstFrame": { + "tooltip": "The first frame to play in the animation." + }, + "animation.lastFrame": { + "tooltip": "The last frame to play in the animation." + }, + "animation.fps": { + "tooltip": "The speed of the animation." + }, + "textures": { + "tooltip": "The URL of a JPG or PNG image file to display for each particle." + }, + "originalTextures": { + "tooltip": "A JSON string containing the original texture used on the model." + }, + "imageUrl": { + "tooltip": "The URL for the image source.", + "jsPropertyName": "textures" + }, + "sourceUrl": { + "tooltip": "The URL for the web page source." + }, + "dpi": { + "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame." + }, + "isEmitting": { + "tooltip": "If enabled, then particles are emitted." + }, + "lifespan": { + "tooltip": "How long each particle lives, measured in seconds." + }, + "maxParticles": { + "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." + }, + "emitRate": { + "tooltip": "The number of particles per second to emit." + }, + "emitSpeed": { + "tooltip": "The speed that each particle is emitted at, measured in m/s." + }, + "speedSpread": { + "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." + }, + "emitDimensions": { + "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." + }, + "emitOrientation": { + "tooltip": "The orientation of particle emission relative to the entity's axes." + }, + "emitRadiusStart": { + "tooltip": "The inner limit radius in dimensions that the particles start emitting from." + }, + "emitterShouldTrail": { + "tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." + }, + "particleRadius": { + "tooltip": "The size of each particle." + }, + "radiusStart": { + "tooltip": "" + }, + "radiusFinish": { + "tooltip": "" + }, + "radiusSpread": { + "tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." + }, + "particleColor": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "colorSpread": { + "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." + }, + "alpha": { + "tooltip": "The alpha of each particle." + }, + "alphaStart": { + "tooltip": "" + }, + "alphaFinish": { + "tooltip": "" + }, + "alphaSpread": { + "tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas." + }, + "emitAcceleration": { + "tooltip": "The acceleration that is applied to each particle during its lifetime." + }, + "accelerationSpread": { + "tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." + }, + "particleSpin": { + "tooltip": "The spin of each particle in the system." + }, + "spinStart": { + "tooltip": "" + }, + "spinFinish": { + "tooltip": "" + }, + "spinSpread": { + "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." + }, + "rotateWithEntity": { + "tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole." + }, + "azimuthStart": { + "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "azimuthFinish": { + "tooltip": "" + }, + "polarStart": { + "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "polarFinish": { + "tooltip": "" + }, + "lightColor": { + "tooltip": "The color of the light emitted.", + "jsPropertyName": "color" + }, + "intensity": { + "tooltip": "The brightness of the light." + }, + "falloffRadius": { + "tooltip": "The distance from the light's center where the intensity is reduced." + }, + "isSpotlight": { + "tooltip": "If enabled, then the light is directional, otherwise the light is a point light which emits light in all directions." + }, + "exponent": { + "tooltip": "Affects the softness of the spotlight beam; the higher the value, the softer the beam." + }, + "cutoff": { + "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." + }, + "id": { + "tooltip": "The unique identifier of this entity." + }, + "name": { + "tooltip": "The name of this entity." + }, + "description": { + "tooltip": "Use this field to describe the entity." + }, + "position": { + "tooltip": "The global position of this entity." + }, + "rotation": { + "tooltip": "The rotation of the entity with respect to world coordinates." + }, + "dimensions": { + "tooltip": "The global dimensions of this entity." + }, + "scale": { + "tooltip": "The global scaling of this entity,", + "skipJSProperty": true + }, + "registrationPoint": { + "tooltip": "The point in the entity at which the entity is rotated about." + }, + "collisionless": { + "tooltip": "If enabled, this entity will collide with other entities or avatars." + }, + "dynamic": { + "tooltip": "If enabled, this entity has collisions associated with it that can affect its movement." + }, + "collidesWithStatic": { + "tooltip": "If enabled, this entity will collide with other non-moving, static entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithDynamic": { + "tooltip": "If enabled, this entity will collide with other dynamic entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithKinematic": { + "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", + "jsPropertyName": "collidesWith" + }, + "collidesWithOtherAvatars": { + "tooltip": "If enabled, this entity will collide with other user's avatars.", + "jsPropertyName": "collidesWith" + }, + "collisionSoundURL": { + "tooltip": "The URL of a sound to play when the entity collides with something else." + }, + "grabbable": { + "tooltip": "If enabled, this entity will allow grabbing input and will be moveable.", + "jsPropertyName": "userData[\"grabbableKey\"][\"grabbable\"]" + }, + "triggerable": { + "tooltip": "If enabled, the collider on this entity is used for triggering events.", + "jsPropertyName": "userData[\"grabbableKey\"][\"triggerable\"]" + }, + "cloneable": { + "tooltip": "If enabled, this entity can be duplicated." + }, + "cloneLifetime": { + "tooltip": "The lifetime for clones of this entity." + }, + "cloneLimit": { + "tooltip": "The total number of clones of this entity that can exist in the domain at any given time." + }, + "cloneDynamic": { + "tooltip": "If enabled, then clones created from this entity will be dynamic, allowing the clone to collide." + }, + "cloneAvatarEntity": { + "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." + }, + "ignoreIK": { + "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand.", + "jsPropertyName": "userData[\"grabbableKey\"][\"ignoreIK\"]" + }, + "canCastShadow": { + "tooltip": "If enabled, this geometry of this entity casts shadows when a shadow-casting light source shines on it." + }, + "parentID": { + "tooltip": "The ID of the entity or avatar that this entity is parented to." + }, + "parentJointIndex": { + "tooltip": "If the entity is parented to an avatar, this joint defines where on the avatar the entity is parented." + }, + "href": { + "tooltip": "The URL that will be opened when a user clicks on this entity. Useful for web pages and portals." + }, + "script": { + "tooltip": "The URL to an external JS file to add behaviors to the client." + }, + "serverScripts": { + "tooltip": "The URL to an external JS file to add behaviors to the server." + }, + "serverScriptsStatus": { + "tooltip": "The status of the server script, if provided. This shows if it's running or has an error.", + "skipJSProperty": true + }, + "hasLifetime": { + "tooltip": "If enabled, the entity will disappear after a certain amount of time specified by Lifetime.", + "jsPropertyName": "lifetime" + }, + "lifetime": { + "tooltip": "The time this entity will exist in the environment for." + }, + "userData": { + "tooltip": "Used to store extra data about the entity in JSON format." + }, + "velocity": { + "tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space." + }, + "damping": { + "tooltip": "The linear damping to slow down the linear velocity of an entity over time." + }, + "angularVelocity": { + "tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point." + }, + "angularDamping": { + "tooltip": "The angular damping to slow down the angular velocity of an entity over time." + }, + "restitution": { + "tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness." + }, + "friction": { + "tooltip": "The friction applied to slow down an entity when it's moving against another entity." + }, + "density": { + "tooltip": "The density of the entity. The higher the density, the harder the entity is to move." + }, + "gravity": { + "tooltip": "The acceleration due to gravity that the entity should move with, in world space." + }, + "acceleration": { + "tooltip": "A acceleration that the entity should move with, in world space." + }, + "createModel": { + "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", + "skipJSProperty": true + }, + "createShape": { + "tooltip": "An entity that has many different primitive shapes.", + "skipJSProperty": true + }, + "createLight": { + "tooltip": "An entity that emits light.", + "skipJSProperty": true + }, + "createText": { + "tooltip": "An entity that displays text on a panel.", + "skipJSProperty": true + }, + "createImage": { + "tooltip": "An entity that displays an image on a panel.", + "skipJSProperty": true + }, + "createWeb": { + "tooltip": "An entity that displays a web page on a panel.", + "skipJSProperty": true + }, + "createZone": { + "tooltip": "An entity that can be used for skyboxes, lighting, and can constrain or change avatar behaviors.", + "skipJSProperty": true + }, + "createParticle": { + "tooltip": "An entity that emits particles.", + "skipJSProperty": true + }, + "createMaterial": { + "tooltip": "An entity that creates a material that can be attached to a Shape or Model.", + "skipJSProperty": true + }, + "useAssetServer": { + "tooltip": "A server that hosts content and assets. You can't take items that are hosted here into other domains.", + "skipJSProperty": true + }, + "importNewEntity": { + "tooltip": "Import a local or hosted file that can be used across domains.", + "skipJSProperty": true + } +} diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 9f4ec3c62b..c6432ac2f9 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2481,6 +2481,11 @@ var PropertiesTool = function (opts) { } } else if (data.type === "propertiesPageReady") { updateSelections(true); + } else if (data.type === "tooltipsRequest") { + emitScriptEvent({ + type: 'tooltipsReply', + tooltips: Script.require('./assets/data/createAppTooltips.json') + }); } }; diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 2db7fce065..4ad886f1b7 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1598,3 +1598,25 @@ input.rename-entity { padding-left: 2px; } +.createAppTooltip { + position: absolute; + background: #6a6a6a; + border: 1px solid black; + width: 258px; + min-height: 20px; + padding: 5px; +} + +.createAppTooltip .createAppTooltipDescription { + font-size: 12px; + font-style: italic; + color: #ffffff; +} + +.createAppTooltip .createAppTooltipJSAttribute { + font-size: 10px; + color: #000000; + bottom: 0; + margin-top: 5px; +} + diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 03b034fc83..b56ad346e2 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -21,6 +21,7 @@ + diff --git a/scripts/system/html/js/createAppTooltip.js b/scripts/system/html/js/createAppTooltip.js new file mode 100644 index 0000000000..10ad73e1ee --- /dev/null +++ b/scripts/system/html/js/createAppTooltip.js @@ -0,0 +1,84 @@ +// createAppTooltip.js +// +// Created by Thijs Wenker on 17 Oct 2018 +// Copyright 2018 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 + +const CREATE_APP_TOOLTIP_OFFSET = 20; + +function CreateAppTooltip() { + this._tooltipData = null; + this._tooltipDiv = null; +} + +CreateAppTooltip.prototype = { + _tooltipData: null, + _tooltipDiv: null, + + _removeTooltipIfExists: function() { + if (this._tooltipDiv !== null) { + this._tooltipDiv.remove(); + this._tooltipDiv = null; + } + }, + + setTooltipData: function(tooltipData) { + this._tooltipData = tooltipData; + }, + + registerTooltipElement: function(element, tooltipID) { + element.addEventListener("mouseover", function() { + + this._removeTooltipIfExists(); + + let tooltipData = this._tooltipData[tooltipID]; + + if (!tooltipData || tooltipData.tooltip === "") { + return; + } + + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "createAppTooltip"; + + let elTipDescription = document.createElement("div"); + elTipDescription.className = "createAppTooltipDescription"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); + + let jsAttribute = tooltipID; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } + + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "createAppTooltipJSAttribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } + + document.body.appendChild(elTip); + + let elementTop = window.pageYOffset + elementRect.top; + + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip on below by default + elTip.style.top = desiredTooltipTop; + } + elTip.style.left = window.pageXOffset + elementRect.left; + + this._tooltipDiv = elTip; + }.bind(this), false); + element.addEventListener("mouseout", function() { + this._removeTooltipIfExists(); + }.bind(this), false); + } +}; diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 676ecaf289..59c2ba702b 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1307,6 +1307,7 @@ var colorPickers = {}; var particlePropertyUpdates = {}; var selectedEntityProperties; var lastEntityID = null; +var createAppTooltip = new CreateAppTooltip(); function debugPrint(message) { EventBridge.emitWebEvent( @@ -2659,7 +2660,7 @@ function showParentMaterialNameBox(number, elNumber, elString) { function loaded() { - openEventBridge(function() { + openEventBridge(function() { let elPropertiesList = document.getElementById("properties-list"); GROUPS.forEach(function(group) { @@ -2728,6 +2729,8 @@ function loaded() { let elLabel = document.createElement('label'); elLabel.innerText = propertyData.label; elLabel.setAttribute("for", propertyElementID); + + createAppTooltip.registerTooltipElement(elLabel, propertyID); let property = { data: propertyData, @@ -3150,6 +3153,8 @@ function loaded() { activeElement.select(); } } + } else if (data.type === 'tooltipsReply') { + createAppTooltip.setTooltipData(data.tooltips); } }); } @@ -3381,5 +3386,6 @@ function loaded() { setTimeout(function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'propertiesPageReady' })); + EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' })); }, 1000); } From 358b0b76d1f869882b02eead537d6a20e70f2d41 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 19 Oct 2018 16:32:38 +0200 Subject: [PATCH 112/131] tooltip delay of 500ms --- .../system/assets/data/createAppTooltips.json | 2 +- scripts/system/html/js/createAppTooltip.js | 76 +++++++++++-------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index f8c037297f..7cc9e0a97e 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -294,7 +294,7 @@ "tooltip": "The global dimensions of this entity." }, "scale": { - "tooltip": "The global scaling of this entity,", + "tooltip": "The global scaling of this entity.", "skipJSProperty": true }, "registrationPoint": { diff --git a/scripts/system/html/js/createAppTooltip.js b/scripts/system/html/js/createAppTooltip.js index 10ad73e1ee..edd0f6366a 100644 --- a/scripts/system/html/js/createAppTooltip.js +++ b/scripts/system/html/js/createAppTooltip.js @@ -7,17 +7,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html const CREATE_APP_TOOLTIP_OFFSET = 20; +const TOOLTIP_DELAY = 500; // ms function CreateAppTooltip() { this._tooltipData = null; this._tooltipDiv = null; + this._delayTimeout = null; } CreateAppTooltip.prototype = { _tooltipData: null, _tooltipDiv: null, + _delayTimeout: null, _removeTooltipIfExists: function() { + if (this._delayTimeout !== null) { + window.clearTimeout(this._delayTimeout); + this._delayTimeout = null; + } + if (this._tooltipDiv !== null) { this._tooltipDiv.remove(); this._tooltipDiv = null; @@ -33,49 +41,51 @@ CreateAppTooltip.prototype = { this._removeTooltipIfExists(); - let tooltipData = this._tooltipData[tooltipID]; + this._delayTimeout = window.setTimeout(function() { + let tooltipData = this._tooltipData[tooltipID]; - if (!tooltipData || tooltipData.tooltip === "") { - return; - } + if (!tooltipData || tooltipData.tooltip === "") { + return; + } - let elementRect = element.getBoundingClientRect(); - let elTip = document.createElement("div"); - elTip.className = "createAppTooltip"; + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "createAppTooltip"; - let elTipDescription = document.createElement("div"); - elTipDescription.className = "createAppTooltipDescription"; - elTipDescription.innerText = tooltipData.tooltip; - elTip.appendChild(elTipDescription); + let elTipDescription = document.createElement("div"); + elTipDescription.className = "createAppTooltipDescription"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); - let jsAttribute = tooltipID; - if (tooltipData.jsPropertyName) { - jsAttribute = tooltipData.jsPropertyName; - } + let jsAttribute = tooltipID; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } - if (!tooltipData.skipJSProperty) { - let elTipJSAttribute = document.createElement("div"); - elTipJSAttribute.className = "createAppTooltipJSAttribute"; - elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; - elTip.appendChild(elTipJSAttribute); - } + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "createAppTooltipJSAttribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } - document.body.appendChild(elTip); + document.body.appendChild(elTip); - let elementTop = window.pageYOffset + elementRect.top; + let elementTop = window.pageYOffset + elementRect.top; - let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; - if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { - // show above when otherwise out of bounds - elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; - } else { - // show tooltip on below by default - elTip.style.top = desiredTooltipTop; - } - elTip.style.left = window.pageXOffset + elementRect.left; + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip on below by default + elTip.style.top = desiredTooltipTop; + } + elTip.style.left = window.pageXOffset + elementRect.left; - this._tooltipDiv = elTip; + this._tooltipDiv = elTip; + }.bind(this), TOOLTIP_DELAY); }.bind(this), false); element.addEventListener("mouseout", function() { this._removeTooltipIfExists(); From 83051940a854d8bee91e23f6542fa45554503e98 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Oct 2018 04:49:45 +0200 Subject: [PATCH 113/131] Fix QA feedback --- .../system/assets/data/createAppTooltips.json | 45 ++++++++++++------- scripts/system/edit.js | 3 +- scripts/system/html/js/createAppTooltip.js | 26 ++++++++++- scripts/system/html/js/entityProperties.js | 2 + 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 7cc9e0a97e..0e6818c512 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -147,7 +147,7 @@ "tooltip": "The speed of the animation." }, "textures": { - "tooltip": "The URL of a JPG or PNG image file to display for each particle." + "tooltip": "A JSON string containing a texture. Use a name from the Original Texture property to override it." }, "originalTextures": { "tooltip": "A JSON string containing the original texture used on the model." @@ -171,6 +171,10 @@ "maxParticles": { "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." }, + "particleTextures": { + "tooltip": "The URL of a JPG or PNG image file to display for each particle.", + "jsPropertyName": "textures" + }, "emitRate": { "tooltip": "The number of particles per second to emit." }, @@ -244,16 +248,16 @@ "rotateWithEntity": { "tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole." }, - "azimuthStart": { + "polarStart": { "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." }, - "azimuthFinish": { + "polarFinish": { "tooltip": "" }, - "polarStart": { + "azimuthStart": { "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." }, - "polarFinish": { + "azimuthFinish": { "tooltip": "" }, "lightColor": { @@ -300,6 +304,12 @@ "registrationPoint": { "tooltip": "The point in the entity at which the entity is rotated about." }, + "visible": { + "tooltip": "If enabled, this entity will be visible." + }, + "locked": { + "tooltip": "If enabled, this entity will be locked." + }, "collisionless": { "tooltip": "If enabled, this entity will collide with other entities or avatars." }, @@ -318,20 +328,22 @@ "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", "jsPropertyName": "collidesWith" }, - "collidesWithOtherAvatars": { + "collidesWithOtherAvatar": { "tooltip": "If enabled, this entity will collide with other user's avatars.", "jsPropertyName": "collidesWith" }, + "collidesWithMyAvatar": { + "tooltip": "If enabled, this entity will collide with your own avatar.", + "jsPropertyName": "collidesWith" + }, "collisionSoundURL": { "tooltip": "The URL of a sound to play when the entity collides with something else." }, - "grabbable": { - "tooltip": "If enabled, this entity will allow grabbing input and will be moveable.", - "jsPropertyName": "userData[\"grabbableKey\"][\"grabbable\"]" + "grab.grabbable": { + "tooltip": "If enabled, this entity will allow grabbing input and will be moveable." }, - "triggerable": { - "tooltip": "If enabled, the collider on this entity is used for triggering events.", - "jsPropertyName": "userData[\"grabbableKey\"][\"triggerable\"]" + "grab.triggerable": { + "tooltip": "If enabled, the collider on this entity is used for triggering events." }, "cloneable": { "tooltip": "If enabled, this entity can be duplicated." @@ -348,9 +360,8 @@ "cloneAvatarEntity": { "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." }, - "ignoreIK": { - "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand.", - "jsPropertyName": "userData[\"grabbableKey\"][\"ignoreIK\"]" + "grab.grabFollowsController": { + "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand." }, "canCastShadow": { "tooltip": "If enabled, this geometry of this entity casts shadows when a shadow-casting light source shines on it." @@ -411,6 +422,10 @@ "acceleration": { "tooltip": "A acceleration that the entity should move with, in world space." }, + "alignToGrid": { + "tooltip": "Used to align entities to the grid, or floor of the environment.", + "skipJSProperty": true + }, "createModel": { "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", "skipJSProperty": true diff --git a/scripts/system/edit.js b/scripts/system/edit.js index c6432ac2f9..6425806771 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2484,7 +2484,8 @@ var PropertiesTool = function (opts) { } else if (data.type === "tooltipsRequest") { emitScriptEvent({ type: 'tooltipsReply', - tooltips: Script.require('./assets/data/createAppTooltips.json') + tooltips: Script.require('./assets/data/createAppTooltips.json'), + hmdActive: HMD.active, }); } }; diff --git a/scripts/system/html/js/createAppTooltip.js b/scripts/system/html/js/createAppTooltip.js index edd0f6366a..a42e5efe05 100644 --- a/scripts/system/html/js/createAppTooltip.js +++ b/scripts/system/html/js/createAppTooltip.js @@ -8,17 +8,20 @@ const CREATE_APP_TOOLTIP_OFFSET = 20; const TOOLTIP_DELAY = 500; // ms +const TOOLTIP_DEBUG = false; function CreateAppTooltip() { this._tooltipData = null; this._tooltipDiv = null; this._delayTimeout = null; + this._isEnabled = false; } CreateAppTooltip.prototype = { _tooltipData: null, _tooltipDiv: null, _delayTimeout: null, + _isEnabled: null, _removeTooltipIfExists: function() { if (this._delayTimeout !== null) { @@ -32,12 +35,19 @@ CreateAppTooltip.prototype = { } }, + setIsEnabled: function(isEnabled) { + this._isEnabled = isEnabled; + }, + setTooltipData: function(tooltipData) { this._tooltipData = tooltipData; }, registerTooltipElement: function(element, tooltipID) { element.addEventListener("mouseover", function() { + if (!this._isEnabled) { + return; + } this._removeTooltipIfExists(); @@ -45,7 +55,10 @@ CreateAppTooltip.prototype = { let tooltipData = this._tooltipData[tooltipID]; if (!tooltipData || tooltipData.tooltip === "") { - return; + if (!TOOLTIP_DEBUG) { + return; + } + tooltipData = {tooltip: 'PLEASE SET THIS TOOLTIP'}; } let elementRect = element.getBoundingClientRect(); @@ -74,6 +87,7 @@ CreateAppTooltip.prototype = { let elementTop = window.pageYOffset + elementRect.top; let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipLeft = window.pageXOffset + elementRect.left; if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { // show above when otherwise out of bounds @@ -82,12 +96,20 @@ CreateAppTooltip.prototype = { // show tooltip on below by default elTip.style.top = desiredTooltipTop; } - elTip.style.left = window.pageXOffset + elementRect.left; + if ((window.innerWidth + window.pageXOffset) < (desiredTooltipLeft + elTip.clientWidth)) { + elTip.style.left = document.body.clientWidth + window.pageXOffset - elTip.offsetWidth; + } else { + elTip.style.left = desiredTooltipLeft; + } this._tooltipDiv = elTip; }.bind(this), TOOLTIP_DELAY); }.bind(this), false); element.addEventListener("mouseout", function() { + if (!this._isEnabled) { + return; + } + this._removeTooltipIfExists(); }.bind(this), false); } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 59c2ba702b..173d806d59 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3154,6 +3154,7 @@ function loaded() { } } } else if (data.type === 'tooltipsReply') { + createAppTooltip.setIsEnabled(!data.hmdActive); createAppTooltip.setTooltipData(data.tooltips); } }); @@ -3170,6 +3171,7 @@ function loaded() { let elLabel = document.createElement('label'); elLabel.setAttribute("for", serverScriptStatusElementID); elLabel.innerText = "Server Script Status"; + createAppTooltip.registerTooltipElement(elLabel, "serverScriptsStatus"); let elServerScriptStatus = document.createElement('span'); elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); elDiv.appendChild(elLabel); From bb7d1f8afb2cef4a47a7b21ebc75f60742235161 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Oct 2018 22:37:21 +0200 Subject: [PATCH 114/131] fix broken and missing tooltips --- .../system/assets/data/createAppTooltips.json | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 0e6818c512..83ddcaa34b 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -5,9 +5,6 @@ "color": { "tooltip": "The RGB value of this entity." }, - "materialURL": { - "tooltip": "The URL of a JSON file containing the material. Use this to change the entity's look. " - }, "text": { "tooltip": "The text to display on the entity." }, @@ -35,7 +32,7 @@ "keyLightMode": { "tooltip": "Configures the key light in the zone. This light is directional." }, - "keyLightColor": { + "keyLight.color": { "tooltip": "The color of the key light." }, "keyLight.intensity": { @@ -152,7 +149,7 @@ "originalTextures": { "tooltip": "A JSON string containing the original texture used on the model." }, - "imageUrl": { + "image": { "tooltip": "The URL for the image source.", "jsPropertyName": "textures" }, @@ -279,6 +276,36 @@ "cutoff": { "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." }, + "materialURL": { + "tooltip": "The URL to an external JSON file or \"materialData\", \"materialData? to use Material Data." + }, + "materialData": { + "tooltip": "Can be used instead of a JSON file when material set to materialData." + }, + "materialNameToReplace": { + "tooltip": "Material name of parent entity to map this material entity on.", + "jsPropertyName": "parentMaterialName" + }, + "submeshToReplace": { + "tooltip": "Submesh index of the parent entity to map this material on.", + "jsPropertyName": "parentMaterialName" + }, + "selectSubmesh": { + "tooltip": "If enabled, \"Select Submesh\" property will show up, otherwise \"Material Name to Replace\" will be shown.", + "skipJSProperty": true + }, + "priority": { + "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." + }, + "materialMappingPos": { + "tooltip": "The offset position of the bottom left of the material within the parent's UV space." + }, + "materialMappingScale": { + "tooltip": "How many times the material will repeat in each direction within the parent's UV space." + }, + "materialMappingRot": { + "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." + }, "id": { "tooltip": "The unique identifier of this entity." }, From 13cad17793d8d5d2751ac3b8dae83d290838a7b3 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 24 Oct 2018 21:35:47 +0200 Subject: [PATCH 115/131] better style js attributes --- scripts/system/html/css/edit-style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 4ad886f1b7..cf7124d9eb 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1614,7 +1614,8 @@ input.rename-entity { } .createAppTooltip .createAppTooltipJSAttribute { - font-size: 10px; + font-family: Raleway-SemiBold; + font-size: 11px; color: #000000; bottom: 0; margin-top: 5px; From 252e504ab91e9e53bc963f386c4ee122e205343d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 20 Oct 2018 10:12:52 -0700 Subject: [PATCH 116/131] attempt to keep userData and new grab properties in sync --- .../entities/src/EntityItemProperties.cpp | 10 + libraries/entities/src/EntityItemProperties.h | 1 + .../entities/src/EntityScriptingInterface.cpp | 232 ++++++++++++++++++ libraries/entities/src/EntityTree.cpp | 211 ++++++++-------- libraries/entities/src/EntityTree.h | 2 + libraries/entities/src/GrabPropertyGroup.cpp | 30 +++ 6 files changed, 388 insertions(+), 98 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 37d8dc0bdc..d901592759 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -3803,6 +3803,16 @@ bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const { return parentRelatedPropertyChanged() || dimensionsChanged(); } +bool EntityItemProperties::grabbingRelatedPropertyChanged() const { + const GrabPropertyGroup& grabProperties = getGrab(); + return grabProperties.triggerableChanged() || grabProperties.grabbableChanged() || + grabProperties.grabFollowsControllerChanged() || grabProperties.grabKinematicChanged() || + grabProperties.equippableChanged() || grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || grabProperties.equippableLeftRotationChanged() || + grabProperties.equippableRightRotationChanged() || grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || grabProperties.equippableIndicatorOffsetChanged(); +} + // Checking Certifiable Properties #define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N() #define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString() diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ae2c402d22..c91ccda5aa 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -108,6 +108,7 @@ public: bool getScalesWithParent() const; bool parentRelatedPropertyChanged() const; bool queryAACubeRelatedPropertyChanged() const; + bool grabbingRelatedPropertyChanged() const; AABox getAABox() const; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index f8b22fdbae..ae951c7862 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -16,6 +16,9 @@ #include #include +#include +#include +#include #include #include @@ -37,6 +40,7 @@ #include "WebEntityItem.h" #include #include +#include "GrabPropertyGroup.h" const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; @@ -237,6 +241,229 @@ EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProper } +void synchronizeSpatialKey(const GrabPropertyGroup& grabProperties, QJsonObject& grabbableKey, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); + + if (grabProperties.equippableLeftPositionChanged()) { + if (grabProperties.getEquippableLeftPosition() == INITIAL_LEFT_EQUIPPABLE_POSITION) { + spatialKey.remove("leftRelativePosition"); + } else { + spatialKey["leftRelativePosition"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())); + } + } + if (grabProperties.equippableRightPositionChanged()) { + if (grabProperties.getEquippableRightPosition() == INITIAL_RIGHT_EQUIPPABLE_POSITION) { + spatialKey.remove("rightRelativePosition"); + } else { + spatialKey["rightRelativePosition"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())); + } + } + if (grabProperties.equippableLeftRotationChanged()) { + spatialKey["relativeRotation"] = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())); + } else if (grabProperties.equippableRightRotationChanged()) { + spatialKey["relativeRotation"] = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())); + } + + grabbableKey["spatialKey"] = spatialKey; + userDataChanged = true; + } +} + + +void synchronizeGrabbableKey(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.triggerableChanged() || + grabProperties.grabbableChanged() || + grabProperties.grabFollowsControllerChanged() || + grabProperties.grabKinematicChanged() || + grabProperties.equippableChanged()) { + + QJsonObject grabbableKey = userData["grabbableKey"].toObject(); + + if (grabProperties.triggerableChanged()) { + if (grabProperties.getTriggerable()) { + grabbableKey["triggerable"] = true; + } else { + grabbableKey.remove("triggerable"); + } + } + if (grabProperties.grabbableChanged()) { + if (grabProperties.getGrabbable()) { + grabbableKey.remove("grabbable"); + } else { + grabbableKey["grabbable"] = false; + } + } + if (grabProperties.grabFollowsControllerChanged()) { + if (grabProperties.getGrabFollowsController()) { + grabbableKey.remove("ignoreIK"); + } else { + grabbableKey["ignoreIK"] = false; + } + } + if (grabProperties.grabKinematicChanged()) { + if (grabProperties.getGrabKinematic()) { + grabbableKey.remove("kinematic"); + } else { + grabbableKey["kinematic"] = false; + } + } + if (grabProperties.equippableChanged()) { + if (grabProperties.getEquippable()) { + grabbableKey["equippable"] = true; + } else { + grabbableKey.remove("equippable"); + } + } + + if (grabbableKey.contains("spatialKey")) { + synchronizeSpatialKey(grabProperties, grabbableKey, userDataChanged); + } + + userData["grabbableKey"] = grabbableKey; + userDataChanged = true; + } +} + +void synchronizeGrabJoints(const GrabPropertyGroup& grabProperties, QJsonObject& joints) { + QJsonArray rightHand = joints["RightHand"].toArray(); + QJsonObject rightHandPosition = rightHand.size() > 0 ? rightHand[0].toObject() : QJsonObject(); + QJsonObject rightHandRotation = rightHand.size() > 1 ? rightHand[1].toObject() : QJsonObject(); + QJsonArray leftHand = joints["LeftHand"].toArray(); + QJsonObject leftHandPosition = leftHand.size() > 0 ? leftHand[0].toObject() : QJsonObject(); + QJsonObject leftHandRotation = leftHand.size() > 1 ? leftHand[1].toObject() : QJsonObject(); + + if (grabProperties.equippableLeftPositionChanged()) { + leftHandPosition = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())).toObject(); + } + if (grabProperties.equippableRightPositionChanged()) { + rightHandPosition = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())).toObject(); + } + if (grabProperties.equippableLeftRotationChanged()) { + leftHandRotation = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())).toObject(); + } + if (grabProperties.equippableRightRotationChanged()) { + rightHandRotation = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())).toObject(); + } + + rightHand[0] = rightHandPosition; + rightHand[1] = rightHandRotation; + joints["RightHand"] = rightHand; + leftHand[0] = leftHandPosition; + leftHand[1] = leftHandRotation; + joints["LeftHand"] = leftHand; +} + +void synchronizeEquipHotspot(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonArray equipHotspots = userData["equipHotspots"].toArray(); + QJsonObject equipHotspot = equipHotspots[0].toObject(); + QJsonObject joints = equipHotspot["joints"].toObject(); + + synchronizeGrabJoints(grabProperties, joints); + + if (grabProperties.equippableIndicatorURLChanged()) { + equipHotspot["modelURL"] = grabProperties.getEquippableIndicatorURL(); + } + if (grabProperties.equippableIndicatorScaleChanged()) { + QJsonObject scale = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableIndicatorScale())).toObject(); + equipHotspot["radius"] = scale; + equipHotspot["modelScale"] = scale; + + } + if (grabProperties.equippableIndicatorOffsetChanged()) { + equipHotspot["position"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableIndicatorOffset())).toObject(); + } + + equipHotspot["joints"] = joints; + equipHotspots[0] = equipHotspot; + userData["equipHotspots"] = equipHotspots; + userDataChanged = true; + } +} + +void synchronizeWearable(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonObject wearable = userData["wearable"].toObject(); + QJsonObject joints = wearable["joints"].toObject(); + + synchronizeGrabJoints(grabProperties, joints); + + wearable["joints"] = joints; + userData["wearable"] = wearable; + userDataChanged = true; + } +} + +void synchronizeEditedGrabProperties(EntityItemProperties& properties, const QString& previousUserdata) { + // After sufficient warning to content creators, we should be able to remove this. + + if (properties.grabbingRelatedPropertyChanged()) { + // This edit touches a new-style grab property, so make userData agree... + GrabPropertyGroup& grabProperties = properties.getGrab(); + + bool userDataChanged { false }; + + // if the edit changed userData, use the updated version coming along with the edit. If not, use + // what was already in the entity. + QByteArray userDataString; + if (properties.userDataChanged()) { + userDataString = properties.getUserData().toUtf8(); + } else { + userDataString = previousUserdata.toUtf8();; + } + QJsonObject userData = QJsonDocument::fromJson(userDataString).object(); + + if (userData.contains("grabbableKey")) { + synchronizeGrabbableKey(grabProperties, userData, userDataChanged); + } + if (userData.contains("equipHotspots")) { + synchronizeEquipHotspot(grabProperties, userData, userDataChanged); + } + if (userData.contains("wearable")) { + synchronizeWearable(grabProperties, userData, userDataChanged); + } + + if (userDataChanged) { + properties.setUserData(QJsonDocument(userData).toJson()); + } + + } else if (properties.userDataChanged()) { + // This edit touches userData (and doesn't touch a new-style grab property). Check for grabbableKey in the + // userdata and make the new-style grab properties agree + convertGrabUserDataToProperties(properties); + } +} + + QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { PROFILE_RANGE(script_entities, __FUNCTION__); @@ -257,6 +484,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + synchronizeEditedGrabProperties(propertiesWithSimID, QString()); EntityItemID id; // If we have a local entity tree set, then also update it. @@ -559,6 +787,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& simulationOwner = entity->getSimulationOwner(); }); + QString previousUserdata; if (entity) { if (properties.hasSimulationRestrictedChanges()) { if (_bidOnSimulationOwnership) { @@ -597,6 +826,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // make sure the properties has a type, so that the encode can know which properties to include properties.setType(entity->getType()); + + previousUserdata = entity->getUserData(); } else if (_bidOnSimulationOwnership) { // bail when simulation participants don't know about entity return QUuid(); @@ -605,6 +836,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // How to check for this cheaply? properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); + synchronizeEditedGrabProperties(properties, previousUserdata); properties.setLastEditedBy(sessionID); // done reading and modifying properties --> start write diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0b3b8abba2..fdd92eb11c 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2493,6 +2493,118 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer return true; } +void convertGrabUserDataToProperties(EntityItemProperties& properties) { + GrabPropertyGroup& grabProperties = properties.getGrab(); + QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object(); + + QJsonValue grabbableKeyValue = userData["grabbableKey"]; + if (grabbableKeyValue.isObject()) { + QJsonObject grabbableKey = grabbableKeyValue.toObject(); + + QJsonValue wantsTrigger = grabbableKey["wantsTrigger"]; + if (wantsTrigger.isBool()) { + grabProperties.setTriggerable(wantsTrigger.toBool()); + } + QJsonValue triggerable = grabbableKey["triggerable"]; + if (triggerable.isBool()) { + grabProperties.setTriggerable(triggerable.toBool()); + } + QJsonValue grabbable = grabbableKey["grabbable"]; + if (grabbable.isBool()) { + grabProperties.setGrabbable(grabbable.toBool()); + } + QJsonValue ignoreIK = grabbableKey["ignoreIK"]; + if (ignoreIK.isBool()) { + grabProperties.setGrabFollowsController(ignoreIK.toBool()); + } + QJsonValue kinematic = grabbableKey["kinematic"]; + if (kinematic.isBool()) { + grabProperties.setGrabKinematic(kinematic.toBool()); + } + QJsonValue equippable = grabbableKey["equippable"]; + if (equippable.isBool()) { + grabProperties.setEquippable(equippable.toBool()); + } + + if (grabbableKey["spatialKey"].isObject()) { + QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); + grabProperties.setEquippable(true); + if (spatialKey["leftRelativePosition"].isObject()) { + grabProperties.setEquippableLeftPosition(qMapToVec3(spatialKey["leftRelativePosition"].toVariant())); + } + if (spatialKey["rightRelativePosition"].isObject()) { + grabProperties.setEquippableRightPosition(qMapToVec3(spatialKey["rightRelativePosition"].toVariant())); + } + if (spatialKey["relativeRotation"].isObject()) { + grabProperties.setEquippableLeftRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); + } + } + } + + QJsonValue wearableValue = userData["wearable"]; + if (wearableValue.isObject()) { + QJsonObject wearable = wearableValue.toObject(); + QJsonObject joints = wearable["joints"].toObject(); + if (joints["LeftHand"].isArray()) { + QJsonArray leftHand = joints["LeftHand"].toArray(); + if (leftHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); + grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); + } + } + if (joints["RightHand"].isArray()) { + QJsonArray rightHand = joints["RightHand"].toArray(); + if (rightHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); + } + } + } + + QJsonValue equipHotspotsValue = userData["equipHotspots"]; + if (equipHotspotsValue.isArray()) { + QJsonArray equipHotspots = equipHotspotsValue.toArray(); + if (equipHotspots.size() > 0) { + // just take the first one + QJsonObject firstHotSpot = equipHotspots[0].toObject(); + QJsonObject joints = firstHotSpot["joints"].toObject(); + if (joints["LeftHand"].isArray()) { + QJsonArray leftHand = joints["LeftHand"].toArray(); + if (leftHand.size() == 2) { + grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); + grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); + } + } + if (joints["RightHand"].isArray()) { + QJsonArray rightHand = joints["RightHand"].toArray(); + if (rightHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); + } + } + QJsonValue indicatorURL = firstHotSpot["modelURL"]; + if (indicatorURL.isString()) { + grabProperties.setEquippableIndicatorURL(indicatorURL.toString()); + } + QJsonValue indicatorScale = firstHotSpot["modelScale"]; + if (indicatorScale.isDouble()) { + grabProperties.setEquippableIndicatorScale(glm::vec3((float)indicatorScale.toDouble())); + } else if (indicatorScale.isObject()) { + grabProperties.setEquippableIndicatorScale(qMapToVec3(indicatorScale.toVariant())); + } + QJsonValue indicatorOffset = firstHotSpot["position"]; + if (indicatorOffset.isObject()) { + grabProperties.setEquippableIndicatorOffset(qMapToVec3(indicatorOffset.toVariant())); + } + } + } +} + + bool EntityTree::readFromMap(QVariantMap& map) { // These are needed to deal with older content (before adding inheritance modes) int contentVersion = map["Version"].toInt(); @@ -2639,104 +2751,7 @@ bool EntityTree::readFromMap(QVariantMap& map) { // convert old grab-related userData to new grab properties if (contentVersion < (int)EntityVersion::GrabProperties) { - QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object(); - QJsonObject grabbableKey = userData["grabbableKey"].toObject(); - QJsonValue wantsTrigger = grabbableKey["wantsTrigger"]; - - GrabPropertyGroup& grabProperties = properties.getGrab(); - - if (wantsTrigger.isBool()) { - grabProperties.setTriggerable(wantsTrigger.toBool()); - } - QJsonValue triggerable = grabbableKey["triggerable"]; - if (triggerable.isBool()) { - grabProperties.setTriggerable(triggerable.toBool()); - } - QJsonValue grabbable = grabbableKey["grabbable"]; - if (grabbable.isBool()) { - grabProperties.setGrabbable(grabbable.toBool()); - } - QJsonValue ignoreIK = grabbableKey["ignoreIK"]; - if (ignoreIK.isBool()) { - grabProperties.setGrabFollowsController(ignoreIK.toBool()); - } - QJsonValue kinematic = grabbableKey["kinematic"]; - if (kinematic.isBool()) { - grabProperties.setGrabKinematic(kinematic.toBool()); - } - - if (grabbableKey["spatialKey"].isObject()) { - QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); - grabProperties.setEquippable(true); - if (spatialKey["leftRelativePosition"].isObject()) { - grabProperties.setEquippableLeftPosition(qMapToVec3(spatialKey["leftRelativePosition"].toVariant())); - } - if (spatialKey["rightRelativePosition"].isObject()) { - grabProperties.setEquippableRightPosition(qMapToVec3(spatialKey["rightRelativePosition"].toVariant())); - } - if (spatialKey["relativeRotation"].isObject()) { - grabProperties.setEquippableLeftRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); - } - } - - QJsonObject wearable = userData["wearable"].toObject(); - QJsonObject joints = wearable["joints"].toObject(); - if (joints["LeftHand"].isArray()) { - QJsonArray leftHand = joints["LeftHand"].toArray(); - if (leftHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); - grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); - } - } - if (joints["RightHand"].isArray()) { - QJsonArray rightHand = joints["RightHand"].toArray(); - if (rightHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); - } - } - - if (userData["equipHotspots"].isArray()) { - QJsonArray equipHotspots = userData["equipHotspots"].toArray(); - if (equipHotspots.size() > 0) { - // just take the first one - QJsonObject firstHotSpot = equipHotspots[0].toObject(); - QJsonObject joints = firstHotSpot["joints"].toObject(); - if (joints["LeftHand"].isArray()) { - QJsonArray leftHand = joints["LeftHand"].toArray(); - if (leftHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); - grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); - } - } - if (joints["RightHand"].isArray()) { - QJsonArray rightHand = joints["RightHand"].toArray(); - if (rightHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); - } - } - QJsonValue indicatorURL = firstHotSpot["modelURL"]; - if (indicatorURL.isString()) { - grabProperties.setEquippableIndicatorURL(indicatorURL.toString()); - } - QJsonValue indicatorScale = firstHotSpot["modelScale"]; - if (indicatorScale.isDouble()) { - grabProperties.setEquippableIndicatorScale(glm::vec3((float)indicatorScale.toDouble())); - } else if (indicatorScale.isObject()) { - grabProperties.setEquippableIndicatorScale(qMapToVec3(indicatorScale.toVariant())); - } - QJsonValue indicatorOffset = firstHotSpot["position"]; - if (indicatorOffset.isObject()) { - grabProperties.setEquippableIndicatorOffset(qMapToVec3(indicatorOffset.toVariant())); - } - } - } + convertGrabUserDataToProperties(properties); } // Zero out the spread values that were fixed in version ParticleEntityFix so they behave the same as before diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 2f971b8566..634ffcc1f3 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -424,4 +424,6 @@ private: std::map _namedPaths; }; +void convertGrabUserDataToProperties(EntityItemProperties& properties); + #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/GrabPropertyGroup.cpp b/libraries/entities/src/GrabPropertyGroup.cpp index c433043e31..996eed4720 100644 --- a/libraries/entities/src/GrabPropertyGroup.cpp +++ b/libraries/entities/src/GrabPropertyGroup.cpp @@ -51,6 +51,9 @@ void GrabPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _d COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableLeftRotation, quat, setEquippableLeftRotation); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightPosition, vec3, setEquippableRightPosition); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightRotation, quat, setEquippableRightRotation); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorURL, QString, setEquippableIndicatorURL); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorScale, vec3, setEquippableIndicatorScale); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorOffset, vec3, setEquippableIndicatorOffset); } void GrabPropertyGroup::merge(const GrabPropertyGroup& other) { @@ -63,6 +66,9 @@ void GrabPropertyGroup::merge(const GrabPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(equippableLeftRotation); COPY_PROPERTY_IF_CHANGED(equippableRightPosition); COPY_PROPERTY_IF_CHANGED(equippableRightRotation); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorURL); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorScale); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorOffset); } void GrabPropertyGroup::debugDump() const { @@ -77,6 +83,9 @@ void GrabPropertyGroup::debugDump() const { qCDebug(entities) << " _equippableLeftRotation:" << _equippableLeftRotation; qCDebug(entities) << " _equippableRightPosition:" << _equippableRightPosition; qCDebug(entities) << " _equippableRightRotation:" << _equippableRightRotation; + qCDebug(entities) << " _equippableIndicatorURL:" << _equippableIndicatorURL; + qCDebug(entities) << " _equippableIndicatorScale:" << _equippableIndicatorScale; + qCDebug(entities) << " _equippableIndicatorOffset:" << _equippableIndicatorOffset; } void GrabPropertyGroup::listChangedProperties(QList& out) { @@ -107,6 +116,15 @@ void GrabPropertyGroup::listChangedProperties(QList& out) { if (equippableRightRotationChanged()) { out << "grab-equippableRightRotation"; } + if (equippableIndicatorURLChanged()) { + out << "grab-equippableIndicatorURL"; + } + if (equippableIndicatorScaleChanged()) { + out << "grab-equippableIndicatorScale"; + } + if (equippableIndicatorOffsetChanged()) { + out << "grab-equippableIndicatorOffset"; + } } bool GrabPropertyGroup::appendToEditPacket(OctreePacketData* packetData, @@ -184,6 +202,9 @@ void GrabPropertyGroup::markAllChanged() { _equippableLeftRotationChanged = true; _equippableRightPositionChanged = true; _equippableRightRotationChanged = true; + _equippableIndicatorURLChanged = true; + _equippableIndicatorScaleChanged = true; + _equippableIndicatorOffsetChanged = true; } EntityPropertyFlags GrabPropertyGroup::getChangedProperties() const { @@ -215,6 +236,9 @@ void GrabPropertyGroup::getProperties(EntityItemProperties& properties) const { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableLeftRotation, getEquippableLeftRotation); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightPosition, getEquippableRightPosition); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightRotation, getEquippableRightRotation); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorURL, getEquippableIndicatorURL); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorScale, getEquippableIndicatorScale); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorOffset, getEquippableIndicatorOffset); } bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) { @@ -231,6 +255,12 @@ bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) { setEquippableRightPosition); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableRightRotation, equippableRightRotation, setEquippableRightRotation); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorURL, equippableIndicatorURL, + setEquippableIndicatorURL); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorScale, equippableIndicatorScale, + setEquippableIndicatorScale); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorOffset, equippableIndicatorOffset, + setEquippableIndicatorOffset); return somethingChanged; } From 387a8ce933b2a9d45e7fb818a894a7761f368074 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 24 Oct 2018 10:21:34 -0700 Subject: [PATCH 117/131] fix QJsonArray handling --- libraries/entities/src/EntityScriptingInterface.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index ae951c7862..a920ed92ff 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -360,11 +360,13 @@ void synchronizeGrabJoints(const GrabPropertyGroup& grabProperties, QJsonObject& QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())).toObject(); } - rightHand[0] = rightHandPosition; - rightHand[1] = rightHandRotation; + rightHand = QJsonArray(); + rightHand.append(rightHandPosition); + rightHand.append(rightHandRotation); joints["RightHand"] = rightHand; - leftHand[0] = leftHandPosition; - leftHand[1] = leftHandRotation; + leftHand = QJsonArray(); + leftHand.append(leftHandPosition); + leftHand.append(leftHandRotation); joints["LeftHand"] = leftHand; } @@ -398,7 +400,8 @@ void synchronizeEquipHotspot(const GrabPropertyGroup& grabProperties, QJsonObjec } equipHotspot["joints"] = joints; - equipHotspots[0] = equipHotspot; + equipHotspots = QJsonArray(); + equipHotspots.append(equipHotspot); userData["equipHotspots"] = equipHotspots; userDataChanged = true; } From c5aafd4644be96280bffb98d4bb06f1717ab9c3f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 24 Oct 2018 12:24:06 -0700 Subject: [PATCH 118/131] fix spatialKey conversion --- libraries/entities/src/EntityScriptingInterface.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index a920ed92ff..3491688588 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -286,7 +286,10 @@ void synchronizeGrabbableKey(const GrabPropertyGroup& grabProperties, QJsonObjec grabProperties.grabbableChanged() || grabProperties.grabFollowsControllerChanged() || grabProperties.grabKinematicChanged() || - grabProperties.equippableChanged()) { + grabProperties.equippableChanged() || + grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged()) { QJsonObject grabbableKey = userData["grabbableKey"].toObject(); From b93807b1a766526a16d8c2c8427e450fdb686a21 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 24 Oct 2018 12:32:38 -0700 Subject: [PATCH 119/131] remove conversion code from equipEntity.js --- .../controllerModules/equipEntity.js | 93 ++----------------- 1 file changed, 9 insertions(+), 84 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index f4d9c731b7..9a78c706a9 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -167,16 +167,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; -var alreadyWarned = {}; -function warnAboutUserData(props) { - if (alreadyWarned[props.id]) { - return; - } - print("Warning -- overriding grab properties with userData for " + props.id + " / " + props.name); - alreadyWarned[props.id] = true; -} - - (function() { var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; @@ -199,80 +189,15 @@ function warnAboutUserData(props) { var UNEQUIP_KEY = "u"; function getWearableData(props) { - if (props.grab.equippable) { - // if equippable is true, we know this was already converted from the old userData style to properties - return { - joints: { - LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], - RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ] - }, - indicatorURL: props.grab.equippableIndicatorURL, - indicatorScale: props.grab.equippableIndicatorScale, - indicatorOffset: props.grab.equippableIndicatorOffset - }; - } else { - // check for old userData equippability. The JSON reader will convert userData to properties - // in EntityTree.cpp, but this won't catch things created from scripts or some items in - // the market. Eventually we'll remove this section. - try { - if (!props.userDataParsed) { - props.userDataParsed = JSON.parse(props.userData); - } - var userDataParsed = props.userDataParsed; - - // userData: { wearable: { joints: { LeftHand: {...}, RightHand: {...} } } } - if (userDataParsed.wearable && userDataParsed.wearable.joints) { - warnAboutUserData(props); - userDataParsed.wearable.indicatorURL = ""; - userDataParsed.wearable.indicatorScale = { x: 1, y: 1, z: 1 }; - userDataParsed.wearable.indicatorOffset = { x: 0, y: 0, z: 0 }; - return userDataParsed.wearable; - } - - // userData: { equipHotspots: { joints: { LeftHand: {...}, RightHand: {...} } } } - // https://highfidelity.atlassian.net/wiki/spaces/HOME/pages/51085337/Authoring+Equippable+Entities - if (userDataParsed.equipHotspots && - userDataParsed.equipHotspots.length > 0 && - userDataParsed.equipHotspots[0].joints) { - warnAboutUserData(props); - var hotSpot = userDataParsed.equipHotspots[0]; - - var indicatorScale = { x: hotSpot.radius, y: hotSpot.radius, z: hotSpot.radius }; - if (hotSpot.modelURL && hotSpot.modelURL !== "") { - indicatorScale = hotSpot.modelScale; - } - - return { - joints: hotSpot.joints, - indicatorURL: hotSpot.modelURL, - indicatorScale: indicatorScale, - indicatorOffset: hotSpot.position, - }; - } - - // userData:{grabbableKey:{spatialKey:{leftRelativePosition:{...},rightRelativePosition:{...}}}} - if (userDataParsed.grabbableKey && - userDataParsed.grabbableKey.spatialKey) { - warnAboutUserData(props); - var joints = {}; - joints.LeftHand = [ { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 } ]; - joints.RightHand = [ { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 } ]; - if (userDataParsed.grabbableKey.spatialKey.leftRelativePosition) { - joints.LeftHand = [userDataParsed.grabbableKey.spatialKey.leftRelativePosition, - userDataParsed.grabbableKey.spatialKey.relativeRotation]; - } - if (userDataParsed.grabbableKey.spatialKey.rightRelativePosition) { - joints.RightHand = [userDataParsed.grabbableKey.spatialKey.rightRelativePosition, - userDataParsed.grabbableKey.spatialKey.relativeRotation]; - } - return { joints: joints }; - } - - } catch (err) { - // don't spam logs - } - return null; - } + return { + joints: { + LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], + RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ] + }, + indicatorURL: props.grab.equippableIndicatorURL, + indicatorScale: props.grab.equippableIndicatorScale, + indicatorOffset: props.grab.equippableIndicatorOffset + }; } function getAttachPointSettings() { From b8f1c966852d5dfaf3e6ca1083aabcafcf768e96 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 25 Oct 2018 07:46:46 -0700 Subject: [PATCH 120/131] rats --- .../controllerModules/equipEntity.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 9a78c706a9..2f343ff84b 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -189,15 +189,19 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var UNEQUIP_KEY = "u"; function getWearableData(props) { - return { - joints: { - LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], - RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ] - }, - indicatorURL: props.grab.equippableIndicatorURL, - indicatorScale: props.grab.equippableIndicatorScale, - indicatorOffset: props.grab.equippableIndicatorOffset - }; + if (props.grab.equippable) { + return { + joints: { + LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], + RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ] + }, + indicatorURL: props.grab.equippableIndicatorURL, + indicatorScale: props.grab.equippableIndicatorScale, + indicatorOffset: props.grab.equippableIndicatorOffset + }; + } else { + return null + } } function getAttachPointSettings() { From eb347c55963dbf23b6f17cf2586b02f8b60da97e Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 25 Oct 2018 11:30:18 -0700 Subject: [PATCH 121/131] remove particle explorer --- .../particle_explorer/hifi-entity-ui.js | 709 ------------------ .../particle_explorer/particle-style.css | 140 ---- .../particle_explorer/particleExplorer.html | 43 -- .../particle_explorer/particleExplorer.js | 485 ------------ .../particle_explorer/particleExplorerTool.js | 144 ---- .../particle_explorer/underscore-min.js | 6 - 6 files changed, 1527 deletions(-) delete mode 100644 scripts/system/particle_explorer/hifi-entity-ui.js delete mode 100644 scripts/system/particle_explorer/particle-style.css delete mode 100644 scripts/system/particle_explorer/particleExplorer.html delete mode 100644 scripts/system/particle_explorer/particleExplorer.js delete mode 100644 scripts/system/particle_explorer/particleExplorerTool.js delete mode 100644 scripts/system/particle_explorer/underscore-min.js diff --git a/scripts/system/particle_explorer/hifi-entity-ui.js b/scripts/system/particle_explorer/hifi-entity-ui.js deleted file mode 100644 index 62a0aadc86..0000000000 --- a/scripts/system/particle_explorer/hifi-entity-ui.js +++ /dev/null @@ -1,709 +0,0 @@ -/* global window, document, print, alert, console,setTimeout, clearTimeout, _ $ */ -/* eslint no-console: 0 */ - -/** -UI Builder V1.0 - -Created by Matti 'Menithal' Lahtinen -24/5/2017 -Copyright 2017 High Fidelity, Inc. - -This can eventually be expanded to all of Edit, for now, starting -with Particles Only. - -This is created for the sole purpose of streamliming the bridge, and to simplify -the logic between an inputfield in WebView and Entities in High Fidelity. - -We also do not need anything as heavy as jquery or any other platform, -as we are mostly only building for QT (while, all the other JS frameworks usually do alot of polyfilling) - -Available Types: - - JSONInputField - Accepts JSON input, once one presses Save, it will be propegated. - Button- A Button that listens for a custom event as defined by callback - Boolean - Creates a checkbox that the user can either check or uncheck - SliderFloat - Creates a slider (with input) that has Float values from min to max. - Default is min 0, max 1 - SliderInteger - Creates a slider (with input) that has a Integer value from min to max. - Default is min 1, max 10000 - SliderRadian - Creates a slider (with input) that has Float values in degrees, - that are converted to radians. default is min 0, max Math.PI. - Texture - Creates a Image with an url input field that points to texture. - If image cannot form, show "cannot find image" - VecQuaternion - Creates a 3D Vector field that converts to quaternions. - Checkbox exists to show quaternions instead. - Color - Create field color button, that when pressed, opens the color picker. - Vector - Create a 3D Vector field that has one to one correspondence. - -The script will use this structure to build a UI that is connected The -id fields within High Fidelity - -This should make editing, and everything related much more simpler to maintain, -and If there is any changes to either the Entities or properties of - -**/ - -var RADIANS_PER_DEGREE = Math.PI / 180; -var DEBOUNCE_TIMEOUT = 125; - -var roundFloat = function (input, round) { - round = round ? round : 1000; - var sanitizedInput; - if (typeof input === "string") { - sanitizedInput = parseFloat(input); - } else { - sanitizedInput = input; - } - return Math.round(sanitizedInput * round) / round; -}; - -function HifiEntityUI(parent) { - this.parent = parent; - - var self = this; - this.sendPackage = {}; - this.settingsUpdateLock = false; - this.webBridgeSync = function(id, val) { - if (!this.settingsUpdateLock) { - this.sendPackage[id] = val; - this.webBridgeSyncDebounce(); - } - }; - this.webBridgeSyncDebounce = _.debounce(function () { - if (self.EventBridge) { - self.submitChanges(self.sendPackage); - self.sendPackage = {}; - } - }, DEBOUNCE_TIMEOUT); -} - -HifiEntityUI.prototype = { - setOnSelect: function (callback) { - this.onSelect = callback; - }, - submitChanges: function (structure) { - var message = { - messageType: "settings_update", - updatedSettings: structure - }; - this.EventBridge.emitWebEvent(JSON.stringify(message)); - }, - setUI: function (structure) { - this.structure = structure; - }, - disableFields: function () { - var fields = document.getElementsByTagName("input"); - for (var i = 0; i < fields.length; i++) { - if (fields[i].getAttribute("type") !== "button") { - fields[i].value = ""; - } - - fields[i].setAttribute("disabled", true); - } - var textures = document.getElementsByTagName("img"); - for (i = 0; i < textures.length; i++) { - textures[i].src = ""; - } - - textures = document.getElementsByClassName("with-texture"); - for (i = 0; i < textures.length; i++) { - textures[i].classList.remove("with-textures"); - textures[i].classList.add("no-texture"); - } - - var textareas = document.getElementsByTagName("textarea"); - for (var x = 0; x < textareas.length; x++) { - textareas[x].remove(); - } - }, - getSettings: function () { - var self = this; - var json = {}; - var keys = Object.keys(self.builtRows); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var el = self.builtRows[key]; - if (el.className.indexOf("checkbox") !== -1) { - json[key] = document.getElementById(key) - .checked ? true : false; - } else if (el.className.indexOf("vector-section") !== -1) { - var vector = {}; - if (el.className.indexOf("rgb") !== -1) { - var red = document.getElementById(key + "-red"); - var blue = document.getElementById(key + "-blue"); - var green = document.getElementById(key + "-green"); - vector.red = red.value; - vector.blue = blue.value; - vector.green = green.value; - } else if (el.className.indexOf("pyr") !== -1) { - var p = document.getElementById(key + "-Pitch"); - var y = document.getElementById(key + "-Yaw"); - var r = document.getElementById(key + "-Roll"); - vector.x = p.value; - vector.y = y.value; - vector.z = r.value; - } else { - var x = document.getElementById(key + "-x"); - var ey = document.getElementById(key + "-y"); - var z = document.getElementById(key + "-z"); - vector.x = x.value; - vector.y = ey.value; - vector.z = z.value; - } - json[key] = vector; - } else if (el.className.indexOf("radian") !== -1) { - json[key] = document.getElementById(key).value * RADIANS_PER_DEGREE; - } else if (el.className.length > 0) { - json[key] = document.getElementById(key).value; - } - } - - - return json; - }, - fillFields: function (currentProperties) { - var self = this; - var fields = document.getElementsByTagName("input"); - - if (!currentProperties.locked) { - for (var i = 0; i < fields.length; i++) { - fields[i].removeAttribute("disabled"); - if (fields[i].hasAttribute("data-max")) { - // Reset Max to original max - fields[i].setAttribute("max", fields[i].getAttribute("data-max")); - } - } - } - - if (self.onSelect) { - self.onSelect(); - } - var keys = Object.keys(currentProperties); - - - for (var e in keys) { - if (keys.hasOwnProperty(e)) { - var value = keys[e]; - - var property = currentProperties[value]; - var field = self.builtRows[value]; - if (field) { - var el = document.getElementById(value); - - if (field.className.indexOf("radian") !== -1) { - el.value = property / RADIANS_PER_DEGREE; - el.onchange({ - target: el - }); - } else if (field.className.indexOf("range") !== -1 || field.className.indexOf("texture") !== -1) { - el.value = property; - el.onchange({ - target: el - }); - } else if (field.className.indexOf("checkbox") !== -1) { - if (property) { - el.setAttribute("checked", property); - } else { - el.removeAttribute("checked"); - } - } else if (field.className.indexOf("vector-section") !== -1) { - if (field.className.indexOf("rgb") !== -1) { - var red = document.getElementById(value + "-red"); - var blue = document.getElementById(value + "-blue"); - var green = document.getElementById(value + "-green"); - red.value = parseInt(property.red); - blue.value = parseInt(property.blue); - green.value = parseInt(property.green); - - red.oninput({ - target: red - }); - } else if (field.className.indexOf("xyz") !== -1) { - var x = document.getElementById(value + "-x"); - var y = document.getElementById(value + "-y"); - var z = document.getElementById(value + "-z"); - - x.value = roundFloat(property.x, 100); - y.value = roundFloat(property.y, 100); - z.value = roundFloat(property.z, 100); - } else if (field.className.indexOf("pyr") !== -1) { - var pitch = document.getElementById(value + "-Pitch"); - var yaw = document.getElementById(value + "-Yaw"); - var roll = document.getElementById(value + "-Roll"); - - pitch.value = roundFloat(property.x, 100); - yaw.value = roundFloat(property.y, 100); - roll.value = roundFloat(property.z, 100); - - } - } - } - } - } - }, - connect: function (EventBridge) { - this.EventBridge = EventBridge; - - var self = this; - - EventBridge.emitWebEvent(JSON.stringify({ - messageType: 'page_loaded' - })); - - EventBridge.scriptEventReceived.connect(function (data) { - data = JSON.parse(data); - - if (data.messageType === 'particle_settings') { - self.settingsUpdateLock = true; - self.fillFields(data.currentProperties); - self.settingsUpdateLock = false; - // Do expected property match with structure; - } else if (data.messageType === 'particle_close') { - self.disableFields(); - } - }); - }, - build: function () { - var self = this; - var sections = Object.keys(this.structure); - this.builtRows = {}; - sections.forEach(function (section, index) { - var properties = self.structure[section]; - self.addSection(self.parent, section, properties, index); - }); - }, - addSection: function (parent, section, properties, index) { - var self = this; - - var sectionDivHeader = document.createElement("fieldset"); - var title = document.createElement("legend"); - var dropDown = document.createElement("span"); - - dropDown.className = "arrow"; - sectionDivHeader.className = "major"; - title.className = "section-header"; - title.id = section + "-section"; - title.innerHTML = section; - title.appendChild(dropDown); - sectionDivHeader.appendChild(title); - - var collapsed = index !== 0; - - dropDown.innerHTML = collapsed ? "L" : "M"; - sectionDivHeader.setAttribute("collapsed", collapsed); - parent.appendChild(sectionDivHeader); - - var sectionDivBody = document.createElement("div"); - sectionDivBody.className = "property-group"; - - var animationWrapper = document.createElement("div"); - animationWrapper.className = "section-wrap"; - - for (var property in properties) { - if (properties.hasOwnProperty(property)) { - var builtRow = self.addElement(animationWrapper, properties[property]); - var id = properties[property].id; - if (id) { - self.builtRows[id] = builtRow; - } - } - } - sectionDivBody.appendChild(animationWrapper); - sectionDivHeader.appendChild(sectionDivBody); - _.defer(function () { - var height = (animationWrapper.clientHeight) + "px"; - if (collapsed) { - sectionDivBody.classList.remove("visible"); - sectionDivBody.style.maxHeight = "0px"; - } else { - sectionDivBody.classList.add("visible"); - sectionDivBody.style.maxHeight = height; - } - - title.onclick = function () { - collapsed = !collapsed; - if (collapsed) { - sectionDivBody.classList.remove("visible"); - sectionDivBody.style.maxHeight = "0px"; - } else { - sectionDivBody.classList.add("visible"); - sectionDivBody.style.maxHeight = (animationWrapper.clientHeight) + "px"; - } - // sectionDivBody.style.display = collapsed ? "none": "block"; - dropDown.innerHTML = collapsed ? "L" : "M"; - title.setAttribute("collapsed", collapsed); - }; - }); - }, - addLabel: function (parent, group) { - var label = document.createElement("label"); - label.innerHTML = group.name; - parent.appendChild(label); - if (group.unit) { - var span = document.createElement("span"); - span.innerHTML = group.unit; - span.className = "unit"; - label.appendChild(span); - } - return label; - }, - addVector: function (parent, group, labels, domArray) { - var self = this; - var inputs = labels ? labels : ["x", "y", "z"]; - domArray = domArray ? domArray : []; - parent.id = group.id; - for (var index in inputs) { - var element = document.createElement("input"); - - element.setAttribute("type", "number"); - element.className = inputs[index]; - element.id = group.id + "-" + inputs[index]; - - if (group.defaultRange) { - if (group.defaultRange.min) { - element.setAttribute("min", group.defaultRange.min); - } - if (group.defaultRange.max) { - element.setAttribute("max", group.defaultRange.max); - } - if (group.defaultRange.step) { - element.setAttribute("step", group.defaultRange.step); - } - } - if (group.oninput) { - element.oninput = group.oninput; - } else { - element.oninput = function (event) { - self.webBridgeSync(group.id, { - x: domArray[0].value, - y: domArray[1].value, - z: domArray[2].value - }); - }; - } - element.onchange = element.oninput; - domArray.push(element); - } - - this.addLabel(parent, group); - var className = ""; - for (var i = 0; i < inputs.length; i++) { - className += inputs[i].charAt(0) - .toLowerCase(); - } - parent.className += " property vector-section " + className; - - // Add Tuple and the rest - var tupleContainer = document.createElement("div"); - tupleContainer.className = "tuple"; - for (var domIndex in domArray) { - var container = domArray[domIndex]; - var div = document.createElement("div"); - var label = document.createElement("label"); - label.innerHTML = inputs[domIndex] + ":"; - label.setAttribute("for", container.id); - div.appendChild(container); - div.appendChild(label); - tupleContainer.appendChild(div); - } - parent.appendChild(tupleContainer); - }, - addVectorQuaternion: function (parent, group) { - this.addVector(parent, group, ["Pitch", "Yaw", "Roll"]); - }, - addColorPicker: function (parent, group) { - var self = this; - var $colPickContainer = $('
', { - id: group.id, - class: "color-picker" - }); - var updateColors = function (red, green, blue) { - $colPickContainer.css('background-color', "rgb(" + - red + "," + - green + "," + - blue + ")"); - }; - - var inputs = ["red", "green", "blue"]; - var domArray = []; - group.oninput = function (event) { - $colPickContainer.colpickSetColor( - { - r: domArray[0].value, - g: domArray[1].value, - b: domArray[2].value - }, - true); - }; - group.defaultRange = { - min: 0, - max: 255, - step: 1 - }; - - parent.appendChild($colPickContainer[0]); - self.addVector(parent, group, inputs, domArray); - - updateColors(domArray[0].value, domArray[1].value, domArray[2].value); - - // Could probably write a custom one for this to completely write out jquery, - // but for now, using the same as earlier. - - /* Color Picker Logic Here */ - - - $colPickContainer.colpick({ - colorScheme: (group.layoutColorScheme === undefined ? 'dark' : group.layoutColorScheme), - layout: (group.layoutType === undefined ? 'hex' : group.layoutType), - submit: (group.useSubmitButton === undefined ? true : group.useSubmitButton), - color: { - r: domArray[0].value, - g: domArray[1].value, - b: domArray[2].value - }, - onChange: function (hsb, hex, rgb, el) { - updateColors(rgb.r, rgb.g, rgb.b); - - domArray[0].value = rgb.r; - domArray[1].value = rgb.g; - domArray[2].value = rgb.b; - self.webBridgeSync(group.id, { - red: rgb.r, - green: rgb.g, - blue: rgb.b - }); - }, - onSubmit: function (hsb, hex, rgb, el) { - $(el) - .css('background-color', '#' + hex); - $(el) - .colpickHide(); - domArray[0].value = rgb.r; - domArray[1].value = rgb.g; - domArray[2].value = rgb.b; - self.webBridgeSync(group.id, { - red: rgb.r, - green: rgb.g, - blue: rgb.b - }); - } - }); - }, - addTextureField: function (parent, group) { - var self = this; - this.addLabel(parent, group); - parent.className += " property texture"; - var textureImage = document.createElement("div"); - var textureUrl = document.createElement("input"); - textureUrl.setAttribute("type", "text"); - textureUrl.id = group.id; - textureImage.className = "texture-image no-texture"; - var image = document.createElement("img"); - var imageLoad = _.debounce(function (url) { - if (url.slice(0, 5).toLowerCase() === "atp:/") { - image.src = ""; - image.style.display = "none"; - textureImage.classList.remove("with-texture"); - textureImage.classList.remove("no-texture"); - textureImage.classList.add("no-preview"); - } else if (url.length > 0) { - textureImage.classList.remove("no-texture"); - textureImage.classList.remove("no-preview"); - textureImage.classList.add("with-texture"); - image.src = url; - image.style.display = "block"; - } else { - image.src = ""; - image.style.display = "none"; - textureImage.classList.remove("with-texture"); - textureImage.classList.remove("no-preview"); - textureImage.classList.add("no-texture"); - } - }, DEBOUNCE_TIMEOUT * 2); - - textureUrl.oninput = function (event) { - // Add throttle - var url = event.target.value; - imageLoad(url); - self.webBridgeSync(group.id, url); - }; - textureUrl.onchange = textureUrl.oninput; - textureImage.appendChild(image); - parent.appendChild(textureImage); - parent.appendChild(textureUrl); - }, - addSlider: function (parent, group) { - var self = this; - this.addLabel(parent, group); - parent.className += " property range"; - var container = document.createElement("div"); - container.className = "slider-wrapper"; - var slider = document.createElement("input"); - slider.setAttribute("type", "range"); - - var inputField = document.createElement("input"); - inputField.setAttribute("type", "number"); - - container.appendChild(slider); - container.appendChild(inputField); - parent.appendChild(container); - - if (group.type === "SliderInteger") { - inputField.setAttribute("min", group.min !== undefined ? group.min : 0); - inputField.setAttribute("step", 1); - - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 10000); - slider.setAttribute("data-max", group.max !== undefined ? group.max : 10000); - slider.setAttribute("step", 1); - - inputField.oninput = function (event) { - // TODO: Remove this functionality? Alan finds it confusing - if (parseInt(event.target.value) > parseInt(slider.getAttribute("max")) && group.max !== 1) { - slider.setAttribute("max", event.target.value); - } - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value); - }; - inputField.onchange = inputField.oninput; - slider.oninput = function (event) { - inputField.value = event.target.value; - self.webBridgeSync(group.id, inputField.value); - }; - - inputField.id = group.id; - } else if (group.type === "SliderRadian") { - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 180); - slider.setAttribute("step", 1); - parent.className += " radian"; - inputField.setAttribute("min", (group.min !== undefined ? group.min : 0)); - inputField.setAttribute("max", (group.max !== undefined ? group.max : 180)); - - inputField.oninput = function (event) { - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE); - }; - inputField.onchange = inputField.oninput; - - inputField.id = group.id; - slider.oninput = function (event) { - if (event.target.value > 0) { - inputField.value = Math.floor(event.target.value); - } else { - inputField.value = Math.ceil(event.target.value); - } - self.webBridgeSync(group.id, inputField.value * RADIANS_PER_DEGREE); - }; - var degrees = document.createElement("label"); - degrees.innerHTML = "°"; - degrees.style.fontSize = "1.4rem"; - degrees.style.display = "inline"; - degrees.style.verticalAlign = "top"; - degrees.style.paddingLeft = "0.4rem"; - container.appendChild(degrees); - - } else { - // Must then be Float - inputField.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("step", 0.01); - - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 1); - slider.setAttribute("data-max", group.max !== undefined ? group.max : 1); - slider.setAttribute("step", 0.01); - - inputField.oninput = function (event) { - // TODO: Remove this functionality? Alan finds it confusing - if (parseFloat(event.target.value) > parseFloat(slider.getAttribute("max")) && group.max !== 1) { - slider.setAttribute("max", event.target.value); - } - - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value); - // bind web sock update here. - }; - inputField.onchange = inputField.oninput; - slider.oninput = function (event) { - inputField.value = event.target.value; - self.webBridgeSync(group.id, inputField.value); - }; - - inputField.id = group.id; - } - - // UpdateBinding - }, - addCheckBox: function (parent, group) { - var checkBox = document.createElement("input"); - checkBox.setAttribute("type", "checkbox"); - var self = this; - checkBox.onchange = function (event) { - self.webBridgeSync(group.id, event.target.checked); - }; - checkBox.id = group.id; - parent.appendChild(checkBox); - var label = this.addLabel(parent, group); - label.setAttribute("for", checkBox.id); - parent.className += " property checkbox"; - }, - addElement: function (parent, group) { - var self = this; - var property = document.createElement("div"); - property.id = group.id; - - var row = document.createElement("div"); - switch (group.type) { - case "Button": - var button = document.createElement("input"); - button.setAttribute("type", "button"); - button.id = group.id; - if (group.disabled) { - button.disabled = group.disabled; - } - button.className = group.class; - button.value = group.name; - - button.onclick = group.callback; - parent.appendChild(button); - break; - case "Row": - var hr = document.createElement("hr"); - hr.className = "splitter"; - if (group.id) { - hr.id = group.id; - } - parent.appendChild(hr); - break; - case "Boolean": - self.addCheckBox(row, group); - parent.appendChild(row); - break; - case "SliderFloat": - case "SliderInteger": - case "SliderRadian": - self.addSlider(row, group); - parent.appendChild(row); - break; - case "Texture": - self.addTextureField(row, group); - parent.appendChild(row); - break; - case "Color": - self.addColorPicker(row, group); - parent.appendChild(row); - break; - case "Vector": - self.addVector(row, group); - parent.appendChild(row); - break; - case "VectorQuaternion": - self.addVectorQuaternion(row, group); - parent.appendChild(row); - break; - default: - console.log("not defined"); - } - return row; - } -}; \ No newline at end of file diff --git a/scripts/system/particle_explorer/particle-style.css b/scripts/system/particle_explorer/particle-style.css deleted file mode 100644 index cde325f6c6..0000000000 --- a/scripts/system/particle_explorer/particle-style.css +++ /dev/null @@ -1,140 +0,0 @@ -/* -// particle-style.css -// -// Created by Matti 'Menithal' Lahtinen on 21 May 2017 -// Copyright 2017 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 -*/ - - -.property-group { - max-height: 0; - -webkit-transition: max-height 0.15s ease-out; - transition: max-height 0.15s ease-out; - overflow: hidden; -} -.property-group.visible { - transition: max-height 0.25s ease-in; -} -.section-wrap { - width: 100%; -} -.property { - padding: 0.4rem 0; - margin: 0; -} -.property.checkbox { - margin: 0; -} -.property.range label{ - display: block; -} - -input[type="button"] { - margin: 0.4rem; - min-width: 6rem; -} -input[type="text"] { - margin: 0; -} -.property.range input[type=number]{ - margin-left: 0.8rem; - width: 5.4rem; - height: 1.8rem; -} -input[type=range] { - -webkit-appearance: none; - background: #2e2e2e; - height: 1.8rem; - border-radius: 1rem; -} -input[type=range]::-webkit-slider-thumb { - -webkit-appearance:none; - width: 0.6rem; - height: 1.8rem; - padding:0; - margin: 0; - background-color: #696969; - border-radius: 1rem; -} -input[type=range]::-webkit-slider-thumb:hover { - background-color: white; -} -input[type=range]:focus { /*#252525*/ - outline: none; -} -.tuple label { - text-transform: capitalize; -} -.slider-wrapper { - display: table; - padding: 0.4rem 0; -} -hr.splitter{ - width: 100%; - padding: 0.2rem 0 0 0; - margin: 0; - position: relative; - clear: both; -} -hr.splitter:last-of-type{ - padding:0; -} -#rem { - height: 1rem; - width: 1rem; -} -.property { - min-height: 2rem; -} -.property.vector-section{ - - width: 24rem; -} - -.property.texture { - display: block; -} -.property.texture input{ - margin: 0.4rem 0; -} -.texture-image img{ - padding: 0; - margin: 0; - width: 100%; - height: 100%; - display: none; -} -.texture-image { - display: block; - position: relative; - background-repeat: no-repeat; - background-position: center; - background-size: 100% 100%; - margin-top: 0.4rem; - height:128px; - width: 128px; - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII='); -} - -.texture-image.no-texture { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); -} - -.texture-image.no-preview { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); -} - -#properties-list > fieldset { - margin-top: 0px; -} - -#main-header { - margin-bottom: 21px; -} - -.section-wrap { - padding: 21px 0px; -} \ No newline at end of file diff --git a/scripts/system/particle_explorer/particleExplorer.html b/scripts/system/particle_explorer/particleExplorer.html deleted file mode 100644 index ab4c249cc3..0000000000 --- a/scripts/system/particle_explorer/particleExplorer.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - -
-
- -
- -
-
- - - diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js deleted file mode 100644 index f1b7c8600f..0000000000 --- a/scripts/system/particle_explorer/particleExplorer.js +++ /dev/null @@ -1,485 +0,0 @@ -// -// particleExplorer.js -// -// Created by James B. Pollack @imgntn on 9/26/2015 -// Copyright 2017 High Fidelity, Inc. -// -// Reworked by Menithal on 20/5/2017 -// Reworked by Daniela Fontes and Artur Gomes (Mimicry) on 12/18/2017 -// -// Web app side of the App - contains GUI. -// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -/* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */ -/* eslint no-console: 0, no-global-assign: 0 */ - -(function () { - - var root = document.getElementById("properties-list"); - - window.onload = function () { - var ui = new HifiEntityUI(root); - var textarea = document.createElement("textarea"); - var properties = ""; - var menuStructure = { - General: [ - { - type: "Row", - id: "export-import-field" - }, - { - id: "show-properties-button", - name: "Show Properties", - type: "Button", - class: "blue", - disabled: true, - callback: function (event) { - var insertZone = document.getElementById("export-import-field"); - var json = ui.getSettings(); - properties = JSON.stringify(json); - textarea.value = properties; - if (!insertZone.contains(textarea)) { - insertZone.appendChild(textarea); - insertZone.parentNode.parentNode.style.maxHeight = - insertZone.parentNode.clientHeight + "px"; - document.getElementById("export-properties-button").removeAttribute("disabled"); - textarea.onchange = function (e) { - if (e.target.value !== properties) { - document.getElementById("import-properties-button").removeAttribute("disabled"); - } - }; - textarea.oninput = textarea.onchange; - document.getElementById("show-properties-button").value = "Hide Properties"; - } else { - textarea.onchange = function () {}; - textarea.oninput = textarea.onchange; - textarea.value = ""; - textarea.remove(); - insertZone.parentNode.parentNode.style.maxHeight = - insertZone.parentNode.clientHeight + "px"; - document.getElementById("export-properties-button").setAttribute("disabled", true); - document.getElementById("import-properties-button").setAttribute("disabled", true); - document.getElementById("show-properties-button").value = "Show Properties"; - } - } - }, - { - id: "import-properties-button", - name: "Import", - type: "Button", - class: "blue", - disabled: true, - callback: function (event) { - ui.fillFields(JSON.parse(textarea.value)); - ui.submitChanges(JSON.parse(textarea.value)); - } - }, - { - id: "export-properties-button", - name: "Export", - type: "Button", - class: "red", - disabled: true, - callback: function (event) { - textarea.select(); - try { - var success = document.execCommand('copy'); - if (!success) { - throw "Not success :("; - } - } catch (e) { - print("couldnt copy field"); - } - } - }, - { - type: "Row" - }, - { - id: "isEmitting", - name: "Is Emitting", - type: "Boolean" - }, - { - type: "Row" - }, - { - id: "lifespan", - name: "Lifespan", - type: "SliderFloat", - min: 0.01, - max: 10 - }, - { - type: "Row" - }, - { - id: "maxParticles", - name: "Max Particles", - type: "SliderInteger", - min: 1, - max: 10000 - }, - { - type: "Row" - }, - { - id: "textures", - name: "Textures", - type: "Texture" - }, - { - type: "Row" - } - ], - Emit: [ - { - id: "emitRate", - name: "Emit Rate", - type: "SliderInteger", - max: 1000, - min: 1 - }, - { - type: "Row" - }, - { - id: "emitSpeed", - name: "Emit Speed", - type: "SliderFloat", - max: 5 - }, - { - id: "speedSpread", - name: "Speed Spread", - type: "SliderFloat", - max: 5 - }, - { - type: "Row" - }, - { - id: "emitDimensions", - name: "Emit Dimension", - type: "Vector", - defaultRange: { - min: 0, - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "emitOrientation", - unit: "deg", - name: "Emit Orientation", - type: "VectorQuaternion", - defaultRange: { - min: 0, - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "emitterShouldTrail", - name: "Emitter Should Trail", - type: "Boolean" - }, - { - type: "Row" - } - ], - Radius: [ - { - id: "particleRadius", - name: "Particle Radius", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusSpread", - name: "Radius Spread", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusStart", - name: "Radius Start", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusFinish", - name: "Radius Finish", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - } - ], - Color: [ - { - id: "color", - name: "Color", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorSpread", - name: "Color Spread", - type: "Color", - defaultColor: { - red: 0, - green: 0, - blue: 0 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorStart", - name: "Color Start", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorFinish", - name: "Color Finish", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - } - ], - Acceleration: [ - { - id: "emitAcceleration", - name: "Emit Acceleration", - type: "Vector", - defaultRange: { - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "accelerationSpread", - name: "Acceleration Spread", - type: "Vector", - defaultRange: { - step: 0.01 - } - }, - { - type: "Row" - } - ], - Alpha: [ - { - id: "alpha", - name: "Alpha", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaSpread", - name: "Alpha Spread", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaStart", - name: "Alpha Start", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaFinish", - name: "Alpha Finish", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - } - ], - Spin: [ - { - id: "particleSpin", - name: "Particle Spin", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinSpread", - name: "Spin Spread", - type: "SliderRadian", - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinStart", - name: "Spin Start", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinFinish", - name: "Spin Finish", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "rotateWithEntity", - name: "Rotate with Entity", - type: "Boolean" - }, - { - type: "Row" - } - ], - Polar: [ - { - id: "polarStart", - name: "Polar Start", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - }, - { - id: "polarFinish", - name: "Polar Finish", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - } - ], - Azimuth: [ - { - id: "azimuthStart", - name: "Azimuth Start", - unit: "deg", - type: "SliderRadian", - min: -180, - max: 0 - }, - { - type: "Row" - }, - { - id: "azimuthFinish", - name: "Azimuth Finish", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - } - ] - }; - ui.setUI(menuStructure); - ui.setOnSelect(function () { - document.getElementById("show-properties-button").removeAttribute("disabled"); - document.getElementById("export-properties-button").setAttribute("disabled", true); - document.getElementById("import-properties-button").setAttribute("disabled", true); - }); - ui.build(); - var overrideLoad = false; - if (openEventBridge === undefined) { - overrideLoad = true, - openEventBridge = function (callback) { - callback({ - emitWebEvent: function () {}, - submitChanges: function () {}, - scriptEventReceived: { - connect: function () { - - } - } - }); - }; - } - openEventBridge(function (EventBridge) { - ui.connect(EventBridge); - }); - if (overrideLoad) { - openEventBridge(); - } - }; -})(); diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js deleted file mode 100644 index a3be004329..0000000000 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ /dev/null @@ -1,144 +0,0 @@ -// -// particleExplorerTool.js -// -// Created by Eric Levin on 2/15/16 -// Copyright 2016 High Fidelity, Inc. -// Adds particleExplorer tool to the edit panel when a user selects a particle entity from the edit tool window -// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -/* global ParticleExplorerTool */ - - -var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); - -ParticleExplorerTool = function(createToolsWindow) { - var that = {}; - that.activeParticleEntity = 0; - that.updatedActiveParticleProperties = {}; - - that.createWebView = function() { - that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - that.webView.setVisible = function(value) {}; - that.webView.webEventReceived.connect(that.webEventReceived); - createToolsWindow.webEventReceived.addListener(this, that.webEventReceived); - }; - - function emitScriptEvent(data) { - var messageData = JSON.stringify(data); - that.webView.emitScriptEvent(messageData); - createToolsWindow.emitScriptEvent(messageData); - } - - that.destroyWebView = function() { - if (!that.webView) { - return; - } - that.activeParticleEntity = 0; - that.updatedActiveParticleProperties = {}; - - emitScriptEvent({ - messageType: "particle_close" - }); - }; - - function sendParticleProperties(properties) { - emitScriptEvent({ - messageType: "particle_settings", - currentProperties: properties - }); - } - - function sendActiveParticleProperties() { - var properties = Entities.getEntityProperties(that.activeParticleEntity); - if (properties.emitOrientation) { - properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); - } - // Update uninitialized variables - if (isNaN(properties.alphaStart)) { - properties.alphaStart = properties.alpha; - } - if (isNaN(properties.alphaFinish)) { - properties.alphaFinish = properties.alpha; - } - if (isNaN(properties.radiusStart)) { - properties.radiusStart = properties.particleRadius; - } - if (isNaN(properties.radiusFinish)) { - properties.radiusFinish = properties.particleRadius; - } - if (isNaN(properties.colorStart.red)) { - properties.colorStart = properties.color; - } - if (isNaN(properties.colorFinish.red)) { - properties.colorFinish = properties.color; - } - if (isNaN(properties.spinStart)) { - properties.spinStart = properties.particleSpin; - } - if (isNaN(properties.spinFinish)) { - properties.spinFinish = properties.particleSpin; - } - sendParticleProperties(properties); - } - - function sendUpdatedActiveParticleProperties() { - sendParticleProperties(that.updatedActiveParticleProperties); - that.updatedActiveParticleProperties = {}; - } - - that.webEventReceived = function(message) { - var data = JSON.parse(message); - if (data.messageType === "settings_update") { - var updatedSettings = data.updatedSettings; - - var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish", "spinStart", "spinFinish"]; - var fallbackProps = ["alpha", "particleRadius", "color", "particleSpin"]; - for (var i = 0; i < optionalProps.length; i++) { - var fallbackProp = fallbackProps[Math.floor(i / 2)]; - var optionalValue = updatedSettings[optionalProps[i]]; - var fallbackValue = updatedSettings[fallbackProp]; - if (optionalValue && fallbackValue) { - delete updatedSettings[optionalProps[i]]; - } - } - - if (updatedSettings.emitOrientation) { - updatedSettings.emitOrientation = Quat.fromVec3Degrees(updatedSettings.emitOrientation); - } - - Entities.editEntity(that.activeParticleEntity, updatedSettings); - - var entityProps = Entities.getEntityProperties(that.activeParticleEntity, optionalProps); - - var needsUpdate = false; - for (var i = 0; i < optionalProps.length; i++) { - var fallbackProp = fallbackProps[Math.floor(i / 2)]; - var fallbackValue = updatedSettings[fallbackProp]; - if (fallbackValue) { - var optionalProp = optionalProps[i]; - if ((fallbackProp !== "color" && isNaN(entityProps[optionalProp])) || (fallbackProp === "color" && isNaN(entityProps[optionalProp].red))) { - that.updatedActiveParticleProperties[optionalProp] = fallbackValue; - needsUpdate = true; - } - } - } - - if (needsUpdate) { - sendUpdatedActiveParticleProperties(); - } - - } else if (data.messageType === "page_loaded") { - sendActiveParticleProperties(); - } - }; - - that.setActiveParticleEntity = function(id) { - that.activeParticleEntity = id; - sendActiveParticleProperties(); - }; - - return that; -}; diff --git a/scripts/system/particle_explorer/underscore-min.js b/scripts/system/particle_explorer/underscore-min.js deleted file mode 100644 index f01025b7bc..0000000000 --- a/scripts/system/particle_explorer/underscore-min.js +++ /dev/null @@ -1,6 +0,0 @@ -// Underscore.js 1.8.3 -// http://underscorejs.org -// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Underscore may be freely distributed under the MIT license. -(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); -//# sourceMappingURL=underscore-min.map \ No newline at end of file From 7dff0155851bca5e23202a5c8c9704e73f54b6db Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 25 Oct 2018 10:09:38 -0700 Subject: [PATCH 122/131] maybe? --- libraries/render-utils/src/MeshPartPayload.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 2fe0368db2..4ebd92bb05 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -242,9 +242,13 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in #ifdef Q_OS_MAC // On mac AMD, we specifically need to have a _meshBlendshapeBuffer bound when using a deformed mesh pipeline // it cannot be null otherwise we crash in the drawcall using a deformed pipeline with a skinned only (not blendshaped) mesh - if ((_isBlendShaped || _isSkinned)) { - glm::vec4 data; - _meshBlendshapeBuffer = std::make_shared(sizeof(glm::vec4), reinterpret_cast(&data)); + if (_isBlendShaped) { + std::vector data(_meshNumVertices); + const auto blendShapeBufferSize = _meshNumVertices * sizeof(BlendshapeOffset); + _meshBlendshapeBuffer = std::make_shared(blendShapeBufferSize, reinterpret_cast(data.data()), blendShapeBufferSize); + } else if (_isSkinned) { + BlendshapeOffset data; + _meshBlendshapeBuffer = std::make_shared(sizeof(BlendshapeOffset), reinterpret_cast(&data), sizeof(BlendshapeOffset)); } #endif From 13acfb325ae55db7ffde438c5e344a6658431f60 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 25 Oct 2018 15:06:15 -0700 Subject: [PATCH 123/131] fix collides with properties, fix ordering for start/finish/spread properties --- scripts/system/html/js/entityProperties.js | 216 ++++++++++----------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d564a59a43..f73d634af9 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -710,15 +710,6 @@ const GROUPS = [ decimals: 2, propertyID: "particleRadius", }, - { - label: "Size Spread", - type: "slider", - min: 0, - max: 4, - step: 0.01, - decimals: 2, - propertyID: "radiusSpread", - }, { label: "Size Start", type: "slider", @@ -739,6 +730,15 @@ const GROUPS = [ propertyID: "radiusFinish", fallbackProperty: "particleRadius", }, + { + label: "Size Spread", + type: "slider", + min: 0, + max: 4, + step: 0.01, + decimals: 2, + propertyID: "radiusSpread", + }, ] }, { @@ -783,15 +783,6 @@ const GROUPS = [ decimals: 2, propertyID: "alpha", }, - { - label: "Alpha Spread", - type: "slider", - min: 0, - max: 1, - step: 0.01, - decimals: 2, - propertyID: "alphaSpread", - }, { label: "Alpha Start", type: "slider", @@ -812,6 +803,15 @@ const GROUPS = [ propertyID: "alphaFinish", fallbackProperty: "alpha", }, + { + label: "Alpha Spread", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "alphaSpread", + }, ] }, { @@ -853,17 +853,6 @@ const GROUPS = [ unit: "deg", propertyID: "particleSpin", }, - { - label: "Spin Spread", - type: "slider", - min: 0, - max: 360, - step: 1, - decimals: 0, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "spinSpread", - }, { label: "Spin Start", type: "slider", @@ -888,6 +877,17 @@ const GROUPS = [ propertyID: "spinFinish", fallbackProperty: "particleSpin", }, + { + label: "Spin Spread", + type: "slider", + min: 0, + max: 360, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinSpread", + }, { label: "Rotate with Entity", type: "bool", @@ -1355,15 +1355,15 @@ function getPropertyInputElement(propertyID) { } function enableChildren(el, selector) { - var elSelectors = el.querySelectorAll(selector); - for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { elSelectors[selectorIndex].removeAttribute('disabled'); } } function disableChildren(el, selector) { - var elSelectors = el.querySelectorAll(selector); - for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { elSelectors[selectorIndex].setAttribute('disabled', 'disabled'); } } @@ -1371,7 +1371,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = getPropertyInputElement("locked"); + let elLocked = getPropertyInputElement("locked"); if (elLocked.checked === false) { removeStaticUserData(); @@ -1382,10 +1382,10 @@ function enableProperties() { function disableProperties() { disableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); disableChildren(document, ".colpick"); - for (var pickKey in colorPickers) { + for (let pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = getPropertyInputElement("locked"); + let elLocked = getPropertyInputElement("locked"); if (elLocked.checked === true) { if ($('#property-userData-editor').css('display') === "block") { @@ -1526,7 +1526,7 @@ function getPropertyValue(originalPropertyName) { * PROPERTY UPDATE FUNCTIONS */ -function updateProperty(originalPropertyName, propertyValue) { +function updateProperty(originalPropertyName, propertyValue, isParticleProperty) { let propertyUpdate = {}; // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times let splitPropertyName = originalPropertyName.split('.'); @@ -1545,8 +1545,8 @@ function updateProperty(originalPropertyName, propertyValue) { propertyUpdate[originalPropertyName] = propertyValue; } // queue up particle property changes with the debounced sync to avoid - // causing particle emitting to reset each frame when updating values - if (properties[originalPropertyName].isParticleProperty) { + // causing particle emitting to reset excessively with each value change + if (isParticleProperty) { Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) { particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey]; }); @@ -1569,30 +1569,29 @@ function updateProperties(propertiesToUpdate) { })); } - -function createEmitTextPropertyUpdateFunction(propertyName) { +function createEmitTextPropertyUpdateFunction(propertyName, isParticleProperty) { return function() { - updateProperty(propertyName, this.value); + updateProperty(propertyName, this.value, isParticleProperty); }; } -function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { +function createEmitCheckedPropertyUpdateFunction(propertyName, inverse, isParticleProperty) { return function() { - updateProperty(propertyName, inverse ? !this.checked : this.checked); + updateProperty(propertyName, inverse ? !this.checked : this.checked, isParticleProperty); }; } -function createEmitNumberPropertyUpdateFunction(propertyName, multiplier) { +function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, isParticleProperty) { return function() { if (multiplier === undefined) { multiplier = 1; } let value = parseFloat(this.value) * multiplier; - updateProperty(propertyName, value); + updateProperty(propertyName, value, isParticleProperty); }; } -function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier) { +function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier, isParticleProperty) { return function () { if (multiplier === undefined) { multiplier = 1; @@ -1601,11 +1600,11 @@ function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier x: elX.value * multiplier, y: elY.value * multiplier }; - updateProperty(propertyName, newValue); + updateProperty(propertyName, newValue, isParticleProperty); }; } -function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier) { +function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier, isParticleProperty) { return function() { if (multiplier === undefined) { multiplier = 1; @@ -1615,26 +1614,26 @@ function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multi y: elY.value * multiplier, z: elZ.value * multiplier }; - updateProperty(propertyName, newValue); + updateProperty(propertyName, newValue, isParticleProperty); }; } -function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue) { +function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue, isParticleProperty) { return function() { - emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value); + emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value, isParticleProperty); }; } -function emitColorPropertyUpdate(propertyName, red, green, blue) { +function emitColorPropertyUpdate(propertyName, red, green, blue, isParticleProperty) { let newValue = { red: red, green: green, blue: blue }; - updateProperty(propertyName, newValue); + updateProperty(propertyName, newValue, isParticleProperty); } -function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { +function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString, isParticleProperty) { if (subPropertyElement.checked) { if (propertyValue.indexOf(subPropertyString)) { propertyValue += subPropertyString + ','; @@ -1643,13 +1642,13 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen // We've unchecked, so remove propertyValue = propertyValue.replace(subPropertyString + ",", ""); } - updateProperty(propertyName, propertyValue); + updateProperty(propertyName, propertyValue, isParticleProperty); } -function createImageURLUpdateFunction(propertyName) { +function createImageURLUpdateFunction(propertyName, isParticleProperty) { return function () { - var newTextures = JSON.stringify({ "tex.picture": this.value }); - updateProperty(propertyName, newTextures); + let newTextures = JSON.stringify({ "tex.picture": this.value }); + updateProperty(propertyName, newTextures, isParticleProperty); }; } @@ -1672,7 +1671,7 @@ function createStringProperty(property, elProperty, elLabel) { elInput.readOnly = true; } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elProperty.appendChild(elLabel); elProperty.appendChild(elInput); @@ -1708,10 +1707,10 @@ function createBoolProperty(property, elProperty, elLabel) { let subPropertyOf = propertyData.subPropertyOf; if (subPropertyOf !== undefined) { elInput.addEventListener('change', function() { - updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName); + updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName, property.isParticleProperty); }); } else { - elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse)); + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse, property.isParticleProperty)); } return elInput; @@ -1744,7 +1743,7 @@ function createNumberProperty(property, elProperty, elLabel) { elInput.value = defaultValue; } - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals)); + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals, property.isParticleProperty)); elProperty.appendChild(elLabel); elProperty.appendChild(elInput); @@ -1790,7 +1789,7 @@ function createSliderProperty(property, elProperty, elLabel) { if (propertyData.multiplier !== undefined) { inputValue *= propertyData.multiplier; } - updateProperty(property.name, inputValue); + updateProperty(property.name, inputValue, property.isParticleProperty); }; elSlider.oninput = function (event) { let sliderValue = event.target.value; @@ -1806,7 +1805,7 @@ function createSliderProperty(property, elProperty, elLabel) { if (propertyData.multiplier !== undefined) { sliderValue *= propertyData.multiplier; } - updateProperty(property.name, sliderValue); + updateProperty(property.name, sliderValue, property.isParticleProperty); }; elDiv.appendChild(elLabel); @@ -1842,8 +1841,8 @@ function createVec3Property(property, elProperty, elLabel) { let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_INPUT], propertyData.min, propertyData.max, propertyData.step); - let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, - elInputZ, propertyData.multiplier); + let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ, + propertyData.multiplier, property.isParticleProperty); elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); elInputZ.addEventListener('change', inputChangeFunction); @@ -1875,8 +1874,8 @@ function createVec2Property(property, elProperty, elLabel) { let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT], propertyData.min, propertyData.max, propertyData.step); - let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, - elInputY, propertyData.multiplier); + let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY, + propertyData.multiplier, property.isParticleProperty); elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); @@ -1907,7 +1906,8 @@ function createColorProperty(property, elProperty, elLabel) { let elInputG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP); let elInputB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP); - let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB, + property.isParticleProperty); elInputR.addEventListener('change', inputChangeFunction); elInputG.addEventListener('change', inputChangeFunction); elInputB.addEventListener('change', inputChangeFunction); @@ -1963,7 +1963,7 @@ function createDropdownProperty(property, propertyID, elProperty, elLabel) { elInput.add(option); } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elProperty.appendChild(elLabel); elProperty.appendChild(elInput); @@ -1990,7 +1990,7 @@ function createTextareaProperty(property, elProperty, elLabel) { elInput.readOnly = true; } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elProperty.appendChild(elInput); @@ -2056,9 +2056,9 @@ function createTextureProperty(property, elProperty, elLabel) { elInput.imageLoad = imageLoad; elInput.oninput = function (event) { // Add throttle - var url = event.target.value; + let url = event.target.value; imageLoad(url); - updateProperty(property.name, url) + updateProperty(property.name, url, property.isParticleProperty) }; elInput.onchange = elInput.oninput; @@ -2199,7 +2199,7 @@ function reloadServerScripts() { function copySkyboxURLToAmbientURL() { let skyboxURL = getPropertyInputElement("skybox.url").value; getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL; - updateProperty("ambientLight.ambientURL", skyboxURL); + updateProperty("ambientLight.ambientURL", skyboxURL, false); } @@ -2214,13 +2214,13 @@ function clearUserData() { showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); - updateProperty('userData', elUserData.value); + updateProperty('userData', elUserData.value, false); } function newJSONEditor() { deleteJSONEditor(); createJSONEditor(); - var data = {}; + let data = {}; setEditorJSON(data); hideUserDataTextArea(); hideNewJSONEditorButton(); @@ -2232,7 +2232,7 @@ function saveUserData() { } function setUserDataFromEditor(noUpdate) { - var json = null; + let json = null; try { json = editor.get(); } catch (e) { @@ -2241,7 +2241,7 @@ function setUserDataFromEditor(noUpdate) { if (json === null) { return; } else { - var text = editor.getText(); + let text = editor.getText(); if (noUpdate === true) { EventBridge.emitWebEvent( JSON.stringify({ @@ -2254,15 +2254,15 @@ function setUserDataFromEditor(noUpdate) { ); return; } else { - updateProperty('userData', text); + updateProperty('userData', text, false); } } } function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, removeKeys) { - var propertyUpdate = {}; - var parsedData = {}; - var keysToBeRemoved = removeKeys ? removeKeys : []; + let propertyUpdate = {}; + let parsedData = {}; + let keysToBeRemoved = removeKeys ? removeKeys : []; try { if ($('#property-userData-editor').css('height') !== "0px") { // if there is an expanded, we want to use its json. @@ -2277,14 +2277,14 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r if (!(groupName in parsedData)) { parsedData[groupName] = {}; } - var keys = Object.keys(updateKeyPair); + let keys = Object.keys(updateKeyPair); keys.forEach(function (key) { if (updateKeyPair[key] !== null && updateKeyPair[key] !== "null") { if (updateKeyPair[key] instanceof Element) { if (updateKeyPair[key].type === "checkbox") { parsedData[groupName][key] = updateKeyPair[key].checked; } else { - var val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value); + let val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value); parsedData[groupName][key] = val; } } else { @@ -2317,8 +2317,8 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r var editor = null; function createJSONEditor() { - var container = document.getElementById("property-userData-editor"); - var options = { + let container = document.getElementById("property-userData-editor"); + let options = { search: false, mode: 'tree', modes: ['code', 'tree'], @@ -2330,7 +2330,7 @@ function createJSONEditor() { alert('JSON editor:' + e); }, onChange: function() { - var currentJSONString = editor.getText(); + let currentJSONString = editor.getText(); if (currentJSONString === '{"":""}') { return; @@ -2431,13 +2431,13 @@ function clearMaterialData() { showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); - updateProperty('materialData', elMaterialData.value); + updateProperty('materialData', elMaterialData.value, false); } function newJSONMaterialEditor() { deleteJSONMaterialEditor(); createJSONMaterialEditor(); - var data = {}; + let data = {}; setMaterialEditorJSON(data); hideMaterialDataTextArea(); hideNewJSONMaterialEditorButton(); @@ -2449,7 +2449,7 @@ function saveMaterialData() { } function setMaterialDataFromEditor(noUpdate) { - var json = null; + let json = null; try { json = materialEditor.get(); } catch (e) { @@ -2458,7 +2458,7 @@ function setMaterialDataFromEditor(noUpdate) { if (json === null) { return; } else { - var text = materialEditor.getText(); + let text = materialEditor.getText(); if (noUpdate === true) { EventBridge.emitWebEvent( JSON.stringify({ @@ -2471,7 +2471,7 @@ function setMaterialDataFromEditor(noUpdate) { ); return; } else { - updateProperty('materialData', text); + updateProperty('materialData', text, false); } } } @@ -2479,8 +2479,8 @@ function setMaterialDataFromEditor(noUpdate) { var materialEditor = null; function createJSONMaterialEditor() { - var container = document.getElementById("property-materialData-editor"); - var options = { + let container = document.getElementById("property-materialData-editor"); + let options = { search: false, mode: 'tree', modes: ['code', 'tree'], @@ -2492,7 +2492,7 @@ function createJSONMaterialEditor() { alert('JSON editor:' + e); }, onChange: function() { - var currentJSONString = materialEditor.getText(); + let currentJSONString = materialEditor.getText(); if (currentJSONString === '{"":""}') { return; @@ -2582,11 +2582,11 @@ function saveJSONMaterialData(noUpdate) { } function bindAllNonJSONEditorElements() { - var inputs = $('input'); - var i; + let inputs = $('input'); + let i; for (i = 0; i < inputs.length; ++i) { - var input = inputs[i]; - var field = $(input); + let input = inputs[i]; + let field = $(input); // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { @@ -2649,7 +2649,7 @@ function setDropdownValue(event) { */ function setTextareaScrolling(element) { - var isScrolling = element.scrollHeight > element.offsetHeight; + let isScrolling = element.scrollHeight > element.offsetHeight; element.setAttribute("scrolling", isScrolling ? "true" : "false"); } @@ -3229,22 +3229,22 @@ function loaded() { let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); elParentMaterialNameString.addEventListener('change', function () { - updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value, false); }); elParentMaterialNameNumber.addEventListener('change', function () { - updateProperty("parentMaterialName", this.value); + updateProperty("parentMaterialName", this.value, false); }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { - updateProperty("parentMaterialName", elParentMaterialNameNumber.value); + updateProperty("parentMaterialName", elParentMaterialNameNumber.value, false); showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); } else { - updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value); + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value, false); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); } }); - getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures')); + getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false)); // Collapsible sections let elCollapsible = document.getElementsByClassName("section-header"); @@ -3339,12 +3339,12 @@ function loaded() { let propertyID = elDropdown.getAttribute("propertyID"); let property = properties[propertyID]; property.elInput = dt; - dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name)); + dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name, property.isParticleProperty)); } elDropdowns = document.getElementsByTagName("select"); while (elDropdowns.length > 0) { - var el = elDropdowns[0]; + let el = elDropdowns[0]; el.parentNode.removeChild(el); elDropdowns = document.getElementsByTagName("select"); } From 95d160a1701fe1ea3b290f1fe2b090dca0838111 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Fri, 28 Sep 2018 15:59:04 -0700 Subject: [PATCH 124/131] Working on mac GL issues --- CMakeLists.txt | 2 +- interface/src/Application.cpp | 45 ++-- interface/src/main.cpp | 13 ++ interface/src/ui/ApplicationOverlay.cpp | 1 + .../src/RenderableWebEntityItem.cpp | 1 + libraries/gl/src/gl/Context.cpp | 30 +-- libraries/gl/src/gl/Context.h | 11 +- libraries/gl/src/gl/ContextQt.cpp | 52 ++++- libraries/gl/src/gl/GLHelpers.cpp | 7 +- libraries/gl/src/gl/GLWidget.cpp | 4 +- libraries/gl/src/gl/GLWidget.h | 2 +- libraries/gl/src/gl/GLWindow.cpp | 2 +- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 23 -- libraries/gl/src/gl/OffscreenGLCanvas.h | 3 - libraries/gl/src/gl/QOpenGLContextWrapper.cpp | 17 ++ libraries/gl/src/gl/QOpenGLContextWrapper.h | 5 + .../gpu-gl-common/src/gpu/gl/GLBackend.cpp | 199 ++++++++--------- .../gpu-gl-common/src/gpu/gl/GLBackend.h | 36 ++- .../qml/src/qml/impl/RenderEventHandler.cpp | 5 +- libraries/qml/src/qml/impl/TextureCache.cpp | 14 +- tests-manual/qml/qml/MacQml.qml | 77 +++++++ tests-manual/qml/src/MacQml.cpp | 78 +++++++ tests-manual/qml/src/MacQml.h | 16 ++ tests-manual/qml/src/StressWeb.cpp | 131 +++++++++++ tests-manual/qml/src/StressWeb.h | 34 +++ tests-manual/qml/src/TestCase.cpp | 25 +++ tests-manual/qml/src/TestCase.h | 23 ++ tests-manual/qml/src/main.cpp | 205 +++++------------- 28 files changed, 706 insertions(+), 355 deletions(-) create mode 100644 tests-manual/qml/qml/MacQml.qml create mode 100644 tests-manual/qml/src/MacQml.cpp create mode 100644 tests-manual/qml/src/MacQml.h create mode 100644 tests-manual/qml/src/StressWeb.cpp create mode 100644 tests-manual/qml/src/StressWeb.h create mode 100644 tests-manual/qml/src/TestCase.cpp create mode 100644 tests-manual/qml/src/TestCase.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d52a557cb1..6120e27b75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ if (ANDROID) set(GLES_OPTION ON) set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) else () - set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) + set(PLATFORM_QT_COMPONENTS WebEngine) endif () if (USE_GLES AND (NOT ANDROID)) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 58aad654db..700561325f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -52,6 +52,8 @@ #include #include +#include +#include #include #include @@ -971,9 +973,11 @@ OffscreenGLCanvas* _qmlShareContext { nullptr }; // and manually set THAT to be the shared context for the Chromium helper #if !defined(DISABLE_QML) OffscreenGLCanvas* _chromiumShareContext { nullptr }; -Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); #endif +Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); + Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 60.0f; @@ -1370,7 +1374,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _glWidget->setMouseTracking(true); // Make sure the window is set to the correct size by processing the pending events QCoreApplication::processEvents(); - _glWidget->createContext(); // Create the main thread context, the GPU backend initializeGL(); @@ -2727,46 +2730,58 @@ void Application::initializeGL() { _isGLInitialized = true; } + _glWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat()); + + // When loading QtWebEngineWidgets, it creates a global share context on startup. + // We have to account for this possibility by checking here for an existing + // global share context + auto globalShareContext = qt_gl_global_share_context(); + _glWidget->createContext(globalShareContext); + if (!_glWidget->makeCurrent()) { qCWarning(interfaceapp, "Unable to make window context current"); } #if !defined(DISABLE_QML) - // Build a shared canvas / context for the Chromium processes - { - // Disable signed distance field font rendering on ATI/AMD GPUs, due to - // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app - std::string vendor{ (const char*)glGetString(GL_VENDOR) }; - if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); - } + // Disable signed distance field font rendering on ATI/AMD GPUs, due to + // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app + std::string vendor{ (const char*)glGetString(GL_VENDOR) }; + if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); + } + // Build a shared canvas / context for the Chromium processes + if (!globalShareContext) { // Chromium rendering uses some GL functions that prevent nSight from capturing // frames, so we only create the shared context if nsight is NOT active. if (!nsightActive()) { - _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext = new OffscreenGLCanvas(); _chromiumShareContext->setObjectName("ChromiumShareContext"); _chromiumShareContext->create(_glWidget->qglContext()); if (!_chromiumShareContext->makeCurrent()) { qCWarning(interfaceapp, "Unable to make chromium shared context current"); } - qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + globalShareContext = _chromiumShareContext->getContext(); + qt_gl_set_global_share_context(globalShareContext); _chromiumShareContext->doneCurrent(); // Restore the GL widget context if (!_glWidget->makeCurrent()) { qCWarning(interfaceapp, "Unable to make window context current"); } - } else { - qCWarning(interfaceapp) << "nSight detected, disabling chrome rendering"; } } #endif + if (!globalShareContext) { + globalShareContext = _glWidget->qglContext(); + qt_gl_set_global_share_context(globalShareContext); + } + // Build a shared canvas / context for the QML rendering { _qmlShareContext = new OffscreenGLCanvas(); _qmlShareContext->setObjectName("QmlShareContext"); - _qmlShareContext->create(_glWidget->qglContext()); + _qmlShareContext->create(globalShareContext); if (!_qmlShareContext->makeCurrent()) { qCWarning(interfaceapp, "Unable to make QML shared context current"); } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index d9396ae4d1..12a02ffd32 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "AddressManager.h" #include "Application.h" @@ -40,6 +41,18 @@ extern "C" { #endif int main(int argc, const char* argv[]) { + auto format = getDefaultOpenGLSurfaceFormat(); +#ifdef Q_OS_MAC + // Deal with some weirdness in the chromium context sharing on Mac. + // The primary share context needs to be 3.2, so that the Chromium will + // succeed in it's creation of it's command stub contexts. + format.setVersion(3, 2); + // This appears to resolve the issues with corrupted fonts on OSX. No + // idea why. + qputenv("QT_ENABLE_GLYPH_CACHE_WORKAROUND", "true"); + // https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg +#endif + QSurfaceFormat::setDefaultFormat(format); setupHifiApplication(BuildInfo::INTERFACE_NAME); QStringList arguments; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 108f20b2dd..2fb40c8c30 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -115,6 +115,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { batch.resetViewTransform(); batch.setResourceTexture(0, _uiTexture); geometryCache->renderUnitQuad(batch, glm::vec4(1), _qmlGeometryId); + batch.setResourceTexture(0, nullptr); } void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index bc9ac84c91..bce5225a5c 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -261,6 +261,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { DependencyManager::get()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId); batch.popProjectionJitter(); + batch.setResourceTexture(0, nullptr); } bool WebEntityRenderer::hasWebSurface() { diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index ad7e51fbd3..cd2b19beec 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -25,6 +25,7 @@ #include "GLLogging.h" #include "Config.h" #include "GLHelpers.h" +#include "QOpenGLContextWrapper.h" using namespace gl; @@ -68,8 +69,6 @@ void Context::updateSwapchainMemoryUsage(size_t prevSize, size_t newSize) { } -Context* Context::PRIMARY = nullptr; - Context::Context() {} Context::Context(QWindow* window) { @@ -97,9 +96,6 @@ void Context::release() { _context = nullptr; #endif _window = nullptr; - if (PRIMARY == this) { - PRIMARY = nullptr; - } updateSwapchainMemoryCounter(); } @@ -235,16 +231,10 @@ typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShare GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); -void Context::create() { - if (!PRIMARY) { - PRIMARY = static_cast(qApp->property(hifi::properties::gl::PRIMARY_CONTEXT).value()); - } - - if (PRIMARY) { - _version = PRIMARY->_version; - } +void Context::create(QOpenGLContext* shareContext) { assert(0 != _hwnd); assert(0 == _hdc); auto hwnd = _hwnd; @@ -338,7 +328,10 @@ void Context::create() { contextAttribs.push_back(0); } contextAttribs.push_back(0); - auto shareHglrc = PRIMARY ? PRIMARY->_hglrc : 0; + if (!shareContext) { + shareContext = qt_gl_global_share_context(); + } + HGLRC shareHglrc = (HGLRC)QOpenGLContextWrapper::nativeContext(shareContext); _hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]); } @@ -346,11 +339,6 @@ void Context::create() { throw std::runtime_error("Could not create GL context"); } - if (!PRIMARY) { - PRIMARY = this; - qApp->setProperty(hifi::properties::gl::PRIMARY_CONTEXT, QVariant::fromValue((void*)PRIMARY)); - } - if (!makeCurrent()) { throw std::runtime_error("Could not make context current"); } @@ -368,7 +356,7 @@ OffscreenContext::~OffscreenContext() { _window->deleteLater(); } -void OffscreenContext::create() { +void OffscreenContext::create(QOpenGLContext* shareContext) { if (!_window) { _window = new QWindow(); _window->setFlags(Qt::MSWindowsOwnDC); @@ -379,5 +367,5 @@ void OffscreenContext::create() { qCDebug(glLogging) << "New Offscreen GLContext, window size = " << windowSize.width() << " , " << windowSize.height(); QGuiApplication::processEvents(); } - Parent::create(); + Parent::create(shareContext); } diff --git a/libraries/gl/src/gl/Context.h b/libraries/gl/src/gl/Context.h index b6160cbd6c..05cb361725 100644 --- a/libraries/gl/src/gl/Context.h +++ b/libraries/gl/src/gl/Context.h @@ -21,6 +21,7 @@ class QSurface; class QWindow; class QOpenGLContext; class QThread; +class QOpenGLDebugMessage; #if defined(Q_OS_WIN) #define GL_CUSTOM_CONTEXT @@ -30,7 +31,6 @@ namespace gl { class Context { protected: QWindow* _window { nullptr }; - static Context* PRIMARY; static void destroyContext(QOpenGLContext* context); #if defined(GL_CUSTOM_CONTEXT) uint32_t _version { 0x0401 }; @@ -48,6 +48,9 @@ namespace gl { public: static bool enableDebugLogger(); + static void debugMessageHandler(const QOpenGLDebugMessage &debugMessage); + static void setupDebugLogging(QOpenGLContext* context); + Context(); Context(QWindow* window); void release(); @@ -59,14 +62,14 @@ namespace gl { static void makeCurrent(QOpenGLContext* context, QSurface* surface); void swapBuffers(); void doneCurrent(); - virtual void create(); + virtual void create(QOpenGLContext* shareContext = nullptr); QOpenGLContext* qglContext(); void moveToThread(QThread* thread); static size_t evalSurfaceMemoryUsage(uint32_t width, uint32_t height, uint32_t pixelSize); static size_t getSwapchainMemoryUsage(); static void updateSwapchainMemoryUsage(size_t prevSize, size_t newSize); - + private: static std::atomic _totalSwapchainMemoryUsage; @@ -81,7 +84,7 @@ namespace gl { QWindow* _window { nullptr }; public: virtual ~OffscreenContext(); - void create() override; + void create(QOpenGLContext* shareContext = nullptr) override; }; } diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index dd65c3076c..f554877b2a 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -17,6 +17,8 @@ #include #endif +#include + #include "GLHelpers.h" using namespace gl; @@ -47,6 +49,32 @@ void Context::moveToThread(QThread* thread) { qglContext()->moveToThread(thread); } +void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { + auto severity = debugMessage.severity(); + switch (severity) { + case QOpenGLDebugMessage::NotificationSeverity: + case QOpenGLDebugMessage::LowSeverity: + return; + default: + break; + } + qDebug(glLogging) << debugMessage; + return; +} + +void Context::setupDebugLogging(QOpenGLContext *context) { + QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(context); + QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, nullptr, [](const QOpenGLDebugMessage& message){ + Context::debugMessageHandler(message); + }); + if (logger->initialize()) { + logger->enableMessages(); + logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + } else { + qCWarning(glLogging) << "OpenGL context does not support debugging"; + } +} + #ifndef GL_CUSTOM_CONTEXT bool Context::makeCurrent() { updateSwapchainMemoryCounter(); @@ -65,21 +93,29 @@ void Context::doneCurrent() { } } +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); - -void Context::create() { +void Context::create(QOpenGLContext* shareContext) { _context = new QOpenGLContext(); - if (PRIMARY) { - _context->setShareContext(PRIMARY->qglContext()); - } else { - PRIMARY = this; + _context->setFormat(_window->format()); + if (!shareContext) { + shareContext = qt_gl_global_share_context(); } - _context->setFormat(getDefaultOpenGLSurfaceFormat()); - _context->create(); + _context->setShareContext(shareContext); + _context->create(); _swapchainPixelSize = evalGLFormatSwapchainPixelSize(_context->format()); updateSwapchainMemoryCounter(); + + if (!makeCurrent()) { + throw std::runtime_error("Could not make context current"); + } + if (enableDebugLogger()) { + setupDebugLogging(_context); + } + doneCurrent(); + } #endif diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 7ebba4f8d8..c22bd0dde5 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -38,10 +38,15 @@ void gl::getTargetVersion(int& major, int& minor) { #if defined(USE_GLES) major = 3; minor = 2; +#else +#if defined(Q_OS_MAC) + major = 4; + minor = 1; #else major = 4; minor = disableGl45() ? 1 : 5; #endif +#endif } const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { @@ -57,6 +62,7 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { #else format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); #endif + format.setOption(QSurfaceFormat::DebugContext); // Qt Quick may need a depth and stencil buffer. Always make sure these are available. format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); @@ -64,7 +70,6 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { ::gl::getTargetVersion(major, minor); format.setMajorVersion(major); format.setMinorVersion(minor); - QSurfaceFormat::setDefaultFormat(format); }); return format; } diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index 1c0ad1a85e..94702a9906 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -63,10 +63,10 @@ int GLWidget::getDeviceHeight() const { return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); } -void GLWidget::createContext() { +void GLWidget::createContext(QOpenGLContext* shareContext) { _context = new gl::Context(); _context->setWindow(windowHandle()); - _context->create(); + _context->create(shareContext); _context->makeCurrent(); _context->clear(); _context->doneCurrent(); diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h index a0bf8ea0b0..777d43e8af 100644 --- a/libraries/gl/src/gl/GLWidget.h +++ b/libraries/gl/src/gl/GLWidget.h @@ -29,7 +29,7 @@ public: int getDeviceHeight() const; QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); } QPaintEngine* paintEngine() const override; - void createContext(); + void createContext(QOpenGLContext* shareContext = nullptr); bool makeCurrent(); void doneCurrent(); void swapBuffers(); diff --git a/libraries/gl/src/gl/GLWindow.cpp b/libraries/gl/src/gl/GLWindow.cpp index e1e6279b1c..7930e050de 100644 --- a/libraries/gl/src/gl/GLWindow.cpp +++ b/libraries/gl/src/gl/GLWindow.cpp @@ -22,7 +22,7 @@ void GLWindow::createContext(QOpenGLContext* shareContext) { void GLWindow::createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext) { _context = new gl::Context(); _context->setWindow(this); - _context->create(); + _context->create(shareContext); _context->makeCurrent(); _context->clear(); } diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 6598a26c99..1256e316ff 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -75,32 +75,9 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { } #endif - if (gl::Context::enableDebugLogger()) { - _context->makeCurrent(_offscreenSurface); - QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); - connect(logger, &QOpenGLDebugLogger::messageLogged, this, &OffscreenGLCanvas::onMessageLogged); - logger->initialize(); - logger->enableMessages(); - logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); - _context->doneCurrent(); - } - return true; } -void OffscreenGLCanvas::onMessageLogged(const QOpenGLDebugMessage& debugMessage) { - auto severity = debugMessage.severity(); - switch (severity) { - case QOpenGLDebugMessage::NotificationSeverity: - case QOpenGLDebugMessage::LowSeverity: - return; - default: - break; - } - qDebug(glLogging) << debugMessage; - return; -} - bool OffscreenGLCanvas::makeCurrent() { bool result = _context->makeCurrent(_offscreenSurface); if (glGetString) { diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index a4960ae234..374720a910 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -35,9 +35,6 @@ public: void setThreadContext(); static bool restoreThreadContext(); -private slots: - void onMessageLogged(const QOpenGLDebugMessage &debugMessage); - protected: void clearThreadContext(); diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index 0b153a5ae8..fbebb1128d 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -13,6 +13,10 @@ #include +#ifdef Q_OS_WIN +#include +#endif + uint32_t QOpenGLContextWrapper::currentContextVersion() { QOpenGLContext* context = QOpenGLContext::currentContext(); if (!context) { @@ -45,6 +49,19 @@ void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) { _context->setFormat(format); } +#ifdef Q_OS_WIN +void* QOpenGLContextWrapper::nativeContext(QOpenGLContext* context) { + HGLRC result = 0; + if (context != nullptr) { + auto nativeHandle = context->nativeHandle(); + if (nativeHandle.canConvert()) { + result = nativeHandle.value().context(); + } + } + return result; +} +#endif + bool QOpenGLContextWrapper::create() { return _context->create(); } diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index b09ad1a4c3..32ba7f22e8 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -13,6 +13,7 @@ #define hifi_QOpenGLContextWrapper_h #include +#include class QOpenGLContext; class QSurface; @@ -21,6 +22,10 @@ class QThread; class QOpenGLContextWrapper { public: +#ifdef Q_OS_WIN + static void* nativeContext(QOpenGLContext* context); +#endif + QOpenGLContextWrapper(); QOpenGLContextWrapper(QOpenGLContext* context); virtual ~QOpenGLContextWrapper(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 1203e65685..c1ce05c18b 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -708,37 +708,37 @@ void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) { void GLBackend::releaseBuffer(GLuint id, Size size) const { Lock lock(_trashMutex); - _buffersTrash.push_back({ id, size }); + _currentFrameTrash.buffersTrash.push_back({ id, size }); } void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const { Lock lock(_trashMutex); - _externalTexturesTrash.push_back({ id, recycler }); + _currentFrameTrash.externalTexturesTrash.push_back({ id, recycler }); } void GLBackend::releaseTexture(GLuint id, Size size) const { Lock lock(_trashMutex); - _texturesTrash.push_back({ id, size }); + _currentFrameTrash.texturesTrash.push_back({ id, size }); } void GLBackend::releaseFramebuffer(GLuint id) const { Lock lock(_trashMutex); - _framebuffersTrash.push_back(id); + _currentFrameTrash.framebuffersTrash.push_back(id); } void GLBackend::releaseShader(GLuint id) const { Lock lock(_trashMutex); - _shadersTrash.push_back(id); + _currentFrameTrash.shadersTrash.push_back(id); } void GLBackend::releaseProgram(GLuint id) const { Lock lock(_trashMutex); - _programsTrash.push_back(id); + _currentFrameTrash.programsTrash.push_back(id); } void GLBackend::releaseQuery(GLuint id) const { Lock lock(_trashMutex); - _queriesTrash.push_back(id); + _currentFrameTrash.queriesTrash.push_back(id); } void GLBackend::queueLambda(const std::function lambda) const { @@ -746,6 +746,81 @@ void GLBackend::queueLambda(const std::function lambda) const { _lambdaQueue.push_back(lambda); } +void GLBackend::FrameTrash::cleanup() { + glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(fence); + + { + std::vector ids; + ids.reserve(buffersTrash.size()); + for (auto pair : buffersTrash) { + ids.push_back(pair.first); + } + if (!ids.empty()) { + glDeleteBuffers((GLsizei)ids.size(), ids.data()); + } + } + + { + std::vector ids; + ids.reserve(framebuffersTrash.size()); + for (auto id : framebuffersTrash) { + ids.push_back(id); + } + if (!ids.empty()) { + glDeleteFramebuffers((GLsizei)ids.size(), ids.data()); + } + } + + { + std::vector ids; + ids.reserve(texturesTrash.size()); + for (auto pair : texturesTrash) { + ids.push_back(pair.first); + } + if (!ids.empty()) { + glDeleteTextures((GLsizei)ids.size(), ids.data()); + } + } + + { + if (!externalTexturesTrash.empty()) { + std::vector fences; + fences.resize(externalTexturesTrash.size()); + for (size_t i = 0; i < externalTexturesTrash.size(); ++i) { + fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + // External texture fences will be read in another thread/context, so we need a flush + glFlush(); + size_t index = 0; + for (auto pair : externalTexturesTrash) { + auto fence = fences[index++]; + pair.second(pair.first, fence); + } + } + } + + for (auto id : programsTrash) { + glDeleteProgram(id); + } + + for (auto id : shadersTrash) { + glDeleteShader(id); + } + + { + std::vector ids; + ids.reserve(queriesTrash.size()); + for (auto id : queriesTrash) { + ids.push_back(id); + } + if (!ids.empty()) { + glDeleteQueries((GLsizei)ids.size(), ids.data()); + } + } + +} + void GLBackend::recycle() const { PROFILE_RANGE(render_gpu_gl, __FUNCTION__) { @@ -759,112 +834,16 @@ void GLBackend::recycle() const { } } - { - std::vector ids; - std::list> buffersTrash; - { - Lock lock(_trashMutex); - std::swap(_buffersTrash, buffersTrash); - } - ids.reserve(buffersTrash.size()); - for (auto pair : buffersTrash) { - ids.push_back(pair.first); - } - if (!ids.empty()) { - glDeleteBuffers((GLsizei)ids.size(), ids.data()); - } + while (!_previousFrameTrashes.empty()) { + _previousFrameTrashes.front().cleanup(); + _previousFrameTrashes.pop_front(); } + _previousFrameTrashes.emplace_back(); { - std::vector ids; - std::list framebuffersTrash; - { - Lock lock(_trashMutex); - std::swap(_framebuffersTrash, framebuffersTrash); - } - ids.reserve(framebuffersTrash.size()); - for (auto id : framebuffersTrash) { - ids.push_back(id); - } - if (!ids.empty()) { - glDeleteFramebuffers((GLsizei)ids.size(), ids.data()); - } - } - - { - std::vector ids; - std::list> texturesTrash; - { - Lock lock(_trashMutex); - std::swap(_texturesTrash, texturesTrash); - } - ids.reserve(texturesTrash.size()); - for (auto pair : texturesTrash) { - ids.push_back(pair.first); - } - if (!ids.empty()) { - glDeleteTextures((GLsizei)ids.size(), ids.data()); - } - } - - { - std::list> externalTexturesTrash; - { - Lock lock(_trashMutex); - std::swap(_externalTexturesTrash, externalTexturesTrash); - } - if (!externalTexturesTrash.empty()) { - std::vector fences; - fences.resize(externalTexturesTrash.size()); - for (size_t i = 0; i < externalTexturesTrash.size(); ++i) { - fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } - // External texture fences will be read in another thread/context, so we need a flush - glFlush(); - size_t index = 0; - for (auto pair : externalTexturesTrash) { - auto fence = fences[index++]; - pair.second(pair.first, fence); - } - } - } - - { - std::list programsTrash; - { - Lock lock(_trashMutex); - std::swap(_programsTrash, programsTrash); - } - for (auto id : programsTrash) { - glDeleteProgram(id); - } - } - - { - std::list shadersTrash; - { - Lock lock(_trashMutex); - std::swap(_shadersTrash, shadersTrash); - } - for (auto id : shadersTrash) { - glDeleteShader(id); - } - } - - { - std::vector ids; - std::list queriesTrash; - { - Lock lock(_trashMutex); - std::swap(_queriesTrash, queriesTrash); - } - ids.reserve(queriesTrash.size()); - for (auto id : queriesTrash) { - ids.push_back(id); - } - if (!ids.empty()) { - glDeleteQueries((GLsizei)ids.size(), ids.data()); - } + Lock lock(_trashMutex); + _previousFrameTrashes.back().swap(_currentFrameTrash); + _previousFrameTrashes.back().fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } _textureManagement._transferEngine->manageMemory(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index c309bcb864..37dde5b08e 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -419,16 +419,34 @@ protected: static const size_t INVALID_OFFSET = (size_t)-1; bool _inRenderTransferPass{ false }; int _currentDraw{ -1 }; - - std::list profileRanges; + + struct FrameTrash { + GLsync fence = nullptr; + std::list> buffersTrash; + std::list> texturesTrash; + std::list> externalTexturesTrash; + std::list framebuffersTrash; + std::list shadersTrash; + std::list programsTrash; + std::list queriesTrash; + + void swap(FrameTrash& other) { + buffersTrash.swap(other.buffersTrash); + texturesTrash.swap(other.texturesTrash); + externalTexturesTrash.swap(other.externalTexturesTrash); + framebuffersTrash.swap(other.framebuffersTrash); + shadersTrash.swap(other.shadersTrash); + programsTrash.swap(other.programsTrash); + queriesTrash.swap(other.queriesTrash); + } + + void cleanup(); + }; + mutable Mutex _trashMutex; - mutable std::list> _buffersTrash; - mutable std::list> _texturesTrash; - mutable std::list> _externalTexturesTrash; - mutable std::list _framebuffersTrash; - mutable std::list _shadersTrash; - mutable std::list _programsTrash; - mutable std::list _queriesTrash; + mutable FrameTrash _currentFrameTrash; + mutable std::list _previousFrameTrashes; + std::list profileRanges; mutable std::list> _lambdaQueue; void renderPassTransfer(const Batch& batch); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 39f3123d40..8a56929e6b 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -139,9 +139,9 @@ void RenderEventHandler::onRender() { glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + // Fence will be used in another thread / context, so a flush is required glFlush(); _shared->updateTextureAndFence({ texture, fence }); - // Fence will be used in another thread / context, so a flush is required _shared->_quickWindow->resetOpenGLState(); } } @@ -167,4 +167,5 @@ void RenderEventHandler::onQuit() { moveToThread(qApp->thread()); QThread::currentThread()->quit(); } -#endif \ No newline at end of file + +#endif diff --git a/libraries/qml/src/qml/impl/TextureCache.cpp b/libraries/qml/src/qml/impl/TextureCache.cpp index 7af8fa1ac9..7b4fb3adaf 100644 --- a/libraries/qml/src/qml/impl/TextureCache.cpp +++ b/libraries/qml/src/qml/impl/TextureCache.cpp @@ -51,8 +51,10 @@ uint32_t TextureCache::acquireTexture(const QSize& size) { if (!textureSet.returnedTextures.empty()) { auto textureAndFence = textureSet.returnedTextures.front(); textureSet.returnedTextures.pop_front(); - glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED); - glDeleteSync((GLsync)textureAndFence.second); + if (textureAndFence.second) { + glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)textureAndFence.second); + } return textureAndFence.first; } return createTexture(size); @@ -101,9 +103,11 @@ void TextureCache::destroyTexture(uint32_t texture) { void TextureCache::destroy(const Value& textureAndFence) { const auto& fence = textureAndFence.second; - // FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context. - glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED); - glDeleteSync((GLsync)fence); + if (fence) { + // FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context. + glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)fence); + } destroyTexture(textureAndFence.first); } diff --git a/tests-manual/qml/qml/MacQml.qml b/tests-manual/qml/qml/MacQml.qml new file mode 100644 index 0000000000..bb7e3a0dff --- /dev/null +++ b/tests-manual/qml/qml/MacQml.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls 1.2 +import QtWebEngine 1.5 + +Item { + width: 640 + height: 480 + + Rectangle { + width: 5 + height: 5 + color: "red" + ColorAnimation on color { loops: Animation.Infinite; from: "red"; to: "yellow"; duration: 1000 } + } + + + WebEngineView { + id: root + url: "https://google.com/" + x: 6; y: 6; + width: parent.width * 0.8 + height: parent.height * 0.8 + + } +} diff --git a/tests-manual/qml/src/MacQml.cpp b/tests-manual/qml/src/MacQml.cpp new file mode 100644 index 0000000000..7f7854ce87 --- /dev/null +++ b/tests-manual/qml/src/MacQml.cpp @@ -0,0 +1,78 @@ +#include "MacQml.h" + +#include + +#include + +#include + +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; +// +//void MacQml::destroySurface(QmlInfo& qmlInfo) { +// auto& surface = qmlInfo.surface; +// auto& currentTexture = qmlInfo.texture; +// if (currentTexture) { +// auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +// glFlush(); +// _discardLamdba(currentTexture, readFence); +// } +// auto webView = surface->getRootItem(); +// if (webView) { +// // stop loading +// QMetaObject::invokeMethod(webView, "stop"); +// webView->setProperty(URL_PROPERTY, "about:blank"); +// } +// surface->pause(); +// surface.reset(); +//} + +void MacQml::update() { + auto rootItem =_surface->getRootItem(); + float now = sinf(secTimestampNow()); + rootItem->setProperty("level", abs(now)); + rootItem->setProperty("muted", now > 0.0f); + rootItem->setProperty("statsValue", rand()); + + // Fetch any new textures + TextureAndFence newTextureAndFence; + if (_surface->fetchTexture(newTextureAndFence)) { + if (_texture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(_texture, readFence); + } + _texture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } +} + +void MacQml::init() { + Parent::init(); + _glf.glGenFramebuffers(1, &_fbo); + _surface.reset(new hifi::qml::OffscreenSurface()); + //QUrl url =getTestResource("qml/main.qml"); + QUrl url = getTestResource("qml/MacQml.qml"); + hifi::qml::QmlContextObjectCallback callback =[](QQmlContext* context, QQuickItem* item) { + }; + _surface->load(url, callback); + _surface->resize(_window->size()); + _surface->resume(); + +} + +void MacQml::draw() { + auto size = _window->geometry().size(); + if (_texture) { + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + _glf.glBlitFramebuffer( + // src coordinates + 0, 0, size.width(), size.height(), + // dst coordinates + 0, 0, size.width(), size.height(), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + } +} diff --git a/tests-manual/qml/src/MacQml.h b/tests-manual/qml/src/MacQml.h new file mode 100644 index 0000000000..50f71cb72e --- /dev/null +++ b/tests-manual/qml/src/MacQml.h @@ -0,0 +1,16 @@ +#include "TestCase.h" + +#include + +class MacQml : public TestCase { + using Parent = TestCase; +public: + GLuint _texture{ 0 }; + QmlPtr _surface; + GLuint _fbo{ 0 }; + + MacQml(const QWindow* window) : Parent(window) {} + void update() override; + void init() override; + void draw() override; +}; diff --git a/tests-manual/qml/src/StressWeb.cpp b/tests-manual/qml/src/StressWeb.cpp new file mode 100644 index 0000000000..71293feb9a --- /dev/null +++ b/tests-manual/qml/src/StressWeb.cpp @@ -0,0 +1,131 @@ +#include "StressWeb.h" + +#include + +#include + +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +static const int DEFAULT_MAX_FPS = 10; +static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; +static const char* URL_PROPERTY{ "url" }; + +QString StressWeb::getSourceUrl(bool video) { + static const std::vector SOURCE_URLS{ + "https://www.reddit.com/wiki/random", + "https://en.wikipedia.org/wiki/Wikipedia:Random", + "https://slashdot.org/", + }; + + static const std::vector VIDEO_SOURCE_URLS{ + "https://www.youtube.com/watch?v=gDXwhHm4GhM", + "https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; + auto index = rand() % sourceUrls.size(); + return sourceUrls[index]; +} + + + +void StressWeb::buildSurface(QmlInfo& qmlInfo, bool video) { + ++_surfaceCount; + auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); + auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); + qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); + qmlInfo.texture = 0; + qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); + qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl(video)); + }); + qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); + qmlInfo.surface->resize(_qmlSize); + qmlInfo.surface->resume(); +} + +void StressWeb::destroySurface(QmlInfo& qmlInfo) { + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + if (currentTexture) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + auto webView = surface->getRootItem(); + if (webView) { + // stop loading + QMetaObject::invokeMethod(webView, "stop"); + webView->setProperty(URL_PROPERTY, "about:blank"); + } + surface->pause(); + surface.reset(); +} + +void StressWeb::update() { + auto now = usecTimestampNow(); + // Fetch any new textures + for (size_t x = 0; x < DIVISIONS_X; ++x) { + for (size_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface) { + if (now < _createStopTime && randFloat() > 0.99f) { + buildSurface(qmlInfo, x == 0 && y == 0); + } else { + continue; + } + } + + if (now > qmlInfo.lifetime) { + destroySurface(qmlInfo); + continue; + } + + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + + TextureAndFence newTextureAndFence; + if (surface->fetchTexture(newTextureAndFence)) { + if (currentTexture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + currentTexture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } + } + } +} + +void StressWeb::init() { + Parent::init(); + _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); + _glf.glGenFramebuffers(1, &_fbo); +} + +void StressWeb::draw() { + auto size = _window->geometry().size(); + auto incrementX = size.width() / DIVISIONS_X; + auto incrementY = size.height() / DIVISIONS_Y; + + for (uint32_t x = 0; x < DIVISIONS_X; ++x) { + for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface || !qmlInfo.texture) { + continue; + } + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0); + _glf.glBlitFramebuffer( + // src coordinates + 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, + // dst coordinates + incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + } + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); +} diff --git a/tests-manual/qml/src/StressWeb.h b/tests-manual/qml/src/StressWeb.h new file mode 100644 index 0000000000..a68e34d0c1 --- /dev/null +++ b/tests-manual/qml/src/StressWeb.h @@ -0,0 +1,34 @@ +#include "TestCase.h" + +#include + +#include + +#define DIVISIONS_X 5 +#define DIVISIONS_Y 5 + +class StressWeb : public TestCase { + using Parent = TestCase; +public: + using QmlPtr = QSharedPointer; + + struct QmlInfo { + QmlPtr surface; + GLuint texture{ 0 }; + uint64_t lifetime{ 0 }; + }; + + size_t _surfaceCount{ 0 }; + uint64_t _createStopTime{ 0 }; + const QSize _qmlSize{ 640, 480 }; + std::array, DIVISIONS_X> _surfaces; + GLuint _fbo{ 0 }; + + StressWeb(const QWindow* window) : Parent(window) {} + static QString getSourceUrl(bool video); + void buildSurface(QmlInfo& qmlInfo, bool video); + void destroySurface(QmlInfo& qmlInfo); + void update() override; + void init() override; + void draw() override; +}; diff --git a/tests-manual/qml/src/TestCase.cpp b/tests-manual/qml/src/TestCase.cpp new file mode 100644 index 0000000000..534de71e51 --- /dev/null +++ b/tests-manual/qml/src/TestCase.cpp @@ -0,0 +1,25 @@ +#include "TestCase.h" + +#include +#include + +void TestCase::destroy() { +} +void TestCase::update() { +} + +void TestCase::init() { + _glf.initializeOpenGLFunctions(); + _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); +} + +QUrl TestCase::getTestResource(const QString& relativePath) { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return QUrl::fromLocalFile(dir + relativePath); +} diff --git a/tests-manual/qml/src/TestCase.h b/tests-manual/qml/src/TestCase.h new file mode 100644 index 0000000000..191eecb408 --- /dev/null +++ b/tests-manual/qml/src/TestCase.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include + +class TestCase { +public: + using QmlPtr = QSharedPointer; + using Builder = std::function; + TestCase(const QWindow* window) : _window(window) {} + virtual void init(); + virtual void destroy(); + virtual void update(); + virtual void draw() = 0; + static QUrl getTestResource(const QString& relativePath); + +protected: + QOpenGLFunctions_4_1_Core _glf; + const QWindow* _window; + std::function _discardLamdba; +}; diff --git a/tests-manual/qml/src/main.cpp b/tests-manual/qml/src/main.cpp index d70bb52dde..1d98ebf8c8 100644 --- a/tests-manual/qml/src/main.cpp +++ b/tests-manual/qml/src/main.cpp @@ -43,6 +43,11 @@ #include #include #include +#include +#include + +#include "TestCase.h" +#include "MacQml.h" namespace gl { extern void initModuleGl(); @@ -67,53 +72,37 @@ QUrl getTestResource(const QString& relativePath) { return QUrl::fromLocalFile(dir + relativePath); } -#define DIVISIONS_X 5 -#define DIVISIONS_Y 5 - using QmlPtr = QSharedPointer; using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; -struct QmlInfo { - QmlPtr surface; - GLuint texture{ 0 }; - uint64_t lifetime{ 0 }; -}; - class TestWindow : public QWindow { public: - TestWindow(); + TestWindow(const TestCase::Builder& caseBuilder); private: QOpenGLContext _glContext; OffscreenGLCanvas _sharedContext; - std::array, DIVISIONS_X> _surfaces; + TestCase* _testCase{ nullptr }; QOpenGLFunctions_4_1_Core _glf; - std::function _discardLamdba; QSize _size; - size_t _surfaceCount{ 0 }; - GLuint _fbo{ 0 }; - const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; - uint64_t _createStopTime; void initGl(); - void updateSurfaces(); - void buildSurface(QmlInfo& qmlInfo, bool allowVideo); - void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); void resizeEvent(QResizeEvent* ev) override; }; -TestWindow::TestWindow() { +TestWindow::TestWindow(const TestCase::Builder& builder) { Setting::init(); + + _testCase = builder(this); setSurfaceType(QSurface::OpenGLSurface); qmlRegisterType("Hifi", 1, 0, "TestItem"); show(); - _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); resize(QSize(800, 600)); @@ -129,162 +118,84 @@ TestWindow::TestWindow() { }); } +Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); +OffscreenGLCanvas* _chromiumShareContext{ nullptr}; void TestWindow::initGl() { _glContext.setFormat(format()); + + auto globalShareContext = qt_gl_global_share_context(); + if (globalShareContext) { + _glContext.setShareContext(globalShareContext); + globalShareContext->makeCurrent(this); + gl::Context::setupDebugLogging(globalShareContext); + globalShareContext->doneCurrent(); + } + if (!_glContext.create() || !_glContext.makeCurrent(this)) { qFatal("Unable to intialize Window GL context"); } + gl::Context::setupDebugLogging(&_glContext); gl::initModuleGl(); _glf.initializeOpenGLFunctions(); - _glf.glGenFramebuffers(1, &_fbo); if (!_sharedContext.create(&_glContext) || !_sharedContext.makeCurrent()) { qFatal("Unable to intialize Shared GL context"); } hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext()); - _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); + + if (!globalShareContext) { + _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext->setObjectName("ChromiumShareContext"); + _chromiumShareContext->create(&_glContext); + if (!_chromiumShareContext->makeCurrent()) { + qFatal("Unable to make chromium shared context current"); + } + + qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + _chromiumShareContext->doneCurrent(); + } + + // Restore the GL widget context + if (!_glContext.makeCurrent(this)) { + qFatal("Unable to make window context current"); + } + + _testCase->init(); } void TestWindow::resizeWindow(const QSize& size) { _size = size; } -static const int DEFAULT_MAX_FPS = 10; -static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; -static const char* URL_PROPERTY{ "url" }; - -QString getSourceUrl(bool video) { - static const std::vector SOURCE_URLS{ - "https://www.reddit.com/wiki/random", - "https://en.wikipedia.org/wiki/Wikipedia:Random", - "https://slashdot.org/", - }; - - static const std::vector VIDEO_SOURCE_URLS{ - "https://www.youtube.com/watch?v=gDXwhHm4GhM", - "https://www.youtube.com/watch?v=Ch_hoYPPeGc", - }; - - const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; - auto index = rand() % sourceUrls.size(); - return sourceUrls[index]; -} - -void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) { - ++_surfaceCount; - auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); - auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); - qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); - qmlInfo.texture = 0; - qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); - qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { - item->setProperty(URL_PROPERTY, getSourceUrl(video)); - }); - qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); - qmlInfo.surface->resize(_qmlSize); - qmlInfo.surface->resume(); -} - -void TestWindow::destroySurface(QmlInfo& qmlInfo) { - auto& surface = qmlInfo.surface; - auto& currentTexture = qmlInfo.texture; - if (currentTexture) { - auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - _discardLamdba(currentTexture, readFence); - } - auto webView = surface->getRootItem(); - if (webView) { - // stop loading - QMetaObject::invokeMethod(webView, "stop"); - webView->setProperty(URL_PROPERTY, "about:blank"); - } - surface->pause(); - surface.reset(); -} - -void TestWindow::updateSurfaces() { - auto now = usecTimestampNow(); - // Fetch any new textures - for (size_t x = 0; x < DIVISIONS_X; ++x) { - for (size_t y = 0; y < DIVISIONS_Y; ++y) { - auto& qmlInfo = _surfaces[x][y]; - if (!qmlInfo.surface) { - if (now < _createStopTime && randFloat() > 0.99f) { - buildSurface(qmlInfo, x == 0 && y == 0); - } else { - continue; - } - } - - if (now > qmlInfo.lifetime) { - destroySurface(qmlInfo); - continue; - } - - auto& surface = qmlInfo.surface; - auto& currentTexture = qmlInfo.texture; - - TextureAndFence newTextureAndFence; - if (surface->fetchTexture(newTextureAndFence)) { - if (currentTexture != 0) { - auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - _discardLamdba(currentTexture, readFence); - } - currentTexture = newTextureAndFence.first; - _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - } - } - } -} - void TestWindow::draw() { if (_aboutToQuit) { return; } - + // Attempting to draw before we're visible and have a valid size will // produce GL errors. if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { return; } - + static std::once_flag once; std::call_once(once, [&] { initGl(); }); - + if (!_glContext.makeCurrent(this)) { return; } - - updateSurfaces(); - - auto size = this->geometry().size(); - auto incrementX = size.width() / DIVISIONS_X; - auto incrementY = size.height() / DIVISIONS_Y; + + _testCase->update(); + + auto size = geometry().size(); _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - for (uint32_t x = 0; x < DIVISIONS_X; ++x) { - for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { - auto& qmlInfo = _surfaces[x][y]; - if (!qmlInfo.surface || !qmlInfo.texture) { - continue; - } - _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); - _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0); - _glf.glBlitFramebuffer( - // src coordinates - 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, - // dst coordinates - incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), - // blit mask and filter - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - } - _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + _testCase->draw(); + _glContext.swapBuffers(this); } @@ -292,19 +203,15 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { resizeWindow(ev->size()); } -int main(int argc, char** argv) { - QSurfaceFormat format; - format.setDepthBufferSize(24); - format.setStencilBufferSize(8); +int main(int argc, char** argv) { + auto format = getDefaultOpenGLSurfaceFormat(); format.setVersion(4, 1); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); - format.setOption(QSurfaceFormat::DebugContext); QSurfaceFormat::setDefaultFormat(format); - // setFormat(format); QGuiApplication app(argc, argv); - TestWindow window; + TestCase::Builder builder = [](const QWindow* window)->TestCase*{ return new MacQml(window); }; + TestWindow window(builder); return app.exec(); } From 28b45ce1d9932fbb93041be2b7542ab4a6538564 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 1 Oct 2018 15:13:11 -0700 Subject: [PATCH 125/131] Resolve compositing glitches --- interface/src/Application.cpp | 52 +++++++++++-------- .../display-plugins/OpenGLDisplayPlugin.cpp | 2 + libraries/gl/src/gl/GLHelpers.cpp | 35 +++++++++++++ libraries/gl/src/gl/GLHelpers.h | 3 ++ libraries/gl/src/gl/OffscreenGLCanvas.cpp | 6 ++- libraries/gl/src/gl/OffscreenGLCanvas.h | 2 + .../qml/src/qml/impl/RenderEventHandler.cpp | 3 ++ 7 files changed, 80 insertions(+), 23 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 700561325f..6f65a124dd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2736,7 +2736,36 @@ void Application::initializeGL() { // We have to account for this possibility by checking here for an existing // global share context auto globalShareContext = qt_gl_global_share_context(); - _glWidget->createContext(globalShareContext); + +#if !defined(DISABLE_QML) + // Build a shared canvas / context for the Chromium processes + if (!globalShareContext) { + // Chromium rendering uses some GL functions that prevent nSight from capturing + // frames, so we only create the shared context if nsight is NOT active. + if (!nsightActive()) { + _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext->setObjectName("ChromiumShareContext"); + auto format =QSurfaceFormat::defaultFormat(); +#ifdef Q_OS_MAC + // On mac, the primary shared OpenGL context must be a 3.2 core context, + // or chromium flips out and spews error spam (but renders fine) + format.setMajorVersion(3); + format.setMinorVersion(2); +#endif + _chromiumShareContext->setFormat(format); + _chromiumShareContext->create(); + if (!_chromiumShareContext->makeCurrent()) { + qCWarning(interfaceapp, "Unable to make chromium shared context current"); + } + globalShareContext = _chromiumShareContext->getContext(); + qt_gl_set_global_share_context(globalShareContext); + _chromiumShareContext->doneCurrent(); + } + } +#endif + + + _glWidget->createContext(globalShareContext); if (!_glWidget->makeCurrent()) { qCWarning(interfaceapp, "Unable to make window context current"); @@ -2749,27 +2778,6 @@ void Application::initializeGL() { if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); } - - // Build a shared canvas / context for the Chromium processes - if (!globalShareContext) { - // Chromium rendering uses some GL functions that prevent nSight from capturing - // frames, so we only create the shared context if nsight is NOT active. - if (!nsightActive()) { - _chromiumShareContext = new OffscreenGLCanvas(); - _chromiumShareContext->setObjectName("ChromiumShareContext"); - _chromiumShareContext->create(_glWidget->qglContext()); - if (!_chromiumShareContext->makeCurrent()) { - qCWarning(interfaceapp, "Unable to make chromium shared context current"); - } - globalShareContext = _chromiumShareContext->getContext(); - qt_gl_set_global_share_context(globalShareContext); - _chromiumShareContext->doneCurrent(); - // Restore the GL widget context - if (!_glWidget->makeCurrent()) { - qCWarning(interfaceapp, "Unable to make window context current"); - } - } - } #endif if (!globalShareContext) { diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6448c6d3a1..6525672aee 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -179,7 +179,9 @@ public: _context->makeCurrent(); { PROFILE_RANGE(render, "PluginPresent") + gl::globalLock(); currentPlugin->present(); + gl::globalRelease(false); CHECK_GL_ERROR(); } _context->doneCurrent(); diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index c22bd0dde5..ba27a13f58 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -34,6 +34,41 @@ bool gl::disableGl45() { #endif } +#ifdef Q_OS_MAC +#define SERIALIZE_GL_RENDERING +#endif + +#ifdef SERIALIZE_GL_RENDERING + +// This terrible terrible hack brought to you by the complete lack of reasonable +// OpenGL debugging tools on OSX. Without this serialization code, the UI textures +// frequently become 'glitchy' and get composited onto the main scene in what looks +// like a partially rendered state. +// This looks very much like either state bleeding across the contexts, or bad +// synchronization for the shared OpenGL textures. However, previous attempts to resolve +// it, even with gratuitous use of glFinish hasn't improved the situation + +static std::mutex _globalOpenGLLock; + +void gl::globalLock() { + _globalOpenGLLock.lock(); +} + +void gl::globalRelease(bool finish) { + if (finish) { + glFinish(); + } + _globalOpenGLLock.unlock(); +} + +#else + +void gl::globalLock() {} +void gl::globalRelease(bool finish) {} + +#endif + + void gl::getTargetVersion(int& major, int& minor) { #if defined(USE_GLES) major = 3; diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index 6252eba2f0..1865442ef6 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -35,6 +35,9 @@ int glVersionToInteger(QString glVersion); bool isRenderThread(); namespace gl { + void globalLock(); + void globalRelease(bool finish = true); + void withSavedContext(const std::function& f); bool checkGLError(const char* name); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 1256e316ff..37289745a4 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -33,6 +33,7 @@ OffscreenGLCanvas::OffscreenGLCanvas() : _context(new QOpenGLContext), _offscreenSurface(new QOffscreenSurface) { + setFormat(getDefaultOpenGLSurfaceFormat()); } OffscreenGLCanvas::~OffscreenGLCanvas() { @@ -49,12 +50,15 @@ OffscreenGLCanvas::~OffscreenGLCanvas() { } +void OffscreenGLCanvas::setFormat(const QSurfaceFormat& format) { + _context->setFormat(format); +} + bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { if (nullptr != sharedContext) { sharedContext->doneCurrent(); _context->setShareContext(sharedContext); } - _context->setFormat(getDefaultOpenGLSurfaceFormat()); if (!_context->create()) { qFatal("Failed to create OffscreenGLCanvas context"); } diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index 374720a910..3946f760cf 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -18,11 +18,13 @@ class QOpenGLContext; class QOffscreenSurface; class QOpenGLDebugMessage; +class QSurfaceFormat; class OffscreenGLCanvas : public QObject { public: OffscreenGLCanvas(); ~OffscreenGLCanvas(); + void setFormat(const QSurfaceFormat& format); bool create(QOpenGLContext* sharedContext = nullptr); bool makeCurrent(); void doneCurrent(); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 8a56929e6b..46fc3490a7 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -12,6 +12,7 @@ #include #include +#include #include @@ -114,6 +115,7 @@ void RenderEventHandler::onRender() { PROFILE_RANGE(render_qml_gl, __FUNCTION__); + gl::globalLock(); if (!_shared->preRender()) { return; } @@ -144,6 +146,7 @@ void RenderEventHandler::onRender() { _shared->updateTextureAndFence({ texture, fence }); _shared->_quickWindow->resetOpenGLState(); } + gl::globalRelease(); } void RenderEventHandler::onQuit() { From 405ec228b8cae4aac0d340f6f132ddf825c6fb23 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 1 Oct 2018 16:07:02 -0700 Subject: [PATCH 126/131] Fix mac warning --- tests-manual/qml/src/MacQml.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-manual/qml/src/MacQml.cpp b/tests-manual/qml/src/MacQml.cpp index 7f7854ce87..13d8642822 100644 --- a/tests-manual/qml/src/MacQml.cpp +++ b/tests-manual/qml/src/MacQml.cpp @@ -29,7 +29,7 @@ using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; void MacQml::update() { auto rootItem =_surface->getRootItem(); float now = sinf(secTimestampNow()); - rootItem->setProperty("level", abs(now)); + rootItem->setProperty("level", fabs(now)); rootItem->setProperty("muted", now > 0.0f); rootItem->setProperty("statsValue", rand()); From 9a30073f27b0786186a8214e94cd20c27db8ea2b Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 2 Oct 2018 09:20:41 -0700 Subject: [PATCH 127/131] Only enable debug GL context on demand --- libraries/gl/src/gl/Context.cpp | 7 +++++-- libraries/gl/src/gl/GLHelpers.cpp | 6 +++++- tests-manual/qml/src/MacQml.cpp | 18 ------------------ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index cd2b19beec..a3e01018b5 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -31,6 +31,10 @@ using namespace gl; bool Context::enableDebugLogger() { +#if defined(Q_OS_MAC) + // OSX does not support GL_KHR_debug or GL_ARB_debug_output + return false; +#else #if defined(DEBUG) || defined(USE_GLES) static bool enableDebugLogger = true; #else @@ -45,6 +49,7 @@ bool Context::enableDebugLogger() { } }); return enableDebugLogger; +#endif } @@ -233,7 +238,6 @@ GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); - void Context::create(QOpenGLContext* shareContext) { assert(0 != _hwnd); assert(0 == _hdc); @@ -351,7 +355,6 @@ void Context::create(QOpenGLContext* shareContext) { #endif - OffscreenContext::~OffscreenContext() { _window->deleteLater(); } diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index ba27a13f58..15a41c3dc1 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -13,6 +13,8 @@ #include #include +#include "Context.h" + size_t evalGLFormatSwapchainPixelSize(const QSurfaceFormat& format) { size_t pixelSize = format.redBufferSize() + format.greenBufferSize() + format.blueBufferSize() + format.alphaBufferSize(); // We don't apply the length of the swap chain into this pixelSize since it is not vsible for the Process (on windows). @@ -97,7 +99,9 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { #else format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); #endif - format.setOption(QSurfaceFormat::DebugContext); + if (gl::Context::enableDebugLogger()) { + format.setOption(QSurfaceFormat::DebugContext); + } // Qt Quick may need a depth and stencil buffer. Always make sure these are available. format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); diff --git a/tests-manual/qml/src/MacQml.cpp b/tests-manual/qml/src/MacQml.cpp index 13d8642822..9c5f91041e 100644 --- a/tests-manual/qml/src/MacQml.cpp +++ b/tests-manual/qml/src/MacQml.cpp @@ -7,24 +7,6 @@ #include using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; -// -//void MacQml::destroySurface(QmlInfo& qmlInfo) { -// auto& surface = qmlInfo.surface; -// auto& currentTexture = qmlInfo.texture; -// if (currentTexture) { -// auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); -// glFlush(); -// _discardLamdba(currentTexture, readFence); -// } -// auto webView = surface->getRootItem(); -// if (webView) { -// // stop loading -// QMetaObject::invokeMethod(webView, "stop"); -// webView->setProperty(URL_PROPERTY, "about:blank"); -// } -// surface->pause(); -// surface.reset(); -//} void MacQml::update() { auto rootItem =_surface->getRootItem(); From 7ca8b540399586683b075b4ab787bbf7a046e16f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Oct 2018 14:34:23 -0700 Subject: [PATCH 128/131] Fix crash on startup on windows / android --- libraries/gl/src/gl/Context.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index a3e01018b5..d4ecbaa5ba 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -41,13 +41,6 @@ bool Context::enableDebugLogger() { static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); #endif - static std::once_flag once; - std::call_once(once, [&] { - // If the previous run crashed, force GL debug logging on - if (qApp->property(hifi::properties::CRASHED).toBool()) { - enableDebugLogger = true; - } - }); return enableDebugLogger; #endif } From 36edb939c3dbe7f72563fe590f41046d7863e345 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Oct 2018 16:28:30 -0700 Subject: [PATCH 129/131] Try to fix android crash --- interface/src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 12a02ffd32..5af0a9371d 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -41,8 +41,8 @@ extern "C" { #endif int main(int argc, const char* argv[]) { - auto format = getDefaultOpenGLSurfaceFormat(); #ifdef Q_OS_MAC + auto format = getDefaultOpenGLSurfaceFormat(); // Deal with some weirdness in the chromium context sharing on Mac. // The primary share context needs to be 3.2, so that the Chromium will // succeed in it's creation of it's command stub contexts. @@ -51,8 +51,8 @@ int main(int argc, const char* argv[]) { // idea why. qputenv("QT_ENABLE_GLYPH_CACHE_WORKAROUND", "true"); // https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg -#endif QSurfaceFormat::setDefaultFormat(format); +#endif setupHifiApplication(BuildInfo::INTERFACE_NAME); QStringList arguments; From a3a952265a7e8d6cf42c43676a7be33717162740 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 26 Oct 2018 11:23:04 -0700 Subject: [PATCH 130/131] Fix android crash --- .../display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp | 1 + libraries/gl/src/gl/OffscreenGLCanvas.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6525672aee..190d4d4104 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -88,6 +88,7 @@ public: // Move the OpenGL context to the present thread // Extra code because of the widget 'wrapper' context _context = context; + _context->doneCurrent(); _context->moveToThread(this); } diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 37289745a4..f05acb50e9 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -73,6 +73,7 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { if (!_context->makeCurrent(_offscreenSurface)) { qFatal("Unable to make offscreen surface current"); } + _context->doneCurrent(); #else if (!_offscreenSurface->isValid()) { qFatal("Offscreen surface is invalid"); From 1cca975b3713d8a1bca814f36b8600b2ccfa9064 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 27 Oct 2018 11:40:02 +1300 Subject: [PATCH 131/131] Fix Interface eating memory when minimized --- interface/src/Application.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 58aad654db..e54d2e0961 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6017,7 +6017,9 @@ void Application::update(float deltaTime) { { PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); - _overlays.update(deltaTime); + if (qApp->shouldPaint()) { + _overlays.update(deltaTime); + } } // Update _viewFrustum with latest camera and view frustum data... @@ -6102,8 +6104,10 @@ void Application::update(float deltaTime) { PROFILE_RANGE_EX(app, "PostUpdateLambdas", 0xffff0000, (uint64_t)0); PerformanceTimer perfTimer("postUpdateLambdas"); std::unique_lock guard(_postUpdateLambdasLock); - for (auto& iter : _postUpdateLambdas) { - iter.second(); + if (qApp->shouldPaint()) { + for (auto& iter : _postUpdateLambdas) { + iter.second(); + } } _postUpdateLambdas.clear(); }