diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 7922da8af4..7db06f12c0 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -443,13 +443,8 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, - viewFrustumChanged, - boundaryLevelAdjust, octreeSizeScale, - nodeData->getLastTimeBagEmpty(), - isFullScene, &nodeData->stats, _myServer->getJurisdiction(), - &nodeData->extraEncodeData, - nodeData->getUsesFrustum(), - nodeData); + viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, + isFullScene, _myServer->getJurisdiction(), nodeData); nodeData->copyCurrentViewFrustum(params.viewFrustum); if (viewFrustumChanged) { nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); diff --git a/cmake/macros/SetupHifiClientServerPlugin.cmake b/cmake/macros/SetupHifiClientServerPlugin.cmake index 0ce7796756..37b000efb5 100644 --- a/cmake/macros/SetupHifiClientServerPlugin.cmake +++ b/cmake/macros/SetupHifiClientServerPlugin.cmake @@ -6,56 +6,60 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN) - set(${TARGET_NAME}_SHARED 1) - setup_hifi_library(${ARGV}) - if (NOT DEFINED SERVER_ONLY) - add_dependencies(interface ${TARGET_NAME}) - endif() - set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins") + set(${TARGET_NAME}_SHARED 1) + setup_hifi_library(${ARGV}) - if (APPLE) - set(CLIENT_PLUGIN_PATH "${INTERFACE_BUNDLE_NAME}.app/Contents/PlugIns") - set(SERVER_PLUGIN_PATH "plugins") - else() - set(CLIENT_PLUGIN_PATH "plugins") - set(SERVER_PLUGIN_PATH "plugins") - endif() + if (NOT DEFINED SERVER_ONLY) + add_dependencies(interface ${TARGET_NAME}) + endif() - if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles") - set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${CLIENT_PLUGIN_PATH}/") - set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/${SERVER_PLUGIN_PATH}/") - elseif (APPLE) - set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$/${CLIENT_PLUGIN_PATH}/") - set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$/${SERVER_PLUGIN_PATH}/") - else() - set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$/${CLIENT_PLUGIN_PATH}/") - set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$/${SERVER_PLUGIN_PATH}/") - endif() + add_dependencies(assignment-client ${TARGET_NAME}) - # create the destination for the client plugin binaries - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E make_directory - ${CLIENT_PLUGIN_FULL_PATH} - ) - # copy the client plugin binaries - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy - "$" - ${CLIENT_PLUGIN_FULL_PATH} - ) + set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins") - # create the destination for the server plugin binaries - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E make_directory - ${SERVER_PLUGIN_FULL_PATH} - ) - # copy the server plugin binaries - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy - "$" - ${SERVER_PLUGIN_FULL_PATH} - ) + if (APPLE) + set(CLIENT_PLUGIN_PATH "${INTERFACE_BUNDLE_NAME}.app/Contents/PlugIns") + set(SERVER_PLUGIN_PATH "plugins") + else() + set(CLIENT_PLUGIN_PATH "plugins") + set(SERVER_PLUGIN_PATH "plugins") + endif() + + if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles") + set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${CLIENT_PLUGIN_PATH}/") + set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/${SERVER_PLUGIN_PATH}/") + elseif (APPLE) + set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$/${CLIENT_PLUGIN_PATH}/") + set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$/${SERVER_PLUGIN_PATH}/") + else() + set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$/${CLIENT_PLUGIN_PATH}/") + set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$/${SERVER_PLUGIN_PATH}/") + endif() + + # create the destination for the client plugin binaries + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory + ${CLIENT_PLUGIN_FULL_PATH} + ) + # copy the client plugin binaries + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + ${CLIENT_PLUGIN_FULL_PATH} + ) + + # create the destination for the server plugin binaries + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory + ${SERVER_PLUGIN_FULL_PATH} + ) + # copy the server plugin binaries + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + ${SERVER_PLUGIN_FULL_PATH} + ) endmacro() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 726aa7ef84..ac44be18f2 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -192,7 +192,7 @@ link_hifi_libraries( shared octree ktx gpu gl gpu-gl procedural model render recording fbx networking model-networking entities avatars audio audio-client animation script-engine physics - render-utils entities-renderer ui auto-updater + render-utils entities-renderer avatars-renderer ui auto-updater controllers plugins ui-plugins display-plugins input-plugins ${NON_ANDROID_LIBRARIES} diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 78e5edebb5..a37f5be595 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -66,7 +66,11 @@ TabletModalWindow { HifiConstants { id: hifi } onCanceled: { - loginDialogRoot.Stack.view.pop() + if (loginDialogRoot.Stack.view !== null) { + loginDialogRoot.Stack.view.pop() + } else { + Tablet.getTablet("com.highfidelity.interface.tablet.system").gotoHomeScreen(); + } } LoginDialog { diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 3343cec26f..554df82d9e 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -81,6 +81,25 @@ Item { anchors.fill: parent; visible: userImage.status != Image.Ready; } + StateImage { + id: infoHoverImage; + visible: false; + imageURL: "../../images/info-icon-2-state.svg"; + size: 32; + buttonState: 1; + anchors.centerIn: parent; + } + MouseArea { + anchors.fill: parent + enabled: (selected || isMyCard) && activeTab == "nearbyTab"; + hoverEnabled: enabled + onClicked: { + userInfoViewer.url = defaultBaseUrl + "/users/" + userName; + userInfoViewer.visible = true; + } + onEntered: infoHoverImage.visible = true; + onExited: infoHoverImage.visible = false; + } } // Colored border around avatarImage @@ -302,7 +321,7 @@ Item { height: usernameTextPixelSize + 4 // Anchors anchors.top: isMyCard ? myDisplayName.bottom : pal.activeTab == "nearbyTab" ? displayNameContainer.bottom : undefined //(parent.height - displayNameTextPixelSize/2)); - anchors.verticalCenter: pal.activeTab == "connectionsTab" ? avatarImage.verticalCenter : undefined + anchors.verticalCenter: pal.activeTab == "connectionsTab" && !isMyCard ? avatarImage.verticalCenter : undefined anchors.left: avatarImage.right; anchors.leftMargin: avatarImage.visible ? 5 : 0; anchors.rightMargin: 5; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index d785a8582c..1e72d71b18 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -910,17 +910,21 @@ Rectangle { color: hifi.colors.darkGray wrapMode: Text.WordWrap textFormat: Text.StyledText; + property string hmdMountedInstructions: + "1. Put your hand out onto their hand and squeeze your controller's grip button on its side.
" + + "2. Once the other person puts their hand onto yours, you'll see your connection form.
" + + "3. After about 3 seconds, you're connected!" + property string hmdNotMountedInstructions: + "1. Press and hold the 'x' key to extend your arm.
" + + "2. Once the other person puts their hand onto yours, you'll see your connection form.
" + + "3. After about 3 seconds, you're connected!"; + property string notLoggedInInstructions: "You must be logged into your High Fidelity account to make connections.
" + property string instructions: + "When you meet someone you want to remember later, you can connect with a handshake:

" // Text - text: HMD.isMounted ? - "When you meet someone you want to remember later, you can connect with a handshake:

" + - "1. Put your hand out onto their hand and squeeze your controller's grip button on its side.
" + - "2. Once the other person puts their hand onto yours, you'll see your connection form.
" + - "3. After about 3 seconds, you're connected!" - : - "When you meet someone you want to remember later, you can connect with a handshake:

" + - "1. Press and hold the 'x' key to extend your arm.
" + - "2. Once the other person puts their hand onto yours, you'll see your connection form.
" + - "3. After about 3 seconds, you're connected!"; + text: + Account.isLoggedIn() ? ( HMD.mounted ? instructions + hmdMountedInstructions : instructions + hmdNotMountedInstructions) + : ( HMD.mounted ? notLoggedInInstructions + instructions + hmdMountedInstructions : notLoggedInInstructions + instructions + hmdNotMountedInstructions) } } diff --git a/interface/resources/qml/hifi/TabletTextButton.qml b/interface/resources/qml/hifi/TabletTextButton.qml index 12e53eb217..e5ff1d381d 100644 --- a/interface/resources/qml/hifi/TabletTextButton.qml +++ b/interface/resources/qml/hifi/TabletTextButton.qml @@ -16,6 +16,7 @@ Rectangle { property alias text: label.text property alias pixelSize: label.font.pixelSize; property bool selected: false + property bool hovered: false property int spacing: 2 property var action: function () {} property string highlightColor: hifi.colors.blueHighlight; @@ -37,14 +38,14 @@ Rectangle { Rectangle { id: indicator width: parent.width - height: 3 + height: selected ? 3 : 1 anchors { left: parent.left right: parent.right bottom: parent.bottom } color: hifi.colors.blueHighlight - visible: parent.selected + visible: parent.selected || hovered } MouseArea { @@ -53,6 +54,8 @@ Rectangle { acceptedButtons: Qt.LeftButton; onClicked: action(parent); hoverEnabled: true; + onEntered: hovered = true + onExited: hovered = false } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 39892f27a4..356ff92664 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -255,7 +255,7 @@ StackView { TabletTextButton { id: allTab; text: "ALL"; - property string includeActions: 'snapshot, concurrency'; + property string includeActions: 'snapshot,concurrency'; selected: allTab === selectedTab; action: tabSelect; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1717039423..6562698452 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1802,10 +1802,11 @@ Application::~Application() { _physicsEngine->setCharacterController(nullptr); // remove avatars from physics engine - DependencyManager::get()->clearAllAvatars(); + DependencyManager::get()->clearOtherAvatars(); VectorOfMotionStates motionStates; DependencyManager::get()->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); + DependencyManager::get()->deleteAllAvatars(); DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b6ec45b308..083b513a31 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -193,6 +193,9 @@ void Avatar::animateScaleChanges(float deltaTime) { } setScale(glm::vec3(animatedScale)); // avatar scale is uniform + // flag the joints as having changed for force update to RenderItem + _hasNewJointData = true; + // TODO: rebuilding the shape constantly is somehwat expensive. // We should only rebuild after significant change. rebuildCollisionShape(); @@ -200,8 +203,12 @@ void Avatar::animateScaleChanges(float deltaTime) { } void Avatar::setTargetScale(float targetScale) { - AvatarData::setTargetScale(targetScale); - _isAnimatingScale = true; + float newValue = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + if (_targetScale != newValue) { + _targetScale = newValue; + _scaleChanged = usecTimestampNow(); + _isAnimatingScale = true; + } } void Avatar::updateAvatarEntities() { @@ -476,7 +483,7 @@ static TextRenderer3D* textRenderer(TextRendererType type) { return displayNameRenderer; } -bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr scene, render::Transaction& transaction) { +void Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr scene, render::Transaction& transaction) { auto avatarPayload = new render::Payload(self); auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload); _renderItemID = scene->allocateID(); @@ -486,9 +493,6 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, transaction); } - - _inScene = true; - return true; } void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr scene, render::Transaction& transaction) { @@ -498,7 +502,6 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrremoveFromScene(scene, transaction); } - _inScene = false; } void Avatar::updateRenderItem(render::Transaction& transaction) { @@ -933,6 +936,21 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const { return translation; } +glm::quat Avatar::getAbsoluteDefaultJointRotationInObjectFrame(int index) const { + glm::quat rotation; + auto rig = _skeletonModel->getRig(); + glm::quat rot = rig->getAnimSkeleton()->getAbsoluteDefaultPose(index).rot(); + return Quaternions::Y_180 * rot; +} + +glm::vec3 Avatar::getAbsoluteDefaultJointTranslationInObjectFrame(int index) const { + glm::vec3 translation; + auto rig = _skeletonModel->getRig(); + glm::vec3 trans = rig->getAnimSkeleton()->getAbsoluteDefaultPose(index).trans(); + glm::mat4 y180Mat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); + return transformPoint(y180Mat * rig->getGeometryToRigTransform(), trans); +} + glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { if (index < 0) { index += numeric_limits::max() + 1; // 65536 @@ -1435,7 +1453,7 @@ void Avatar::addToScene(AvatarSharedPointer myHandle) { } } void Avatar::ensureInScene(AvatarSharedPointer self) { - if (!_inScene) { + if (!render::Item::isValidID(_renderItemID)) { addToScene(self); } } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 7fadf96e11..3e55bedc38 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -81,7 +81,7 @@ public: virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition); - bool addToScene(AvatarSharedPointer self, std::shared_ptr scene, + void addToScene(AvatarSharedPointer self, std::shared_ptr scene, render::Transaction& transaction); void removeFromScene(AvatarSharedPointer self, std::shared_ptr scene, @@ -121,6 +121,26 @@ public: Q_INVOKABLE virtual glm::quat getDefaultJointRotation(int index) const; Q_INVOKABLE virtual glm::vec3 getDefaultJointTranslation(int index) const; + /**jsdoc + * Provides read only access to the default joint rotations in avatar coordinates. + * The default pose of the avatar is defined by the position and orientation of all bones + * in the avatar's model file. Typically this is a t-pose. + * @function Avatar.getAbsoluteDefaultJointRotationInObjectFrame + * @param index {number} index number + * @returns {Quat} The rotation of this joint in avatar coordinates. + */ + Q_INVOKABLE virtual glm::quat getAbsoluteDefaultJointRotationInObjectFrame(int index) const; + + /**jsdoc + * Provides read only access to the default joint translations in avatar coordinates. + * The default pose of the avatar is defined by the position and orientation of all bones + * in the avatar's model file. Typically this is a t-pose. + * @function Avatar.getAbsoluteDefaultJointTranslationInObjectFrame + * @param index {number} index number + * @returns {Vec3} The position of this joint in avatar coordinates. + */ + Q_INVOKABLE virtual glm::vec3 getAbsoluteDefaultJointTranslationInObjectFrame(int index) const; + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } @@ -285,6 +305,7 @@ protected: void addToScene(AvatarSharedPointer self); void ensureInScene(AvatarSharedPointer self); + bool isInScene() const { return render::Item::isValidID(_renderItemID); } // Some rate tracking support RateCounter<> _simulationRate; @@ -310,7 +331,6 @@ private: int _nameRectGeometryID { 0 }; bool _initialized; bool _isLookAtTarget { false }; - bool _inScene { false }; bool _isAnimatingScale { false }; float getBoundingRadius() const; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 48914908c6..f1170a7085 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -68,7 +68,7 @@ void AvatarManager::registerMetaTypes(QScriptEngine* engine) { } AvatarManager::AvatarManager(QObject* parent) : - _avatarFades(), + _avatarsToFade(), _myAvatar(std::make_shared(std::make_shared())) { // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar @@ -100,15 +100,16 @@ void AvatarManager::init() { _avatarHash.insert(MY_AVATAR_KEY, _myAvatar); } + _shouldRender = DependencyManager::get()->shouldRenderAvatars(); connect(DependencyManager::get().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged, this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection); - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - if (DependencyManager::get()->shouldRenderAvatars()) { + if (_shouldRender) { + render::ScenePointer scene = qApp->getMain3DScene(); + render::Transaction transaction; _myAvatar->addToScene(_myAvatar, scene, transaction); + scene->enqueueTransaction(transaction); } - scene->enqueueTransaction(transaction); } void AvatarManager::updateMyAvatar(float deltaTime) { @@ -151,7 +152,7 @@ float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QStri void AvatarManager::updateOtherAvatars(float deltaTime) { // lock the hash for read to check the size QReadLocker lock(&_hashLock); - if (_avatarHash.size() < 2 && _avatarFades.isEmpty()) { + if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { return; } lock.unlock(); @@ -181,30 +182,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // DO NOT update or fade out uninitialized Avatars return true; // ignore it } - if (avatar->shouldDie()) { - removeAvatar(avatar->getID()); - return true; // ignore it - } - if (avatar->isDead()) { - return true; // ignore it - } - return false; }); - render::Transaction transaction; uint64_t startTime = usecTimestampNow(); const uint64_t UPDATE_BUDGET = 2000; // usec uint64_t updateExpiry = startTime + UPDATE_BUDGET; - int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; + + render::Transaction transaction; while (!sortedAvatars.empty()) { const AvatarPriority& sortData = sortedAvatars.top(); const auto& avatar = std::static_pointer_cast(sortData.avatar); // for ALL avatars... - avatar->ensureInScene(avatar); + if (_shouldRender) { + avatar->ensureInScene(avatar); + } if (!avatar->getMotionState()) { ShapeInfo shapeInfo; avatar->computeShapeInfo(shapeInfo); @@ -218,6 +213,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } } avatar->animateScaleChanges(deltaTime); + if (avatar->shouldDie()) { + avatar->die(); + removeAvatar(avatar->getID()); + } const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; uint64_t now = usecTimestampNow(); @@ -259,10 +258,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { sortedAvatars.pop(); } + if (_shouldRender) { + if (!_avatarsToFade.empty()) { + QReadLocker lock(&_hashLock); + QVector::iterator itr = _avatarsToFade.begin(); + while (itr != _avatarsToFade.end() && usecTimestampNow() > updateExpiry) { + auto avatar = std::static_pointer_cast(*itr); + avatar->animateScaleChanges(deltaTime); + avatar->simulate(deltaTime, true); + avatar->updateRenderItem(transaction); + ++itr; + } + } + qApp->getMain3DScene()->enqueueTransaction(transaction); + } + _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC; _numAvatarsUpdated = numAvatarsUpdated; _numAvatarsNotUpdated = numAVatarsNotUpdated; - qApp->getMain3DScene()->enqueueTransaction(transaction); simulateAvatarFades(deltaTime); } @@ -277,54 +290,76 @@ void AvatarManager::postUpdate(float deltaTime) { } void AvatarManager::simulateAvatarFades(float deltaTime) { - QVector::iterator fadingIterator = _avatarFades.begin(); + if (_avatarsToFade.empty()) { + return; + } const float SHRINK_RATE = 0.15f; const float MIN_FADE_SCALE = MIN_AVATAR_SCALE; - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - while (fadingIterator != _avatarFades.end()) { - auto avatar = std::static_pointer_cast(*fadingIterator); + QReadLocker locker(&_hashLock); + QVector::iterator itr = _avatarsToFade.begin(); + while (itr != _avatarsToFade.end()) { + auto avatar = std::static_pointer_cast(*itr); avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE); avatar->animateScaleChanges(deltaTime); if (avatar->getTargetScale() <= MIN_FADE_SCALE) { - avatar->removeFromScene(*fadingIterator, scene, transaction); - // only remove from _avatarFades if we're sure its motionState has been removed from PhysicsEngine + // fading to zero is such a rare event we push unique transaction for each one + if (avatar->isInScene()) { + render::ScenePointer scene = qApp->getMain3DScene(); + render::Transaction transaction; + avatar->removeFromScene(*itr, scene, transaction); + if (scene) { + scene->enqueueTransaction(transaction); + } + } + + // only remove from _avatarsToFade if we're sure its motionState has been removed from PhysicsEngine if (_motionStatesToRemoveFromPhysics.empty()) { - fadingIterator = _avatarFades.erase(fadingIterator); + itr = _avatarsToFade.erase(itr); } else { - ++fadingIterator; + ++itr; } } else { const bool inView = true; // HACK avatar->simulate(deltaTime, inView); - ++fadingIterator; + ++itr; } } - scene->enqueueTransaction(transaction); } AvatarSharedPointer AvatarManager::newSharedAvatar() { return std::make_shared(std::make_shared()); } -AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { - auto newAvatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer); - auto rawRenderableAvatar = std::static_pointer_cast(newAvatar); - - rawRenderableAvatar->addToScene(rawRenderableAvatar); - - return newAvatar; -} - -// virtual -void AvatarManager::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { - QWriteLocker locker(&_hashLock); - - auto removedAvatar = _avatarHash.take(sessionUUID); - if (removedAvatar) { - handleRemovedAvatar(removedAvatar, removalReason); +void AvatarManager::processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode) { + PerformanceTimer perfTimer("receiveAvatar"); + // enumerate over all of the avatars in this packet + // only add them if mixerWeakPointer points to something (meaning that mixer is still around) + while (message->getBytesLeftToRead()) { + AvatarSharedPointer avatarData = parseAvatarData(message, sendingNode); + if (avatarData) { + auto avatar = std::static_pointer_cast(avatarData); + if (avatar->isInScene()) { + if (!_shouldRender) { + // rare transition so we process the transaction immediately + render::ScenePointer scene = qApp->getMain3DScene(); + if (scene) { + render::Transaction transaction; + avatar->removeFromScene(avatar, scene, transaction); + scene->enqueueTransaction(transaction); + } + } + } else if (_shouldRender) { + // very rare transition so we process the transaction immediately + render::ScenePointer scene = qApp->getMain3DScene(); + if (scene) { + render::Transaction transaction; + avatar->addToScene(avatar, scene, transaction); + scene->enqueueTransaction(transaction); + } + } + } } } @@ -353,35 +388,46 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar DependencyManager::get()->removeFromIgnoreMuteSets(avatar->getSessionUUID()); DependencyManager::get()->avatarDisconnected(avatar->getSessionUUID()); } - _avatarFades.push_back(removedAvatar); + _avatarsToFade.push_back(removedAvatar); } void AvatarManager::clearOtherAvatars() { - // clear any avatars that came from an avatar-mixer - QWriteLocker locker(&_hashLock); + // Remove other avatars from the world but don't actually remove them from _avatarHash + // each will either be removed on timeout or will re-added to the world on receipt of update. + render::ScenePointer scene = qApp->getMain3DScene(); + render::Transaction transaction; + QReadLocker locker(&_hashLock); AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast(avatarIterator.value()); - if (avatar == _myAvatar || !avatar->isInitialized()) { - // don't remove myAvatar or uninitialized avatars from the list - ++avatarIterator; - } else { - auto removedAvatar = avatarIterator.value(); - avatarIterator = _avatarHash.erase(avatarIterator); - - handleRemovedAvatar(removedAvatar); + if (avatar != _myAvatar) { + if (avatar->isInScene()) { + avatar->removeFromScene(avatar, scene, transaction); + } + AvatarMotionState* motionState = avatar->getMotionState(); + if (motionState) { + _motionStatesThatMightUpdate.remove(motionState); + _motionStatesToAddToPhysics.remove(motionState); + _motionStatesToRemoveFromPhysics.push_back(motionState); + } } + ++avatarIterator; + } + if (scene) { + scene->enqueueTransaction(transaction); } _myAvatar->clearLookAtTargetAvatar(); } -void AvatarManager::clearAllAvatars() { - clearOtherAvatars(); - - QWriteLocker locker(&_hashLock); - - handleRemovedAvatar(_myAvatar); +void AvatarManager::deleteAllAvatars() { + QReadLocker locker(&_hashLock); + AvatarHash::iterator avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + auto avatar = std::static_pointer_cast(avatarIterator.value()); + avatarIterator = _avatarHash.erase(avatarIterator); + avatar->die(); + } } void AvatarManager::setLocalLights(const QVector& localLights) { @@ -475,26 +521,25 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents } void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) { - if (DependencyManager::get()->shouldRenderAvatars()) { + _shouldRender = shouldRenderAvatars; + render::ScenePointer scene = qApp->getMain3DScene(); + render::Transaction transaction; + if (_shouldRender) { for (auto avatarData : _avatarHash) { auto avatar = std::static_pointer_cast(avatarData); - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; avatar->addToScene(avatar, scene, transaction); - scene->enqueueTransaction(transaction); } } else { for (auto avatarData : _avatarHash) { auto avatar = std::static_pointer_cast(avatarData); - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; avatar->removeFromScene(avatar, scene, transaction); - scene->enqueueTransaction(transaction); } } + if (scene) { + scene->enqueueTransaction(transaction); + } } - AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) const { if (sessionID == AVATAR_SELF_ID || sessionID == _myAvatar->getSessionUUID()) { return _myAvatar; diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index b94f9e6a96..c8229115bd 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -53,7 +53,7 @@ public: void postUpdate(float deltaTime); void clearOtherAvatars(); - void clearAllAvatars(); + void deleteAllAvatars(); bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; } @@ -91,8 +91,8 @@ public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void updateAvatarRenderStatus(bool shouldRenderAvatars); -private slots: - virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; +protected slots: + void processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode) override; private: explicit AvatarManager(QObject* parent = 0); @@ -100,12 +100,15 @@ private: void simulateAvatarFades(float deltaTime); - // virtual overrides - virtual AvatarSharedPointer newSharedAvatar() override; - virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) override; - virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + AvatarSharedPointer newSharedAvatar() override; + void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + + QVector _avatarsToFade; + + SetOfAvatarMotionStates _motionStatesThatMightUpdate; + VectorOfMotionStates _motionStatesToRemoveFromPhysics; + SetOfMotionStates _motionStatesToAddToPhysics; - QVector _avatarFades; std::shared_ptr _myAvatar; quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate. @@ -115,14 +118,11 @@ private: std::list> _collisionInjectors; - SetOfAvatarMotionStates _motionStatesThatMightUpdate; - SetOfMotionStates _motionStatesToAddToPhysics; - VectorOfMotionStates _motionStatesToRemoveFromPhysics; - RateCounter<> _myAvatarSendRate; int _numAvatarsUpdated { 0 }; int _numAvatarsNotUpdated { 0 }; float _avatarSimulationTime { 0.0f }; + bool _shouldRender { true }; }; Q_DECLARE_METATYPE(AvatarManager::LocalLight) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d1edf9d44e..2a616a7a2a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -54,6 +54,7 @@ #include "DebugDraw.h" #include "EntityEditPacketSender.h" #include "MovingEntitiesOperator.h" +#include "SceneScriptingInterface.h" using namespace std; @@ -1634,7 +1635,7 @@ void MyAvatar::postUpdate(float deltaTime) { Avatar::postUpdate(deltaTime); render::ScenePointer scene = qApp->getMain3DScene(); - if (_skeletonModel->initWhenReady(scene)) { + if (DependencyManager::get()->shouldRenderAvatars() && _skeletonModel->initWhenReady(scene)) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index 911899f5a9..832ab8678c 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -109,7 +109,7 @@ void AnimVariantMap::animVariantMapFromScriptValue(const QScriptValue& source) { if (z.isNumber()) { QScriptValue w = value.property("w"); if (w.isNumber()) { - set(property.name(), glm::quat(x.toNumber(), y.toNumber(), z.toNumber(), w.toNumber())); + set(property.name(), glm::quat(w.toNumber(), x.toNumber(), y.toNumber(), z.toNumber())); } else { set(property.name(), glm::vec3(x.toNumber(), y.toNumber(), z.toNumber())); } diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 23c9d1d0b5..6594482085 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -74,7 +74,7 @@ void AnimationReader::run() { // Parse the FBX directly from the QNetworkReply FBXGeometry::Pointer fbxgeo; if (_url.path().toLower().endsWith(".fbx")) { - fbxgeo.reset(readFBX(_data, QVariantHash(), _url)); + fbxgeo.reset(readFBX(_data, QVariantHash(), _url.path())); } else { QString errorStr("usupported format"); emit onError(299, errorStr); diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt new file mode 100644 index 0000000000..bee2cb31d6 --- /dev/null +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET_NAME avatars-renderer) +AUTOSCRIBE_SHADER_LIB(gpu model render render-utils) +setup_hifi_library(Widgets Network Script) +link_hifi_libraries(shared gpu model animation physics model-networking script-engine render render-utils) + +target_bullet() diff --git a/libraries/avatars-renderer/src/AvatarsRendererLogging.cpp b/libraries/avatars-renderer/src/AvatarsRendererLogging.cpp new file mode 100644 index 0000000000..2804df1b7a --- /dev/null +++ b/libraries/avatars-renderer/src/AvatarsRendererLogging.cpp @@ -0,0 +1,11 @@ +// +// Created by Bradley Austin Davis on 2016/12/06 +// Copyright 2013-2016 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 "AvatarsRendererLogging.h" + +Q_LOGGING_CATEGORY(avatars_renderer, "hifi.avatars.rendering") diff --git a/libraries/avatars-renderer/src/AvatarsRendererLogging.h b/libraries/avatars-renderer/src/AvatarsRendererLogging.h new file mode 100644 index 0000000000..a1cfd8d9e4 --- /dev/null +++ b/libraries/avatars-renderer/src/AvatarsRendererLogging.h @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis on 2016/12/06 +// Copyright 2013-2016 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_AvatarsRendererLogging_h +#define hifi_AvatarsRendererLogging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(avatars_renderer) + +#endif // hifi_AvatarsRendererLogging_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a1ea103edb..23aa0cd811 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -123,6 +123,7 @@ void AvatarData::setTargetScale(float targetScale) { if (_targetScale != newValue) { _targetScale = newValue; _scaleChanged = usecTimestampNow(); + _avatarScaleChanged = _scaleChanged; } } diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 48e5d673c9..8708030190 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -80,13 +80,10 @@ AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWe AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { QWriteLocker locker(&_hashLock); - auto avatar = _avatarHash.value(sessionUUID); - if (!avatar) { avatar = addAvatar(sessionUUID, mixerWeakPointer); } - return avatar; } @@ -103,27 +100,33 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess // enumerate over all of the avatars in this packet // only add them if mixerWeakPointer points to something (meaning that mixer is still around) while (message->getBytesLeftToRead()) { - QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + parseAvatarData(message, sendingNode); + } +} - int positionBeforeRead = message->getPosition(); +AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode) { + QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead()); + int positionBeforeRead = message->getPosition(); - // make sure this isn't our own avatar data or for a previously ignored node - auto nodeList = DependencyManager::get(); + QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead()); - if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) { - auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); + // make sure this isn't our own avatar data or for a previously ignored node + auto nodeList = DependencyManager::get(); - // have the matching (or new) avatar parse the data from the packet - int bytesRead = avatar->parseDataFromBuffer(byteArray); - message->seek(positionBeforeRead + bytesRead); - } else { - // create a dummy AvatarData class to throw this data on the ground - AvatarData dummyData; - int bytesRead = dummyData.parseDataFromBuffer(byteArray); - message->seek(positionBeforeRead + bytesRead); - } + if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) { + auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); + + // have the matching (or new) avatar parse the data from the packet + int bytesRead = avatar->parseDataFromBuffer(byteArray); + message->seek(positionBeforeRead + bytesRead); + return avatar; + } else { + // create a dummy AvatarData class to throw this data on the ground + AvatarData dummyData; + int bytesRead = dummyData.parseDataFromBuffer(byteArray); + message->seek(positionBeforeRead + bytesRead); + return std::make_shared(); } } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 104ac83261..346cd36b60 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -49,11 +49,11 @@ signals: public slots: bool isAvatarInRange(const glm::vec3 & position, const float range); - -private slots: + +protected slots: void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID); - - void processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode); + + virtual void processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode); void processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode); void processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode); void processExitingSpaceBubble(QSharedPointer message, SharedNodePointer sendingNode); @@ -61,12 +61,13 @@ private slots: protected: AvatarHashMap(); + virtual AvatarSharedPointer parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode); virtual AvatarSharedPointer newSharedAvatar(); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); - + virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 91a4fc2ff9..99669ef3bf 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -607,11 +608,19 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori face, surfaceNormal, extraInfo, precisionPicking); } +void RenderableModelEntityItem::getCollisionGeometryResource() { + QUrl hullURL(getCompoundShapeURL()); + QUrlQuery queryArgs(hullURL); + queryArgs.addQueryItem("collision-hull", ""); + hullURL.setQuery(queryArgs); + _compoundShapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); +} + void RenderableModelEntityItem::setShapeType(ShapeType type) { ModelEntityItem::setShapeType(type); if (getShapeType() == SHAPE_TYPE_COMPOUND) { if (!_compoundShapeResource && !getCompoundShapeURL().isEmpty()) { - _compoundShapeResource = DependencyManager::get()->getGeometryResource(getCompoundShapeURL()); + getCollisionGeometryResource(); } } else if (_compoundShapeResource && !getCompoundShapeURL().isEmpty()) { // the compoundURL has been set but the shapeType does not agree @@ -620,6 +629,10 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) { } void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) { + // because the caching system only allows one Geometry per url, and because this url might also be used + // as a visual model, we need to change this url in some way. We add a "collision-hull" query-arg so it + // will end up in a different hash-key in ResourceCache. TODO: It would be better to use the same URL and + // parse it twice. auto currentCompoundShapeURL = getCompoundShapeURL(); ModelEntityItem::setCompoundShapeURL(url); @@ -629,7 +642,7 @@ void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) { QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); } if (getShapeType() == SHAPE_TYPE_COMPOUND) { - _compoundShapeResource = DependencyManager::get()->getGeometryResource(url); + getCollisionGeometryResource(); } } } @@ -661,7 +674,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { } return true; } else if (!getCompoundShapeURL().isEmpty()) { - _compoundShapeResource = DependencyManager::get()->getGeometryResource(getCompoundShapeURL()); + getCollisionGeometryResource(); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index c770e85089..7eab5172d8 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -108,6 +108,7 @@ private: QVariantMap parseTexturesToMap(QString textures); void remapTextures(); + void getCollisionGeometryResource(); GeometryResource::Pointer _compoundShapeResource; ModelPointer _model = nullptr; bool _needsInitialSimulation = true; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 766ebfb4dc..1ce916de66 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -126,7 +126,7 @@ public: void markAsChangedOnServer(); quint64 getLastChangedOnServer() const; - // TODO: eventually only include properties changed since the params.lastQuerySent time + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 755c19e625..98287541e3 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -51,7 +51,10 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons qCDebug(entities) << "EntityTreeElement::debugExtraEncodeData()... "; qCDebug(entities) << " element:" << _cube; - OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; + auto entityNodeData = static_cast(params.nodeData); + assert(entityNodeData); + + OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes if (extraEncodeData->contains(this)) { @@ -65,7 +68,11 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) { - OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; + auto entityNodeData = static_cast(params.nodeData); + assert(entityNodeData); + + OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; + assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes // Check to see if this element yet has encode data... if it doesn't create it if (!extraEncodeData->contains(this)) { @@ -93,7 +100,11 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) } bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { - OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; + + auto entityNodeData = static_cast(params.nodeData); + assert(entityNodeData); + + OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes if (extraEncodeData->contains(this)) { @@ -122,7 +133,10 @@ bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamPa } bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const { - OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; + auto entityNodeData = static_cast(params.nodeData); + assert(entityNodeData); + + OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes if (extraEncodeData->contains(this)) { @@ -137,8 +151,12 @@ bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const } void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { - OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; + auto entityNodeData = static_cast(params.nodeData); + assert(entityNodeData); + + OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes + if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData = std::static_pointer_cast((*extraEncodeData)[this]); @@ -161,7 +179,10 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube; } - OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; + auto entityNodeData = static_cast(params.nodeData); + assert(entityNodeData); + + OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes assert(extraEncodeData->contains(this)); @@ -236,8 +257,12 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best... + auto entityNodeData = static_cast(params.nodeData); + Q_ASSERT_X(entityNodeData, "EntityTreeElement::appendElementData", "expected params.nodeData not to be null"); + // first, check the params.extraEncodeData to see if there's any partial re-encode data for this element - OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; + OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData = NULL; bool hadElementExtraData = false; if (extraEncodeData && extraEncodeData->contains(this)) { @@ -285,20 +310,17 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // need to handle the case where our sibling elements need encoding but we don't. if (!entityTreeElementExtraEncodeData->elementCompleted) { - QJsonObject jsonFilters; - auto entityNodeData = static_cast(params.nodeData); - if (entityNodeData) { - // we have an EntityNodeData instance - // so we should assume that means we might have JSON filters to check - jsonFilters = entityNodeData->getJSONParameters(); - } + // we have an EntityNodeData instance + // so we should assume that means we might have JSON filters to check + auto jsonFilters = entityNodeData->getJSONParameters(); + for (uint16_t i = 0; i < _entityItems.size(); i++) { EntityItemPointer entity = _entityItems[i]; bool includeThisEntity = true; - if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastQuerySent) { + if (!params.forceSendScene && entity->getLastChangedOnServer() < entityNodeData->getLastTimeBagEmpty()) { includeThisEntity = false; } @@ -330,7 +352,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // we only check the bounds against our frustum and LOD if the query has asked us to check against the frustum // which can sometimes not be the case when JSON filters are sent - if (params.usesFrustum && (includeThisEntity || params.recurseEverything)) { + if (entityNodeData->getUsesFrustum() && (includeThisEntity || params.recurseEverything)) { // we want to use the maximum possible box for this, so that we don't have to worry about the nuance of // simulation changing what's visible. consider the case where the entity contains an angular velocity diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 753ff1d3c8..c59c4b90e3 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -195,7 +195,7 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_IS_SPOTLIGHT; diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 58cdb1cd7b..4ba58ef9b2 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -131,7 +131,7 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index bb8ae8a21a..bbe0828e57 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -26,7 +26,7 @@ class LineEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.lastQuerySent time + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 911ff224b2..51d9c2ad25 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -160,7 +160,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index e1cb5cd92c..5076a43892 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -29,7 +29,7 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.lastQuerySent time + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 80f7f3b6b8..b1366e96b8 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -469,7 +469,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index 9fe8ac0c8f..c1f6508a76 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -178,7 +178,7 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 0ee9a638de..ed161762fc 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -26,7 +26,7 @@ class PolyLineEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.lastQuerySent time + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 711c3b5625..b5500da798 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -179,7 +179,7 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_VOXEL_VOLUME_SIZE; diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 05b5cb33a6..a5c8910931 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -26,7 +26,7 @@ class PolyVoxEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.lastQuerySent time + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 345d9e54ab..018d8c568a 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -137,7 +137,7 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SHAPE; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index c6b857f6e7..1d8cb50a4b 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -99,7 +99,7 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_TEXT; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index ee421d567a..8db929fa47 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -30,7 +30,7 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.lastQuerySent time + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 5b060cc702..61c6c8d80e 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -84,7 +84,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SOURCE_URL; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 5cd081687f..44c2741db2 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -29,7 +29,7 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.lastQuerySent time + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 26d566a795..1d73902e8d 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -137,7 +137,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.lastQuerySent time +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index dfc18aff80..c868da0954 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -30,7 +30,7 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.lastQuerySent time + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index bcd2be3384..64ee0bc869 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -376,10 +376,10 @@ public: }; bool checkMaterialsHaveTextures(const QHash& materials, - const QHash& textureFilepaths, const QMultiMap& _connectionChildMap) { + const QHash& textureFilenames, const QMultiMap& _connectionChildMap) { foreach (const QString& materialID, materials.keys()) { foreach (const QString& childID, _connectionChildMap.values(materialID)) { - if (textureFilepaths.contains(childID)) { + if (textureFilenames.contains(childID)) { return true; } } @@ -443,48 +443,21 @@ FBXLight extractLight(const FBXNode& object) { return light; } -QByteArray fixedTextureFilepath(QByteArray fbxRelativeFilepath, QUrl url) { - // first setup a QFileInfo for the passed relative filepath, with backslashes replaced by forward slashes - auto fileInfo = QFileInfo { fbxRelativeFilepath.replace("\\", "/") }; +QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { + QString path = QFileInfo(url).path(); + QByteArray filename = filepath; + QFileInfo checkFile(path + "/" + filepath); -#ifndef Q_OS_WIN - // it turns out that absolute windows paths starting with drive letters look like relative paths to QFileInfo on UNIX - // so we add a check for that here to work around it - bool isRelative = fbxRelativeFilepath[1] != ':' && fileInfo.isRelative(); -#else - bool isRelative = fileInfo.isRelative(); -#endif - - if (isRelative) { - // the RelativeFilename pulled from the FBX is already correctly relative - // so simply return this as the filepath to use - return fbxRelativeFilepath; - } else { - // the RelativeFilename pulled from the FBX is an absolute path - - // use the URL to figure out where the FBX is being loaded from - auto filename = fileInfo.fileName(); - - if (url.isLocalFile()) { - // the FBX is being loaded from the local filesystem - - if (fileInfo.exists() && fileInfo.isFile()) { - // found a file at the absolute path in the FBX, return that path - return fbxRelativeFilepath; - } else { - // didn't find a file at the absolute path, assume it is right beside the FBX - // return just the filename as the relative path - return filename.toUtf8(); - } - } else { - // this is a remote file, meaning we can't really do anything with the absolute path to the texture - // so assume it will be right beside the fbx - return filename.toUtf8(); - } + // check if the file exists at the RelativeFilename + if (!(checkFile.exists() && checkFile.isFile())) { + // if not, assume it is in the fbx directory + filename = filename.mid(filename.lastIndexOf('/') + 1); } + + return filename; } -FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QUrl& url) { +FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { const FBXNode& node = _fbxNode; QMap meshes; QHash modelIDsToNames; @@ -860,9 +833,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU const int MODEL_UV_SCALING_MIN_SIZE = 2; const int CROPPING_MIN_SIZE = 4; if (subobject.name == "RelativeFilename" && subobject.properties.length() >= RELATIVE_FILENAME_MIN_SIZE) { - auto filepath = fixedTextureFilepath(subobject.properties.at(0).toByteArray(), url); - + QByteArray filename = subobject.properties.at(0).toByteArray(); + QByteArray filepath = filename.replace('\\', '/'); + filename = fileOnUrl(filepath, url); _textureFilepaths.insert(getID(object.properties), filepath); + _textureFilenames.insert(getID(object.properties), filename); } else if (subobject.name == "TextureName" && subobject.properties.length() >= TEXTURE_NAME_MIN_SIZE) { // trim the name from the timestamp QString name = QString(subobject.properties.at(0).toByteArray()); @@ -955,7 +930,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU QByteArray content; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "RelativeFilename") { - filepath = subobject.properties.at(0).toByteArray(); + filepath= subobject.properties.at(0).toByteArray(); filepath = filepath.replace('\\', '/'); } else if (subobject.name == "Content" && !subobject.properties.isEmpty()) { @@ -1527,7 +1502,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU geometry.materials = _fbxMaterials; // see if any materials have texture children - bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, _textureFilepaths, _connectionChildMap); + bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, _textureFilenames, _connectionChildMap); for (QMap::iterator it = meshes.begin(); it != meshes.end(); it++) { ExtractedMesh& extracted = it.value(); @@ -1572,7 +1547,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU materialIndex++; - } else if (_textureFilepaths.contains(childID)) { + } else if (_textureFilenames.contains(childID)) { FBXTexture texture = getTexture(childID); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { int partTexture = extracted.partMaterialTextures.at(j).second; @@ -1843,13 +1818,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU return geometryPtr; } -FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel); } -FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { FBXReader reader; reader._fbxNode = FBXReader::parseFBX(device); reader._loadLightmaps = loadLightmaps; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index dd746322e9..f73088e7a1 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -268,7 +268,7 @@ class FBXGeometry { public: using Pointer = std::shared_ptr; - QUrl originalURL; + QString originalURL; QString author; QString applicationName; ///< the name of the application that generated the model @@ -330,11 +330,11 @@ Q_DECLARE_METATYPE(FBXGeometry::Pointer) /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QUrl& url = QUrl(), bool loadLightmaps = true, float lightmapLevel = 1.0f); +FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QUrl& url = QUrl(), bool loadLightmaps = true, float lightmapLevel = 1.0f); +FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); class TextureParam { public: @@ -402,17 +402,19 @@ public: FBXNode _fbxNode; static FBXNode parseFBX(QIODevice* device); - FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QUrl& url); + FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QString& url); ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex); QHash meshes; - static void buildModelMesh(FBXMesh& extractedMesh, const QUrl& url); + static void buildModelMesh(FBXMesh& extractedMesh, const QString& url); FBXTexture getTexture(const QString& textureID); QHash _textureNames; // Hashes the original RelativeFilename of textures QHash _textureFilepaths; + // Hashes the place to look for textures, in case they are not inlined + QHash _textureFilenames; // Hashes texture content by filepath, in case they are inlined QHash _textureContent; QHash _textureParams; diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXReader_Material.cpp index 2f63d894fd..ca2ec557b4 100644 --- a/libraries/fbx/src/FBXReader_Material.cpp +++ b/libraries/fbx/src/FBXReader_Material.cpp @@ -85,7 +85,12 @@ FBXTexture FBXReader::getTexture(const QString& textureID) { FBXTexture texture; const QByteArray& filepath = _textureFilepaths.value(textureID); texture.content = _textureContent.value(filepath); - texture.filename = filepath; + + if (texture.content.isEmpty()) { // the content is not inlined + texture.filename = _textureFilenames.value(textureID); + } else { // use supplied filepath for inlined content + texture.filename = filepath; + } texture.name = _textureNames.value(textureID); texture.transform.setIdentity(); @@ -150,7 +155,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { // FBX files generated by 3DSMax have an intermediate texture parent, apparently foreach(const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) { - if (_textureFilepaths.contains(childTextureID)) { + if (_textureFilenames.contains(childTextureID)) { diffuseTexture = getTexture(diffuseTextureID); } } diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index a6d70408ae..4e153dfe3a 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -388,7 +388,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn return data.extracted; } -void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QUrl& url) { +void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*"); unsigned int totalSourceIndices = 0; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index c99b847722..7b46556530 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -302,7 +302,8 @@ QNetworkReply* OBJReader::request(QUrl& url, bool isTest) { } -bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess) { +bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, + float& scaleGuess, bool combineParts) { FaceGroup faces; FBXMesh& mesh = geometry.meshes[0]; mesh.parts.append(FBXMeshPart()); @@ -348,6 +349,9 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi } QByteArray groupName = tokenizer.getDatum(); currentGroup = groupName; + if (!combineParts) { + currentMaterialName = QString("part-") + QString::number(_partCounter++); + } } else if (token == "mtllib" && !_url.isEmpty()) { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { break; @@ -361,7 +365,9 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi } QString nextName = tokenizer.getDatum(); if (nextName != currentMaterialName) { - currentMaterialName = nextName; + if (combineParts) { + currentMaterialName = nextName; + } #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader new current material:" << currentMaterialName; #endif @@ -419,7 +425,7 @@ done: } -FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, const QUrl& url) { +FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); QBuffer buffer { &model }; buffer.open(QIODevice::ReadOnly); @@ -438,7 +444,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, try { // call parseOBJGroup as long as it's returning true. Each successful call will // add a new meshPart to the geometry's single mesh. - while (parseOBJGroup(tokenizer, mapping, geometry, scaleGuess)) {} + while (parseOBJGroup(tokenizer, mapping, geometry, scaleGuess, combineParts)) {} FBXMesh& mesh = geometry.meshes[0]; mesh.meshIndex = 0; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index b4a48c570e..4be5705f9a 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -22,7 +22,7 @@ public: glm::vec3 getVec3(); glm::vec2 getVec2(); float getFloat() { return std::stof((nextToken() != OBJTokenizer::DATUM_TOKEN) ? nullptr : getDatum().data()); } - + private: QIODevice* _device; QByteArray _datum; @@ -73,15 +73,18 @@ public: QHash materials; QNetworkReply* request(QUrl& url, bool isTest); - FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, const QUrl& url = QUrl()); - + FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); + private: QUrl _url; QHash librariesSeen; - bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess); + bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, + float& scaleGuess, bool combineParts); void parseMaterialLibrary(QIODevice* device); bool isValidTexture(const QByteArray &filename); // true if the file exists. TODO?: check content-type header and that it is a supported format. + + int _partCounter { 0 }; }; // What are these utilities doing here? One is used by fbx loading code in VHACD Utils, and the other a general debugging utility. diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 142ea74af4..a5df41e944 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -32,6 +32,7 @@ class GeometryExtra { public: const QVariantHash& mapping; const QUrl& textureBaseUrl; + bool combineParts; }; QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) { @@ -89,7 +90,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { } auto modelCache = DependencyManager::get(); - GeometryExtra extra{ mapping, _textureBaseUrl }; + GeometryExtra extra{ mapping, _textureBaseUrl, false }; // Get the raw GeometryResource _geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast(); @@ -129,8 +130,8 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) { class GeometryReader : public QRunnable { public: GeometryReader(QWeakPointer& resource, const QUrl& url, const QVariantHash& mapping, - const QByteArray& data) : - _resource(resource), _url(url), _mapping(mapping), _data(data) { + const QByteArray& data, bool combineParts) : + _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts) { DependencyManager::get()->incrementStat("PendingProcessing"); } @@ -142,6 +143,7 @@ private: QUrl _url; QVariantHash _mapping; QByteArray _data; + bool _combineParts; }; void GeometryReader::run() { @@ -173,12 +175,12 @@ void GeometryReader::run() { FBXGeometry::Pointer fbxGeometry; if (_url.path().toLower().endsWith(".fbx")) { - fbxGeometry.reset(readFBX(_data, _mapping, _url)); + fbxGeometry.reset(readFBX(_data, _mapping, _url.path())); if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) { throw QString("empty geometry, possibly due to an unsupported FBX version"); } } else if (_url.path().toLower().endsWith(".obj")) { - fbxGeometry.reset(OBJReader().readOBJ(_data, _mapping, _url)); + fbxGeometry.reset(OBJReader().readOBJ(_data, _mapping, _combineParts, _url)); } else { throw QString("unsupported format"); } @@ -209,8 +211,8 @@ void GeometryReader::run() { class GeometryDefinitionResource : public GeometryResource { Q_OBJECT public: - GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) : - GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _mapping(mapping) {} + GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) : + GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _mapping(mapping), _combineParts(combineParts) {} QString getType() const override { return "GeometryDefinition"; } @@ -221,10 +223,11 @@ protected: private: QVariantHash _mapping; + bool _combineParts; }; void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { - QThreadPool::globalInstance()->start(new GeometryReader(_self, _url, _mapping, data)); + QThreadPool::globalInstance()->start(new GeometryReader(_self, _url, _mapping, data, _combineParts)); } void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxGeometry) { @@ -265,7 +268,7 @@ ModelCache::ModelCache() { } QSharedPointer ModelCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { + const void* extra) { Resource* resource = nullptr; if (url.path().toLower().endsWith(".fst")) { resource = new GeometryMappingResource(url); @@ -273,14 +276,30 @@ QSharedPointer ModelCache::createResource(const QUrl& url, const QShar const GeometryExtra* geometryExtra = static_cast(extra); auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl(); - resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl); + bool combineParts = geometryExtra ? geometryExtra->combineParts : true; + resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl, combineParts); } return QSharedPointer(resource, &Resource::deleter); } -GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { - GeometryExtra geometryExtra = { mapping, textureBaseUrl }; +GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, + const QVariantHash& mapping, const QUrl& textureBaseUrl) { + bool combineParts = true; + GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; + GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast(); + if (resource) { + if (resource->isLoaded() && resource->shouldSetTextures()) { + resource->setTextures(); + } + } + return resource; +} + +GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& url, + const QVariantHash& mapping, const QUrl& textureBaseUrl) { + bool combineParts = false; + GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 2cd96a84c7..967897477d 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -136,13 +136,18 @@ class ModelCache : public ResourceCache, public Dependency { public: GeometryResource::Pointer getGeometryResource(const QUrl& url, - const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl()); + const QVariantHash& mapping = QVariantHash(), + const QUrl& textureBaseUrl = QUrl()); + + GeometryResource::Pointer getCollisionGeometryResource(const QUrl& url, + const QVariantHash& mapping = QVariantHash(), + const QUrl& textureBaseUrl = QUrl()); protected: friend class GeometryMappingResource; virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + const void* extra) override; private: ModelCache(); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 0396e0ed94..4031ff8bf7 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -191,7 +191,7 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { result->setObjectName(url.toString()); result->_resource = resource; - if (resource->isLoaded()) { + if (resource->isLoaded() || resource->_failedToLoad) { result->finished(!resource->_failedToLoad); } else { result->_progressConnection = connect( diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 8f1f1baed2..53ccd2c386 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -295,8 +295,8 @@ protected: /// Creates a new resource. virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) = 0; - + const void* extra) = 0; + void addUnusedResource(const QSharedPointer& resource); void removeUnusedResource(const QSharedPointer& resource); diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 58910c66bd..dfc6195f95 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -43,14 +43,15 @@ #include #include +#include "Octree.h" #include "OctreeConstants.h" #include "OctreeElementBag.h" -#include "Octree.h" -#include "OctreeUtils.h" #include "OctreeLogging.h" +#include "OctreeQueryNode.h" +#include "OctreeUtils.h" -QVector PERSIST_EXTENSIONS = {"svo", "json", "json.gz"}; +QVector PERSIST_EXTENSIONS = {"json", "json.gz"}; Octree::Octree(bool shouldReaverage) : _rootElement(NULL), @@ -898,8 +899,16 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element, return bytesWritten; } + // you can't call this without a valid nodeData + auto octreeQueryNode = static_cast(params.nodeData); + if (!octreeQueryNode) { + qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL"); + params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA; + return bytesWritten; + } + // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! - if (params.usesFrustum && !params.recurseEverything && !element->isInView(params.viewFrustum)) { + if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything && !element->isInView(params.viewFrustum)) { params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; return bytesWritten; } @@ -935,9 +944,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element, // record some stats, this is the one element that we won't record below in the recursion function, so we need to // track it here - if (params.stats) { - params.stats->traversed(element); - } + octreeQueryNode->stats.traversed(element); ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully @@ -993,6 +1000,15 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, return bytesAtThisLevel; } + // you can't call this without a valid nodeData + auto octreeQueryNode = static_cast(params.nodeData); + if (!octreeQueryNode) { + qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL"); + params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA; + return bytesAtThisLevel; + } + + // Keep track of how deep we've encoded. currentEncodeLevel++; @@ -1015,15 +1031,13 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside - if (params.usesFrustum && !params.recurseEverything) { + if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything) { float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, params.octreeElementSizeScale); // If we're too far away for our render level, then just return if (element->distanceToCamera(params.viewFrustum) >= boundaryDistance) { - if (params.stats) { - params.stats->skippedDistance(element); - } + octreeQueryNode->stats.skippedDistance(element); params.stopReason = EncodeBitstreamParams::LOD_SKIP; return bytesAtThisLevel; } @@ -1039,9 +1053,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if // we're out of view if (nodeLocationThisView == ViewFrustum::OUTSIDE) { - if (params.stats) { - params.stats->skippedOutOfView(element); - } + octreeQueryNode->stats.skippedOutOfView(element); params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; return bytesAtThisLevel; } @@ -1066,7 +1078,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // as "was in view"... if (wasInView) { float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); + params.octreeElementSizeScale); if (element->distanceToCamera(params.lastViewFrustum) >= boundaryDistance) { // This would have been invisible... but now should be visible (we wouldn't be here otherwise)... wasInView = false; @@ -1077,22 +1089,20 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // If we were previously in the view, then we normally will return out of here and stop recursing. But // if we're in deltaView mode, and this element has changed since it was last sent, then we do // need to send it. - if (wasInView && !(params.deltaView && element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE))) { - if (params.stats) { - params.stats->skippedWasInView(element); - } + if (wasInView && !(params.deltaView && element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))) { + octreeQueryNode->stats.skippedWasInView(element); params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW; return bytesAtThisLevel; } } - // If we're not in delta sending mode, and we weren't asked to do a force send, and the octree element hasn't changed, + // If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed, // then we can also bail early and save bits if (!params.forceSendScene && !params.deltaView && - !element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE)) { - if (params.stats) { - params.stats->skippedNoChange(element); - } + !element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE)) { + + octreeQueryNode->stats.skippedNoChange(element); + params.stopReason = EncodeBitstreamParams::NO_CHANGE; return bytesAtThisLevel; } @@ -1164,8 +1174,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // track stats // must check childElement here, because it could be we got here with no childElement - if (params.stats && childElement) { - params.stats->traversed(childElement); + if (childElement) { + octreeQueryNode->stats.traversed(childElement); } } @@ -1176,7 +1186,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, int originalIndex = indexOfChildren[i]; bool childIsInView = (childElement && - (params.recurseEverything || !params.usesFrustum || + (params.recurseEverything || !octreeQueryNode->getUsesFrustum() || (nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are (nodeLocationThisView == ViewFrustum::INTERSECT && childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view @@ -1184,20 +1194,18 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, if (!childIsInView) { // must check childElement here, because it could be we got here because there was no childElement - if (params.stats && childElement) { - params.stats->skippedOutOfView(childElement); + if (childElement) { + octreeQueryNode->stats.skippedOutOfView(childElement); } } else { // Before we consider this further, let's see if it's in our LOD scope... - float boundaryDistance = params.recurseEverything || !params.usesFrustum ? 1 : + float boundaryDistance = params.recurseEverything || !octreeQueryNode->getUsesFrustum() ? 1 : boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust, params.octreeElementSizeScale); if (!(distancesToChildren[i] < boundaryDistance)) { // don't need to check childElement here, because we can't get here with no childElement - if (params.stats) { - params.stats->skippedDistance(childElement); - } + octreeQueryNode->stats.skippedDistance(childElement); } else { inViewCount++; @@ -1211,20 +1219,18 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, bool childIsOccluded = false; // assume it's not occluded - bool shouldRender = params.recurseEverything || !params.usesFrustum || + bool shouldRender = params.recurseEverything || !octreeQueryNode->getUsesFrustum() || childElement->calculateShouldRender(params.viewFrustum, params.octreeElementSizeScale, params.boundaryLevelAdjust); // track some stats - if (params.stats) { - // don't need to check childElement here, because we can't get here with no childElement - if (!shouldRender && childElement->isLeaf()) { - params.stats->skippedDistance(childElement); - } - // don't need to check childElement here, because we can't get here with no childElement - if (childIsOccluded) { - params.stats->skippedOccluded(childElement); - } + // don't need to check childElement here, because we can't get here with no childElement + if (!shouldRender && childElement->isLeaf()) { + octreeQueryNode->stats.skippedDistance(childElement); + } + // don't need to check childElement here, because we can't get here with no childElement + if (childIsOccluded) { + octreeQueryNode->stats.skippedOccluded(childElement); } // track children with actual color, only if the child wasn't previously in view! @@ -1247,19 +1253,17 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // need to send it. if (!childWasInView || (params.deltaView && - childElement->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE))){ + childElement->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))){ childrenDataBits += (1 << (7 - originalIndex)); inViewWithColorCount++; } else { // otherwise just track stats of the items we discarded // don't need to check childElement here, because we can't get here with no childElement - if (params.stats) { - if (childWasInView) { - params.stats->skippedWasInView(childElement); - } else { - params.stats->skippedNoChange(childElement); - } + if (childWasInView) { + octreeQueryNode->stats.skippedWasInView(childElement); + } else { + octreeQueryNode->stats.skippedNoChange(childElement); } } } @@ -1277,9 +1281,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, assert(continueThisLevel); // since we used reserved bits, this really shouldn't fail bytesAtThisLevel += sizeof(childrenDataBits); // keep track of byte count - if (params.stats) { - params.stats->colorBitsWritten(); // really data bits not just color bits - } + + octreeQueryNode->stats.colorBitsWritten(); // really data bits not just color bits // NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data element->initializeExtraEncodeData(params); @@ -1349,8 +1352,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child // don't need to check childElement here, because we can't get here with no childElement - if (params.stats && (childAppendState != OctreeElement::NONE)) { - params.stats->colorSent(childElement); + if (childAppendState != OctreeElement::NONE) { + octreeQueryNode->stats.colorSent(childElement); } } } @@ -1377,9 +1380,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits); if (continueThisLevel) { bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count - if (params.stats) { - params.stats->existsBitsWritten(); - } + + octreeQueryNode->stats.existsBitsWritten(); } else { qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInTreeBits"; qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; @@ -1392,9 +1394,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits); if (continueThisLevel) { bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count - if (params.stats) { - params.stats->existsInPacketBitsWritten(); - } + + octreeQueryNode->stats.existsInPacketBitsWritten(); } else { qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInPacketBits"; qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; @@ -1451,7 +1452,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep // recursing, by returning TRUE in recurseChildrenWithData(). - if (params.recurseEverything || !params.usesFrustum + if (params.recurseEverything || !octreeQueryNode->getUsesFrustum() || recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) { // Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this @@ -1502,8 +1503,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } // If this is the last of the child exists bits, then we're actually be rolling out the entire tree - if (params.stats && childrenExistInPacketBits == 0) { - params.stats->childBitsRemoved(params.includeExistsBits); + if (childrenExistInPacketBits == 0) { + octreeQueryNode->stats.childBitsRemoved(params.includeExistsBits); } if (!continueThisLevel) { @@ -1558,9 +1559,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, if (continueThisLevel) { bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - if (params.stats) { - params.stats->colorSent(element); - } + octreeQueryNode->stats.colorSent(element); } if (!continueThisLevel) { @@ -1595,9 +1594,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, bag.insert(element); // don't need to check element here, because we can't get here with no element - if (params.stats) { - params.stats->didntFit(element); - } + octreeQueryNode->stats.didntFit(element); params.stopReason = EncodeBitstreamParams::DIDNT_FIT; bytesAtThisLevel = 0; // didn't fit @@ -1876,9 +1873,7 @@ bool Octree::writeToFile(const char* fileName, OctreeElementPointer element, QSt const char* cFileName = byteArray.constData(); bool success = false; - if (persistAsFileType == "svo") { - success = writeToSVOFile(fileName, element); - } else if (persistAsFileType == "json") { + if (persistAsFileType == "json") { success = writeToJSONFile(cFileName, element); } else if (persistAsFileType == "json.gz") { success = writeToJSONFile(cFileName, element, true); @@ -1936,95 +1931,6 @@ bool Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element, return success; } -bool Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) { - qWarning() << "SVO file format deprecated. Support for reading SVO files is no longer support and will be removed soon."; - bool success = false; - - std::ofstream file(fileName, std::ios::out|std::ios::binary); - - if(file.is_open()) { - qCDebug(octree, "Saving binary SVO to file %s...", fileName); - - PacketType expectedPacketType = expectedDataPacketType(); - int expectedIntType = (int) expectedPacketType; - PacketVersion expectedVersion = versionForPacketType(expectedPacketType); - bool hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion); - - // before reading the file, check to see if this version of the Octree supports file versions - if (getWantSVOfileVersions()) { - // if so, read the first byte of the file and see if it matches the expected version code - file.write(reinterpret_cast(&expectedIntType), sizeof(expectedIntType)); - file.write(&expectedVersion, sizeof(expectedVersion)); - qCDebug(octree) << "SVO file type: " << expectedPacketType << " version: " << (int)expectedVersion; - - hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion); - } - if (hasBufferBreaks) { - qCDebug(octree) << " this version includes buffer breaks"; - } else { - qCDebug(octree) << " this version does not include buffer breaks"; - } - - - OctreeElementBag elementBag; - OctreeElementExtraEncodeData extraEncodeData; - // If we were given a specific element, start from there, otherwise start from root - if (element) { - elementBag.insert(element); - } else { - elementBag.insert(_rootElement); - } - - OctreePacketData packetData; - int bytesWritten = 0; - bool lastPacketWritten = false; - - while (OctreeElementPointer subTree = elementBag.extract()) { - EncodeBitstreamParams params(INT_MAX, NO_EXISTS_BITS); - params.recurseEverything = true; - withReadLock([&] { - params.extraEncodeData = &extraEncodeData; - bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params); - }); - - // if the subTree couldn't fit, and so we should reset the packet and reinsert the element in our bag and try again - if (bytesWritten == 0 && (params.stopReason == EncodeBitstreamParams::DIDNT_FIT)) { - if (packetData.hasContent()) { - // if this type of SVO file should have buffer breaks, then we will write a buffer size before each - // buffer to allow the reader to read this file in chunks. - if (hasBufferBreaks) { - quint16 bufferSize = packetData.getFinalizedSize(); - file.write((const char*)&bufferSize, sizeof(bufferSize)); - } - file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize()); - lastPacketWritten = true; - } - packetData.reset(); // is there a better way to do this? could we fit more? - elementBag.insert(subTree); - } else { - lastPacketWritten = false; - } - } - - if (!lastPacketWritten) { - // if this type of SVO file should have buffer breaks, then we will write a buffer size before each - // buffer to allow the reader to read this file in chunks. - if (hasBufferBreaks) { - quint16 bufferSize = packetData.getFinalizedSize(); - file.write((const char*)&bufferSize, sizeof(bufferSize)); - } - file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize()); - } - - releaseSceneEncodeData(&extraEncodeData); - - success = true; - } - file.close(); - - return success; -} - unsigned long Octree::getOctreeElementsCount() { unsigned long nodeCount = 0; recurseTreeWithOperation(countOctreeElementsOperation, &nodeCount); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 7b2f303c0d..caae31eaa5 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -59,9 +59,7 @@ const bool DONT_COLLAPSE = false; const int DONT_CHOP = 0; const int NO_BOUNDARY_ADJUST = 0; const int LOW_RES_MOVING_ADJUST = 1; -const quint64 IGNORE_LAST_SENT = 0; -#define IGNORE_SCENE_STATS NULL #define IGNORE_COVERAGE_MAP NULL #define IGNORE_JURISDICTION_MAP NULL @@ -69,7 +67,6 @@ class EncodeBitstreamParams { public: ViewFrustum viewFrustum; ViewFrustum lastViewFrustum; - quint64 lastQuerySent; int maxEncodeLevel; int maxLevelReached; bool includeExistsBits; @@ -79,10 +76,7 @@ public: int boundaryLevelAdjust; float octreeElementSizeScale; bool forceSendScene; - OctreeSceneStats* stats; JurisdictionMap* jurisdictionMap; - OctreeElementExtraEncodeData* extraEncodeData; - bool usesFrustum; NodeData* nodeData; // output hints from the encode process @@ -90,6 +84,7 @@ public: UNKNOWN, DIDNT_FIT, NULL_NODE, + NULL_NODE_DATA, TOO_DEEP, OUT_OF_JURISDICTION, LOD_SKIP, @@ -107,14 +102,9 @@ public: bool useDeltaView = false, int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE, - quint64 lastQuerySent = IGNORE_LAST_SENT, bool forceSendScene = true, - OctreeSceneStats* stats = IGNORE_SCENE_STATS, JurisdictionMap* jurisdictionMap = IGNORE_JURISDICTION_MAP, - OctreeElementExtraEncodeData* extraEncodeData = nullptr, - bool usesFrustum = true, NodeData* nodeData = nullptr) : - lastQuerySent(lastQuerySent), maxEncodeLevel(maxEncodeLevel), maxLevelReached(0), includeExistsBits(includeExistsBits), @@ -123,10 +113,7 @@ public: boundaryLevelAdjust(boundaryLevelAdjust), octreeElementSizeScale(octreeElementSizeScale), forceSendScene(forceSendScene), - stats(stats), jurisdictionMap(jurisdictionMap), - extraEncodeData(extraEncodeData), - usesFrustum(usesFrustum), nodeData(nodeData), stopReason(UNKNOWN) { @@ -306,9 +293,8 @@ public: void loadOctreeFile(const char* fileName); // Octree exporters - bool writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "svo"); + bool writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "json.gz"); bool writeToJSONFile(const char* filename, OctreeElementPointer element = NULL, bool doGzip = false); - bool writeToSVOFile(const char* filename, OctreeElementPointer element = NULL); virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) = 0; diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index f8215fb34a..927304e862 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -35,7 +35,7 @@ public: OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false, - const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, QString persistAsFileType="svo"); + const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, QString persistAsFileType="json.gz"); bool isInitialLoadComplete() const { return _initialLoadComplete; } quint64 getLoadElapsedTime() const { return _loadTimeUSecs; } diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index f9d37a0086..e71aefc51e 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -511,7 +511,6 @@ WebTablet.prototype.mousePressEvent = function (event) { tablet.gotoHomeScreen(); this.setHomeButtonTexture(); } - Messages.sendLocalMessage("home", this.homeButtonID); } } else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) { this.dragging = true; diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index 30d0b5e772..0b12cb64ed 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -43,7 +43,8 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { QByteArray fbxContents = fbx.readAll(); FBXGeometry* geom; if (filename.toLower().endsWith(".obj")) { - geom = OBJReader().readOBJ(fbxContents, QVariantHash()); + bool combineParts = false; + geom = OBJReader().readOBJ(fbxContents, QVariantHash(), combineParts); } else if (filename.toLower().endsWith(".fbx")) { geom = readFBX(fbxContents, QVariantHash(), filename); } else { diff --git a/tools/vhacd-util/src/VHACDUtilApp.cpp b/tools/vhacd-util/src/VHACDUtilApp.cpp index cae184a49c..4d48bdf2bf 100644 --- a/tools/vhacd-util/src/VHACDUtilApp.cpp +++ b/tools/vhacd-util/src/VHACDUtilApp.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include "VHACDUtilApp.h" #include "VHACDUtil.h" @@ -98,6 +99,8 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : { vhacd::VHACDUtil vUtil; + DependencyManager::set(); + // parse command-line QCommandLineParser parser; parser.setApplicationDescription("High Fidelity Object Decomposer");