From d17e04967a7dbbfbd12ce33a08e279f8626b1907 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 18 Jan 2019 13:49:16 -0800 Subject: [PATCH 01/62] fix zone bugs --- .../src/RenderableZoneEntityItem.cpp | 61 +++++++++++-------- libraries/entities/src/ZoneEntityItem.cpp | 3 +- libraries/entities/src/ZoneEntityItem.h | 3 - libraries/graphics/src/graphics/Haze.cpp | 9 --- libraries/graphics/src/graphics/Haze.h | 30 ++++----- libraries/render-utils/src/Haze.slh | 2 +- 6 files changed, 51 insertions(+), 57 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 57ff8ed8c2..631148c27a 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -198,24 +198,33 @@ void ZoneEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { DependencyManager::get()->updateZone(entity->getID()); + auto position = entity->getWorldPosition(); + auto rotation = entity->getWorldOrientation(); + auto dimensions = entity->getScaledDimensions(); + bool rotationChanged = rotation != _lastRotation; + bool transformChanged = rotationChanged || position != _lastPosition || dimensions != _lastDimensions; + + auto proceduralUserData = entity->getUserData(); + bool proceduralUserDataChanged = _proceduralUserData != proceduralUserData; + // FIXME one of the bools here could become true between being fetched and being reset, // resulting in a lost update - bool keyLightChanged = entity->keyLightPropertiesChanged(); - bool ambientLightChanged = entity->ambientLightPropertiesChanged(); - bool skyboxChanged = entity->skyboxPropertiesChanged(); + bool keyLightChanged = entity->keyLightPropertiesChanged() || rotationChanged; + bool ambientLightChanged = entity->ambientLightPropertiesChanged() || transformChanged; + bool skyboxChanged = entity->skyboxPropertiesChanged() || proceduralUserDataChanged; bool hazeChanged = entity->hazePropertiesChanged(); bool bloomChanged = entity->bloomPropertiesChanged(); - entity->resetRenderingPropertiesChanged(); - _lastPosition = entity->getWorldPosition(); - _lastRotation = entity->getWorldOrientation(); - _lastDimensions = entity->getScaledDimensions(); - _keyLightProperties = entity->getKeyLightProperties(); - _ambientLightProperties = entity->getAmbientLightProperties(); - _skyboxProperties = entity->getSkyboxProperties(); - _hazeProperties = entity->getHazeProperties(); - _bloomProperties = entity->getBloomProperties(); + if (transformChanged) { + _lastPosition = entity->getWorldPosition(); + _lastRotation = entity->getWorldOrientation(); + _lastDimensions = entity->getScaledDimensions(); + } + + if (proceduralUserDataChanged) { + _proceduralUserData = entity->getUserData(); + } #if 0 if (_lastShapeURL != _typedEntity->getCompoundShapeURL()) { @@ -239,21 +248,29 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateKeyZoneItemFromEntity(entity); if (keyLightChanged) { + _keyLightProperties = entity->getKeyLightProperties(); updateKeySunFromEntity(entity); } if (ambientLightChanged) { + _ambientLightProperties = entity->getAmbientLightProperties(); updateAmbientLightFromEntity(entity); } - if (skyboxChanged || _proceduralUserData != entity->getUserData()) { + if (skyboxChanged) { + _skyboxProperties = entity->getSkyboxProperties(); updateKeyBackgroundFromEntity(entity); } if (hazeChanged) { + _hazeProperties = entity->getHazeProperties(); updateHazeFromEntity(entity); } + if (bloomChanged) { + _bloomProperties = entity->getBloomProperties(); + updateBloomFromEntity(entity); + } bool visuallyReady = true; uint32_t skyboxMode = entity->getSkyboxMode(); @@ -264,10 +281,6 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen } entity->setVisuallyReady(visuallyReady); - - if (bloomChanged) { - updateBloomFromEntity(entity); - } } void ZoneEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { @@ -344,7 +357,7 @@ void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity // Set the keylight sunLight->setColor(ColorUtils::toVec3(_keyLightProperties.getColor())); sunLight->setIntensity(_keyLightProperties.getIntensity()); - sunLight->setDirection(entity->getTransform().getRotation() * _keyLightProperties.getDirection()); + sunLight->setDirection(_lastRotation * _keyLightProperties.getDirection()); sunLight->setCastShadows(_keyLightProperties.getCastShadows()); } @@ -356,7 +369,6 @@ void ZoneEntityRenderer::updateAmbientLightFromEntity(const TypedEntityPointer& ambientLight->setPosition(_lastPosition); ambientLight->setOrientation(_lastRotation); - // Set the ambient light ambientLight->setAmbientIntensity(_ambientLightProperties.getAmbientIntensity()); @@ -395,8 +407,6 @@ void ZoneEntityRenderer::updateHazeFromEntity(const TypedEntityPointer& entity) haze->setHazeAttenuateKeyLight(_hazeProperties.getHazeAttenuateKeyLight()); haze->setHazeKeyLightRangeFactor(graphics::Haze::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeKeyLightRange())); haze->setHazeKeyLightAltitudeFactor(graphics::Haze::convertHazeAltitudeToHazeAltitudeFactor(_hazeProperties.getHazeKeyLightAltitude())); - - haze->setTransform(entity->getTransform().getMatrix()); } void ZoneEntityRenderer::updateBloomFromEntity(const TypedEntityPointer& entity) { @@ -414,13 +424,13 @@ void ZoneEntityRenderer::updateKeyBackgroundFromEntity(const TypedEntityPointer& editBackground(); setSkyboxColor(toGlm(_skyboxProperties.getColor())); - setProceduralUserData(entity->getUserData()); + setProceduralUserData(_proceduralUserData); setSkyboxURL(_skyboxProperties.getURL()); } void ZoneEntityRenderer::updateKeyZoneItemFromEntity(const TypedEntityPointer& entity) { // Update rotation values - editSkybox()->setOrientation(entity->getTransform().getRotation()); + editSkybox()->setOrientation(_lastRotation); /* TODO: Implement the sun model behavior / Keep this code here for reference, this is how we { @@ -540,9 +550,6 @@ void ZoneEntityRenderer::setSkyboxColor(const glm::vec3& color) { } void ZoneEntityRenderer::setProceduralUserData(const QString& userData) { - if (_proceduralUserData != userData) { - _proceduralUserData = userData; - std::dynamic_pointer_cast(editSkybox())->parse(_proceduralUserData); - } + std::dynamic_pointer_cast(editSkybox())->parse(userData); } diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 7f7f6170d4..7b0491dbc0 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -119,7 +119,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || - _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; + _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; return somethingChanged; } @@ -394,7 +394,6 @@ void ZoneEntityItem::resetRenderingPropertiesChanged() { _skyboxPropertiesChanged = false; _hazePropertiesChanged = false; _bloomPropertiesChanged = false; - _stagePropertiesChanged = false; }); } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 813115add9..11c85dab89 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -102,8 +102,6 @@ public: bool hazePropertiesChanged() const { return _hazePropertiesChanged; } bool bloomPropertiesChanged() const { return _bloomPropertiesChanged; } - bool stagePropertiesChanged() const { return _stagePropertiesChanged; } - void resetRenderingPropertiesChanged(); virtual bool supportsDetailedIntersection() const override { return true; } @@ -155,7 +153,6 @@ protected: bool _skyboxPropertiesChanged { false }; bool _hazePropertiesChanged{ false }; bool _bloomPropertiesChanged { false }; - bool _stagePropertiesChanged { false }; static bool _drawZoneBoundaries; static bool _zonesArePickable; diff --git a/libraries/graphics/src/graphics/Haze.cpp b/libraries/graphics/src/graphics/Haze.cpp index ded48429ba..d9bee7507f 100644 --- a/libraries/graphics/src/graphics/Haze.cpp +++ b/libraries/graphics/src/graphics/Haze.cpp @@ -182,12 +182,3 @@ void Haze::setHazeBackgroundBlend(const float hazeBackgroundBlend) { _hazeParametersBuffer.edit().hazeBackgroundBlend = newBlend; } } - -void Haze::setTransform(const glm::mat4& transform) { - auto& params = _hazeParametersBuffer.get(); - - if (params.transform != transform) { - _hazeParametersBuffer.edit().transform = transform; - } -} - diff --git a/libraries/graphics/src/graphics/Haze.h b/libraries/graphics/src/graphics/Haze.h index 59138319f4..25004f098f 100644 --- a/libraries/graphics/src/graphics/Haze.h +++ b/libraries/graphics/src/graphics/Haze.h @@ -92,8 +92,6 @@ namespace graphics { void setHazeBackgroundBlend(const float hazeBackgroundBlend); - void setTransform(const glm::mat4& transform); - using UniformBufferView = gpu::BufferView; UniformBufferView getHazeParametersBuffer() const { return _hazeParametersBuffer; } @@ -101,30 +99,32 @@ namespace graphics { class Parameters { public: // DO NOT CHANGE ORDER HERE WITHOUT UNDERSTANDING THE std140 LAYOUT - glm::vec3 hazeColor{ INITIAL_HAZE_COLOR }; - float hazeGlareBlend{ convertGlareAngleToPower(INITIAL_HAZE_GLARE_ANGLE) }; + glm::vec3 hazeColor { INITIAL_HAZE_COLOR }; + float hazeGlareBlend { convertGlareAngleToPower(INITIAL_HAZE_GLARE_ANGLE) }; - glm::vec3 hazeGlareColor{ INITIAL_HAZE_GLARE_COLOR }; - float hazeBaseReference{ INITIAL_HAZE_BASE_REFERENCE }; + glm::vec3 hazeGlareColor { INITIAL_HAZE_GLARE_COLOR }; + float hazeBaseReference { INITIAL_HAZE_BASE_REFERENCE }; glm::vec3 colorModulationFactor; - int hazeMode{ 0 }; // bit 0 - set to activate haze attenuation of fragment color + int hazeMode { 0 }; // bit 0 - set to activate haze attenuation of fragment color // bit 1 - set to add the effect of altitude to the haze attenuation // bit 2 - set to activate directional light attenuation mode // bit 3 - set to blend between blend-in and blend-out colours - glm::mat4 transform; + // Padding required to align the struct +#if defined(__clang__) + __attribute__((unused)) +#endif + vec3 __padding; // Amount of background (skybox) to display, overriding the haze effect for the background - float hazeBackgroundBlend{ INITIAL_HAZE_BACKGROUND_BLEND }; + float hazeBackgroundBlend { INITIAL_HAZE_BACKGROUND_BLEND }; // The haze attenuation exponents used by both fragment and directional light attenuation - float hazeRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_HAZE_RANGE) }; - float hazeHeightFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_HAZE_HEIGHT) }; - float hazeKeyLightRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_KEY_LIGHT_RANGE) }; + float hazeRangeFactor { convertHazeRangeToHazeRangeFactor(INITIAL_HAZE_RANGE) }; + float hazeHeightFactor { convertHazeAltitudeToHazeAltitudeFactor(INITIAL_HAZE_HEIGHT) }; + float hazeKeyLightRangeFactor { convertHazeRangeToHazeRangeFactor(INITIAL_KEY_LIGHT_RANGE) }; - float hazeKeyLightAltitudeFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_KEY_LIGHT_ALTITUDE) }; - // Padding required to align the structure to sizeof(vec4) - vec3 __padding; + float hazeKeyLightAltitudeFactor { convertHazeAltitudeToHazeAltitudeFactor(INITIAL_KEY_LIGHT_ALTITUDE) }; Parameters() {} }; diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index 0bf1d5d689..e2285febe4 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -28,7 +28,7 @@ struct HazeParams { vec3 colorModulationFactor; int hazeMode; - mat4 transform; + vec3 spare; float backgroundBlend; float hazeRangeFactor; From 618146e885c2e08e16040ffe7d303991b9b55cee Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 18 Jan 2019 15:36:10 -0800 Subject: [PATCH 02/62] fix animation url change --- .../src/RenderableModelEntityItem.cpp | 35 +++++++------------ .../src/RenderableModelEntityItem.h | 9 ++--- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index aa449b8919..b8dc7816f1 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -959,23 +959,6 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } -void RenderableModelEntityItem::setAnimationURL(const QString& url) { - QString oldURL = getAnimationURL(); - ModelEntityItem::setAnimationURL(url); - if (oldURL != getAnimationURL()) { - _needsAnimationReset = true; - } -} - -bool RenderableModelEntityItem::needsAnimationReset() const { - return _needsAnimationReset; -} - -QString RenderableModelEntityItem::getAnimationURLAndReset() { - _needsAnimationReset = false; - return getAnimationURL(); -} - scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel() { auto model = resultWithReadLock([this]{ return _model; }); @@ -1474,11 +1457,17 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); - if (_animation && entity->needsAnimationReset()) { - //(_animation->getURL().toString() != entity->getAnimationURL())) { // bad check - // the joints have been mapped before but we have a new animation to load - _animation.reset(); - _jointMappingCompleted = false; + auto animationURL = entity->getAnimationURL(); + bool animationChanged = _animationURL != animationURL; + if (animationChanged) { + _animationURL = animationURL; + + if (_animation) { + //(_animation->getURL().toString() != entity->getAnimationURL())) { // bad check + // the joints have been mapped before but we have a new animation to load + _animation.reset(); + _jointMappingCompleted = false; + } } if (!_jointMappingCompleted) { @@ -1525,7 +1514,7 @@ void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const Mode } if (!_animation) { - _animation = DependencyManager::get()->getAnimation(entity->getAnimationURLAndReset()); + _animation = DependencyManager::get()->getAnimation(_animationURL); } if (_animation && _animation->isLoaded()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 725c1d96c3..c3a7a49272 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -113,10 +113,6 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; - void setAnimationURL(const QString& url) override; - bool needsAnimationReset() const; - QString getAnimationURLAndReset(); - private: bool needsUpdateModelBounds() const; void autoResizeJointArrays(); @@ -131,7 +127,6 @@ private: bool _originalTexturesRead { false }; bool _dimensionsInitialized { true }; bool _needsJointSimulation { false }; - bool _needsAnimationReset { false }; }; namespace render { namespace entities { @@ -188,12 +183,12 @@ private: const void* _collisionMeshKey { nullptr }; - // used on client side + QUrl _parsedModelURL; bool _jointMappingCompleted { false }; QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints AnimationPointer _animation; - QUrl _parsedModelURL; bool _animating { false }; + QString _animationURL; uint64_t _lastAnimated { 0 }; render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; From e112bf19cba9c09609b7533092c6ad268f5d5873 Mon Sep 17 00:00:00 2001 From: raveenajain Date: Mon, 28 Jan 2019 11:08:09 -0800 Subject: [PATCH 03/62] gltf color attribute --- libraries/fbx/src/GLTFSerializer.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) mode change 100644 => 100755 libraries/fbx/src/GLTFSerializer.cpp diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp old mode 100644 new mode 100755 index 96c236f703..c2fdc4f0bd --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -831,6 +831,27 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { for (int n = 0; n < normals.size(); n = n + 3) { mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); } + } else if (key == "COLOR_0") { + QVector colors; + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + colors, + accessor.type, + accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; + continue; + } + if (accessor.type == 3) { + for (int n = 0; n < colors.size(); n = n + 4) { + mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); + } + } else { + for (int n = 0; n < colors.size(); n = n + 3) { + mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); + } + } } else if (key == "TEXCOORD_0") { QVector texcoords; success = addArrayOfType(buffer.blob, @@ -926,7 +947,6 @@ HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHas //_file.dump(); auto hfmModelPtr = std::make_shared(); HFMModel& hfmModel = *hfmModelPtr; - buildGeometry(hfmModel, _url); //hfmDebugDump(data); From ff9280a496ed98d8f0407733d21eaeced4f60dbd Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 25 Jan 2019 17:06:08 -0800 Subject: [PATCH 04/62] Remove unused properties related to mapping from FBXSerializer --- libraries/fbx/src/FBXSerializer.cpp | 12 ++++-------- libraries/fbx/src/GLTFSerializer.cpp | 2 -- libraries/hfm/src/hfm/HFM.h | 3 --- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 38465d2809..25e0c0fd3a 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -444,8 +444,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr std::map lights; - QVariantHash joints = mapping.value("joint").toHash(); - QVariantHash blendshapeMappings = mapping.value("bs").toHash(); QMultiHash blendshapeIndices; @@ -473,8 +471,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr HFMModel& hfmModel = *hfmModelPtr; hfmModel.originalURL = url; - hfmModel.hfmToHifiJointNameMapping.clear(); - hfmModel.hfmToHifiJointNameMapping = getJointNameMapping(mapping); + auto hfmToHifiJointNameMapping = getJointNameMapping(mapping); float unitScaleFactor = 1.0f; glm::vec3 ambientColor; @@ -1341,8 +1338,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = fbxModel.name; - if (hfmModel.hfmToHifiJointNameMapping.contains(hfmModel.hfmToHifiJointNameMapping.key(joint.name))) { - joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); + if (hfmToHifiJointNameMapping.contains(hfmToHifiJointNameMapping.key(joint.name))) { + joint.name = hfmToHifiJointNameMapping.key(fbxModel.name); } joint.bindTransformFoundInCluster = false; @@ -1704,7 +1701,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr generateBoundryLinesForDop14(joint.shapeInfo.dots, joint.shapeInfo.avgPoint, joint.shapeInfo.debugLines); } } - hfmModel.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); // attempt to map any meshes to a named model for (QHash::const_iterator m = meshIDsToMeshIndices.constBegin(); @@ -1728,7 +1724,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr QString jointName = itr.key(); glm::quat rotationOffset = itr.value(); int jointIndex = hfmModel.getJointIndex(jointName); - if (hfmModel.hfmToHifiJointNameMapping.contains(jointName)) { + if (hfmToHifiJointNameMapping.contains(jointName)) { jointIndex = hfmModel.getJointIndex(jointName); } if (jointIndex != -1) { diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 96c236f703..238e7d7fdb 100644 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -1187,8 +1187,6 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; qCDebug(modelformat) << " offset =" << hfmModel.offset; - qCDebug(modelformat) << " palmDirection = " << hfmModel.palmDirection; - qCDebug(modelformat) << " neckPivot = " << hfmModel.neckPivot; qCDebug(modelformat) << " bindExtents.size() = " << hfmModel.bindExtents.size(); diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 1bd87332a1..07528f3348 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -291,8 +291,6 @@ public: glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file - glm::vec3 palmDirection; - glm::vec3 neckPivot; Extents bindExtents; @@ -319,7 +317,6 @@ public: QList blendshapeChannelNames; QMap jointRotationOffsets; - QMap hfmToHifiJointNameMapping; }; }; From 3e7a80ac4c779d367d75f56b78fdbbbcf61191cd Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 25 Jan 2019 18:07:17 -0800 Subject: [PATCH 05/62] Move FST joint name and rig processing to the model preparation step --- libraries/fbx/src/FBXSerializer.cpp | 59 ------------ .../model-baker/src/model-baker/Baker.cpp | 31 +++++-- libraries/model-baker/src/model-baker/Baker.h | 4 +- .../src/model-baker/PrepareJointsTask.cpp | 89 +++++++++++++++++++ .../src/model-baker/PrepareJointsTask.h | 30 +++++++ .../src/model-networking/ModelCache.cpp | 8 +- 6 files changed, 150 insertions(+), 71 deletions(-) create mode 100644 libraries/model-baker/src/model-baker/PrepareJointsTask.cpp create mode 100644 libraries/model-baker/src/model-baker/PrepareJointsTask.h diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 25e0c0fd3a..92d5e7e774 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -384,43 +384,6 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { return filepath.mid(filepath.lastIndexOf('/') + 1); } -QMap getJointNameMapping(const QVariantHash& mapping) { - static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; - QMap hfmToHifiJointNameMap; - if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { - auto jointNames = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); - for (auto itr = jointNames.begin(); itr != jointNames.end(); itr++) { - hfmToHifiJointNameMap.insert(itr.key(), itr.value().toString()); - qCDebug(modelformat) << "the mapped key " << itr.key() << " has a value of " << hfmToHifiJointNameMap[itr.key()]; - } - } - return hfmToHifiJointNameMap; -} - -QMap getJointRotationOffsets(const QVariantHash& mapping) { - QMap jointRotationOffsets; - static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; - if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { - auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); - for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { - QString jointName = itr.key(); - QString line = itr.value().toString(); - auto quatCoords = line.split(','); - if (quatCoords.size() == 4) { - float quatX = quatCoords[0].mid(1).toFloat(); - float quatY = quatCoords[1].toFloat(); - float quatZ = quatCoords[2].toFloat(); - float quatW = quatCoords[3].mid(0, quatCoords[3].size() - 1).toFloat(); - if (!isNaN(quatX) && !isNaN(quatY) && !isNaN(quatZ) && !isNaN(quatW)) { - glm::quat rotationOffset = glm::quat(quatW, quatX, quatY, quatZ); - jointRotationOffsets.insert(jointName, rotationOffset); - } - } - } - } - return jointRotationOffsets; -} - HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QString& url) { const FBXNode& node = _rootNode; QMap meshes; @@ -471,7 +434,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr HFMModel& hfmModel = *hfmModelPtr; hfmModel.originalURL = url; - auto hfmToHifiJointNameMapping = getJointNameMapping(mapping); float unitScaleFactor = 1.0f; glm::vec3 ambientColor; @@ -1284,13 +1246,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } // convert the models to joints - QVariantList freeJoints = mapping.values("freeJoint"); hfmModel.hasSkeletonJoints = false; foreach (const QString& modelID, modelIDs) { const FBXModel& fbxModel = fbxModels[modelID]; HFMJoint joint; - joint.isFree = freeJoints.contains(fbxModel.name); joint.parentIndex = fbxModel.parentIndex; // get the indices of all ancestors starting with the first free one (if any) @@ -1338,14 +1298,10 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = fbxModel.name; - if (hfmToHifiJointNameMapping.contains(hfmToHifiJointNameMapping.key(joint.name))) { - joint.name = hfmToHifiJointNameMapping.key(fbxModel.name); - } joint.bindTransformFoundInCluster = false; hfmModel.joints.append(joint); - hfmModel.jointIndices.insert(joint.name, hfmModel.joints.size()); QString rotationID = localRotations.value(modelID); AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); @@ -1718,21 +1674,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } - auto offsets = getJointRotationOffsets(mapping); - hfmModel.jointRotationOffsets.clear(); - for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { - QString jointName = itr.key(); - glm::quat rotationOffset = itr.value(); - int jointIndex = hfmModel.getJointIndex(jointName); - if (hfmToHifiJointNameMapping.contains(jointName)) { - jointIndex = hfmModel.getJointIndex(jointName); - } - if (jointIndex != -1) { - hfmModel.jointRotationOffsets.insert(jointIndex, rotationOffset); - } - qCDebug(modelformat) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; - } - return hfmModelPtr; } diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 8d1a82518d..1c2a2f5c63 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -19,13 +19,14 @@ #include "CalculateMeshTangentsTask.h" #include "CalculateBlendshapeNormalsTask.h" #include "CalculateBlendshapeTangentsTask.h" +#include "PrepareJointsTask.h" namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash>; + using Output = VaryingSet6, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash, std::vector>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { @@ -39,6 +40,7 @@ namespace baker { blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); } output.edit4() = hfmModelIn->materials; + output.edit5() = hfmModelIn->joints.toStdVector(); } }; @@ -99,23 +101,29 @@ namespace baker { class BuildModelTask { public: - using Input = VaryingSet2>; + using Input = VaryingSet5, std::vector, QMap /*jointRotationOffsets*/, QHash /*jointIndices*/>; using Output = hfm::Model::Pointer; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { auto hfmModelOut = input.get0(); hfmModelOut->meshes = QVector::fromStdVector(input.get1()); + hfmModelOut->joints = QVector::fromStdVector(input.get2()); + hfmModelOut->jointRotationOffsets = input.get3(); + hfmModelOut->jointIndices = input.get4(); output = hfmModelOut; } }; class BakerEngineBuilder { public: - using Input = hfm::Model::Pointer; + using Input = VaryingSet2; using Output = hfm::Model::Pointer; using JobModel = Task::ModelIO; - void build(JobModel& model, const Varying& hfmModelIn, Varying& hfmModelOut) { + void build(JobModel& model, const Varying& input, Varying& hfmModelOut) { + const auto& hfmModelIn = input.getN(0); + const auto& mapping = input.getN(1); + // Split up the inputs from hfm::Model const auto modelPartsIn = model.addJob("GetModelParts", hfmModelIn); const auto meshesIn = modelPartsIn.getN(0); @@ -123,6 +131,7 @@ namespace baker { const auto meshIndicesToModelNames = modelPartsIn.getN(2); const auto blendshapesPerMeshIn = modelPartsIn.getN(3); const auto materials = modelPartsIn.getN(4); + const auto jointsIn = modelPartsIn.getN(5); // Calculate normals and tangents for meshes and blendshapes if they do not exist // Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer. @@ -138,19 +147,27 @@ namespace baker { const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying(); const auto graphicsMeshes = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); + // Prepare joint information + const auto prepareJointsInputs = PrepareJointsTask::Input(jointsIn, mapping).asVarying(); + const auto jointInfoOut = model.addJob("PrepareJoints", prepareJointsInputs); + const auto jointsOut = jointInfoOut.getN(0); + const auto jointRotationOffsets = jointInfoOut.getN(1); + const auto jointIndices = jointInfoOut.getN(2); + // Combine the outputs into a new hfm::Model const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); const auto blendshapesPerMeshOut = model.addJob("BuildBlendshapes", buildBlendshapesInputs); const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); const auto meshesOut = model.addJob("BuildMeshes", buildMeshesInputs); - const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut).asVarying(); + const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices).asVarying(); hfmModelOut = model.addJob("BuildModel", buildModelInputs); } }; - Baker::Baker(const hfm::Model::Pointer& hfmModel) : + Baker::Baker(const hfm::Model::Pointer& hfmModel, const QVariantHash& mapping) : _engine(std::make_shared(BakerEngineBuilder::JobModel::create("Baker"), std::make_shared())) { - _engine->feedInput(hfmModel); + _engine->feedInput(0, hfmModel); + _engine->feedInput(1, mapping); } void Baker::run() { diff --git a/libraries/model-baker/src/model-baker/Baker.h b/libraries/model-baker/src/model-baker/Baker.h index 7fb3f420e0..41989d73df 100644 --- a/libraries/model-baker/src/model-baker/Baker.h +++ b/libraries/model-baker/src/model-baker/Baker.h @@ -12,6 +12,8 @@ #ifndef hifi_baker_Baker_h #define hifi_baker_Baker_h +#include + #include #include "Engine.h" @@ -19,7 +21,7 @@ namespace baker { class Baker { public: - Baker(const hfm::Model::Pointer& hfmModel); + Baker(const hfm::Model::Pointer& hfmModel, const QVariantHash& mapping); void run(); diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp new file mode 100644 index 0000000000..82ad77d651 --- /dev/null +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -0,0 +1,89 @@ +// +// PrepareJointsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/25. +// Copyright 2019 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 "PrepareJointsTask.h" + +#include "ModelBakerLogging.h" + +QMap getJointNameMapping(const QVariantHash& mapping) { + static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; + QMap hfmToHifiJointNameMap; + if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { + auto jointNames = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); + for (auto itr = jointNames.begin(); itr != jointNames.end(); itr++) { + hfmToHifiJointNameMap.insert(itr.key(), itr.value().toString()); + qCDebug(model_baker) << "the mapped key " << itr.key() << " has a value of " << hfmToHifiJointNameMap[itr.key()]; + } + } + return hfmToHifiJointNameMap; +} + +QMap getJointRotationOffsets(const QVariantHash& mapping) { + QMap jointRotationOffsets; + static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; + if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { + auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { + QString jointName = itr.key(); + QString line = itr.value().toString(); + auto quatCoords = line.split(','); + if (quatCoords.size() == 4) { + float quatX = quatCoords[0].mid(1).toFloat(); + float quatY = quatCoords[1].toFloat(); + float quatZ = quatCoords[2].toFloat(); + float quatW = quatCoords[3].mid(0, quatCoords[3].size() - 1).toFloat(); + if (!isNaN(quatX) && !isNaN(quatY) && !isNaN(quatZ) && !isNaN(quatW)) { + glm::quat rotationOffset = glm::quat(quatW, quatX, quatY, quatZ); + jointRotationOffsets.insert(jointName, rotationOffset); + } + } + } + } + return jointRotationOffsets; +} + +void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& jointsIn = input.get0(); + const auto& mapping = input.get1(); + auto& jointsOut = output.edit0(); + auto& jointRotationOffsets = output.edit1(); + auto& jointIndices = output.edit2(); + + // Get which joints are free from FST file mappings + QVariantList freeJoints = mapping.values("freeJoint"); + // Get joint renames + auto jointNameMapping = getJointNameMapping(mapping); + // Apply joint metadata from FST file mappings + for (const auto& jointIn : jointsIn) { + jointsOut.push_back(jointIn); + auto& jointOut = jointsOut[jointsOut.size()-1]; + + jointOut.isFree = freeJoints.contains(jointIn.name); + + if (jointNameMapping.contains(jointNameMapping.key(jointIn.name))) { + jointOut.name = jointNameMapping.key(jointIn.name); + } + + jointIndices.insert(jointOut.name, (int)jointsOut.size()); + } + + // Get joint rotation offsets from FST file mappings + auto offsets = getJointRotationOffsets(mapping); + for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { + QString jointName = itr.key(); + glm::quat rotationOffset = itr.value(); + int jointIndex = jointIndices.value(jointName) - 1; + if (jointIndex != -1) { + jointRotationOffsets.insert(jointIndex, rotationOffset); + } + qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; + } +} diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.h b/libraries/model-baker/src/model-baker/PrepareJointsTask.h new file mode 100644 index 0000000000..e12d8ffd2c --- /dev/null +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.h @@ -0,0 +1,30 @@ +// +// PrepareJointsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/25. +// Copyright 2019 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_PrepareJointsTask_h +#define hifi_PrepareJointsTask_h + +#include + +#include + +#include "Engine.h" + +class PrepareJointsTask { +public: + using Input = baker::VaryingSet2, QVariantHash /*mapping*/>; + using Output = baker::VaryingSet3, QMap /*jointRotationOffsets*/, QHash /*jointIndices*/>; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_PrepareJointsTask_h \ No newline at end of file diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 05c4aa0e03..f18d4ab28c 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -233,7 +233,7 @@ void GeometryReader::run() { } QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition", - Q_ARG(HFMModel::Pointer, hfmModel)); + Q_ARG(HFMModel::Pointer, hfmModel), Q_ARG(QVariantHash, _mapping)); } catch (const std::exception&) { auto resource = _resource.toStrongRef(); if (resource) { @@ -261,7 +261,7 @@ public: virtual void downloadFinished(const QByteArray& data) override; protected: - Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel); + Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, QVariantHash mapping); private: ModelLoader _modelLoader; @@ -277,9 +277,9 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType())); } -void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel) { +void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, QVariantHash mapping) { // Do processing on the model - baker::Baker modelBaker(hfmModel); + baker::Baker modelBaker(hfmModel, mapping); modelBaker.run(); // Assume ownership of the processed HFMModel From eace901278335e413d37485bb3e20f52434501a9 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 29 Jan 2019 10:29:12 -0800 Subject: [PATCH 06/62] Fix not calculating joint freeLineage list properly --- libraries/fbx/src/FBXSerializer.cpp | 12 +----------- .../src/model-baker/PrepareJointsTask.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 92d5e7e774..207ee2982d 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1252,18 +1252,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr const FBXModel& fbxModel = fbxModels[modelID]; HFMJoint joint; joint.parentIndex = fbxModel.parentIndex; - - // get the indices of all ancestors starting with the first free one (if any) int jointIndex = hfmModel.joints.size(); - joint.freeLineage.append(jointIndex); - int lastFreeIndex = joint.isFree ? 0 : -1; - for (int index = joint.parentIndex; index != -1; index = hfmModel.joints.at(index).parentIndex) { - if (hfmModel.joints.at(index).isFree) { - lastFreeIndex = joint.freeLineage.size(); - } - joint.freeLineage.append(index); - } - joint.freeLineage.remove(lastFreeIndex + 1, joint.freeLineage.size() - lastFreeIndex - 1); + joint.translation = fbxModel.translation; // these are usually in centimeters joint.preTransform = fbxModel.preTransform; joint.preRotation = fbxModel.preRotation; diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index 82ad77d651..5f4a1b4f04 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -67,6 +67,17 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu auto& jointOut = jointsOut[jointsOut.size()-1]; jointOut.isFree = freeJoints.contains(jointIn.name); + // Get the indices of all ancestors starting with the first free one (if any) + int jointIndex = jointsOut.size() - 1; + jointOut.freeLineage.append(jointIndex); + int lastFreeIndex = jointOut.isFree ? 0 : -1; + for (int index = jointOut.parentIndex; index != -1; index = jointsOut.at(index).parentIndex) { + if (jointsOut.at(index).isFree) { + lastFreeIndex = jointOut.freeLineage.size(); + } + jointOut.freeLineage.append(index); + } + jointOut.freeLineage.remove(lastFreeIndex + 1, jointOut.freeLineage.size() - lastFreeIndex - 1); if (jointNameMapping.contains(jointNameMapping.key(jointIn.name))) { jointOut.name = jointNameMapping.key(jointIn.name); From 203e8e24556398596c2e0b4c669430a86a45b254 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 29 Jan 2019 11:55:35 -0800 Subject: [PATCH 07/62] Realize joint properties isFree and freeLineage are unused, so tear them out --- .../resources/meshes/defaultAvatar_full.fst | 4 --- interface/src/ModelPackager.cpp | 7 ---- interface/src/ModelPropertiesDialog.cpp | 34 ------------------- interface/src/ModelPropertiesDialog.h | 2 -- libraries/animation/src/AnimSkeleton.cpp | 2 -- .../src/avatars-renderer/SkeletonModel.cpp | 8 ----- .../src/avatars-renderer/SkeletonModel.h | 8 ----- libraries/fbx/src/FST.cpp | 5 --- libraries/fbx/src/FSTReader.cpp | 4 +-- libraries/fbx/src/FSTReader.h | 1 - libraries/fbx/src/GLTFSerializer.cpp | 3 -- libraries/fbx/src/OBJSerializer.cpp | 4 +-- libraries/hfm/src/hfm/HFM.h | 2 -- .../src/model-baker/PrepareJointsTask.cpp | 15 -------- libraries/render-utils/src/Model.cpp | 4 --- libraries/render-utils/src/Model.h | 3 -- 16 files changed, 3 insertions(+), 103 deletions(-) diff --git a/interface/resources/meshes/defaultAvatar_full.fst b/interface/resources/meshes/defaultAvatar_full.fst index b47faeb8a8..aa679e319a 100644 --- a/interface/resources/meshes/defaultAvatar_full.fst +++ b/interface/resources/meshes/defaultAvatar_full.fst @@ -10,10 +10,6 @@ joint = jointRoot = Hips joint = jointLeftHand = LeftHand joint = jointRightHand = RightHand joint = jointHead = Head -freeJoint = LeftArm -freeJoint = LeftForeArm -freeJoint = RightArm -freeJoint = RightForeArm bs = JawOpen = mouth_Open = 1 bs = LipsFunnel = Oo = 1 bs = BrowsU_L = brow_Up = 1 diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 84325da473..db74b34d91 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -294,13 +294,6 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename } mapping.insert(JOINT_FIELD, joints); - - if (!mapping.contains(FREE_JOINT_FIELD)) { - mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); - } // If there are no blendshape mappings, and we detect that this is likely a mixamo file, // then we can add the default mixamo to "faceshift" mappings diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index 1bdb170b60..d67341990d 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -58,11 +58,6 @@ _hfmModel(hfmModel) form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox()); form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox()); - form->addRow("Free Joints:", _freeJoints = new QVBoxLayout()); - QPushButton* newFreeJoint = new QPushButton("New Free Joint"); - _freeJoints->addWidget(newFreeJoint); - connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint())); - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Reset); connect(buttons, SIGNAL(accepted()), SLOT(accept())); @@ -102,11 +97,6 @@ QVariantHash ModelPropertiesDialog::getMapping() const { insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText()); insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText()); - mapping.remove(FREE_JOINT_FIELD); - for (int i = 0; i < _freeJoints->count() - 1; i++) { - QComboBox* box = static_cast(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget()); - mapping.insertMulti(FREE_JOINT_FIELD, box->currentText()); - } mapping.insert(JOINT_FIELD, joints); return mapping; @@ -133,16 +123,6 @@ void ModelPropertiesDialog::reset() { setJointText(_headJoint, jointHash.value("jointHead").toString()); setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString()); setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString()); - - while (_freeJoints->count() > 1) { - delete _freeJoints->itemAt(0)->widget(); - } - foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) { - QString jointName = joint.toString(); - if (_hfmModel.jointIndices.contains(jointName)) { - createNewFreeJoint(jointName); - } - } } void ModelPropertiesDialog::chooseTextureDirectory() { @@ -176,20 +156,6 @@ void ModelPropertiesDialog::updatePivotJoint() { _pivotJoint->setEnabled(!_pivotAboutCenter->isChecked()); } -void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) { - QWidget* freeJoint = new QWidget(); - QHBoxLayout* freeJointLayout = new QHBoxLayout(); - freeJointLayout->setContentsMargins(QMargins()); - freeJoint->setLayout(freeJointLayout); - QComboBox* jointBox = createJointBox(false); - jointBox->setCurrentText(joint); - freeJointLayout->addWidget(jointBox, 1); - QPushButton* deleteJoint = new QPushButton("Delete"); - freeJointLayout->addWidget(deleteJoint); - freeJoint->connect(deleteJoint, SIGNAL(clicked(bool)), SLOT(deleteLater())); - _freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint); -} - QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const { QComboBox* box = new QComboBox(); if (withNone) { diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h index 7019d239ff..8cf9bd5248 100644 --- a/interface/src/ModelPropertiesDialog.h +++ b/interface/src/ModelPropertiesDialog.h @@ -39,7 +39,6 @@ private slots: void chooseTextureDirectory(); void chooseScriptDirectory(); void updatePivotJoint(); - void createNewFreeJoint(const QString& joint = QString()); private: QComboBox* createJointBox(bool withNone = true) const; @@ -66,7 +65,6 @@ private: QComboBox* _headJoint = nullptr; QComboBox* _leftHandJoint = nullptr; QComboBox* _rightHandJoint = nullptr; - QVBoxLayout* _freeJoints = nullptr; }; #endif // hifi_ModelPropertiesDialog_h diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index cc48308f17..1e7b4f0c2c 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -289,8 +289,6 @@ void AnimSkeleton::dump(bool verbose) const { qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i); if (verbose) { qCDebug(animation) << " hfmJoint ="; - qCDebug(animation) << " isFree =" << _joints[i].isFree; - qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage; qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex; qCDebug(animation) << " translation =" << _joints[i].translation; qCDebug(animation) << " preTransform =" << _joints[i].preTransform; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 7f2dbda3de..ea71ff128c 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -249,14 +249,6 @@ bool SkeletonModel::getRightHandPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getRightHandJointIndex(), position); } -bool SkeletonModel::getLeftShoulderPosition(glm::vec3& position) const { - return getJointPositionInWorldFrame(getLastFreeJointIndex(getLeftHandJointIndex()), position); -} - -bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { - return getJointPositionInWorldFrame(getLastFreeJointIndex(getRightHandJointIndex()), position); -} - bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const { return isActive() && getJointPositionInWorldFrame(_rig.indexOfJoint("Head"), headPosition); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index ef0e1e0fae..99f6632306 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -57,17 +57,9 @@ public: /// \return true whether or not the position was found bool getRightHandPosition(glm::vec3& position) const; - /// Gets the position of the left shoulder. - /// \return whether or not the left shoulder joint was found - bool getLeftShoulderPosition(glm::vec3& position) const; - /// Returns the extended length from the left hand to its last free ancestor. float getLeftArmLength() const; - /// Gets the position of the right shoulder. - /// \return whether or not the right shoulder joint was found - bool getRightShoulderPosition(glm::vec3& position) const; - /// Returns the position of the head joint. /// \return whether or not the head was found bool getHeadPosition(glm::vec3& headPosition) const; diff --git a/libraries/fbx/src/FST.cpp b/libraries/fbx/src/FST.cpp index 7828037c74..b6f109c217 100644 --- a/libraries/fbx/src/FST.cpp +++ b/libraries/fbx/src/FST.cpp @@ -82,11 +82,6 @@ FST* FST::createFSTFromModel(const QString& fstPath, const QString& modelFilePat } mapping.insert(JOINT_INDEX_FIELD, jointIndices); - mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); - // If there are no blendshape mappings, and we detect that this is likely a mixamo file, // then we can add the default mixamo to "faceshift" mappings diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 43806560dc..99087954eb 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -84,7 +84,7 @@ void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD - << MARKETPLACE_ID_FIELD << TEXDIR_FIELD << SCRIPT_FIELD << JOINT_FIELD << FREE_JOINT_FIELD + << MARKETPLACE_ID_FIELD << TEXDIR_FIELD << SCRIPT_FIELD << JOINT_FIELD << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; QBuffer buffer; buffer.open(QIODevice::WriteOnly); @@ -92,7 +92,7 @@ QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { for (auto key : PREFERED_ORDER) { auto it = mapping.find(key); if (it != mapping.constEnd()) { - if (key == FREE_JOINT_FIELD || key == SCRIPT_FIELD) { // writeVariant does not handle strings added using insertMulti. + if (key == SCRIPT_FIELD) { // writeVariant does not handle strings added using insertMulti. for (auto multi : mapping.values(key)) { buffer.write(key.toUtf8()); buffer.write(" = "); diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index 993d7c3148..ad952c4ed7 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -27,7 +27,6 @@ static const QString TRANSLATION_X_FIELD = "tx"; static const QString TRANSLATION_Y_FIELD = "ty"; static const QString TRANSLATION_Z_FIELD = "tz"; static const QString JOINT_FIELD = "joint"; -static const QString FREE_JOINT_FIELD = "freeJoint"; static const QString BLENDSHAPE_FIELD = "bs"; static const QString SCRIPT_FIELD = "script"; static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 238e7d7fdb..69606b2738 100644 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -719,7 +719,6 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { //Build default joints hfmModel.joints.resize(1); - hfmModel.joints[0].isFree = false; hfmModel.joints[0].parentIndex = -1; hfmModel.joints[0].distanceToParent = 0; hfmModel.joints[0].translation = glm::vec3(0, 0, 0); @@ -1299,8 +1298,6 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << " shapeInfo.dots =" << joint.shapeInfo.dots; qCDebug(modelformat) << " shapeInfo.points =" << joint.shapeInfo.points; - qCDebug(modelformat) << " isFree =" << joint.isFree; - qCDebug(modelformat) << " freeLineage" << joint.freeLineage; qCDebug(modelformat) << " parentIndex" << joint.parentIndex; qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; qCDebug(modelformat) << " translation" << joint.translation; diff --git a/libraries/fbx/src/OBJSerializer.cpp b/libraries/fbx/src/OBJSerializer.cpp index 9d4b1f16a1..91d3fc7cc0 100644 --- a/libraries/fbx/src/OBJSerializer.cpp +++ b/libraries/fbx/src/OBJSerializer.cpp @@ -687,7 +687,6 @@ HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash mesh.meshIndex = 0; hfmModel.joints.resize(1); - hfmModel.joints[0].isFree = false; hfmModel.joints[0].parentIndex = -1; hfmModel.joints[0].distanceToParent = 0; hfmModel.joints[0].translation = glm::vec3(0, 0, 0); @@ -1048,8 +1047,7 @@ void hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << " joints.count() =" << hfmModel.joints.count(); foreach (HFMJoint joint, hfmModel.joints) { - qCDebug(modelformat) << " isFree =" << joint.isFree; - qCDebug(modelformat) << " freeLineage" << joint.freeLineage; + qCDebug(modelformat) << " parentIndex" << joint.parentIndex; qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; qCDebug(modelformat) << " translation" << joint.translation; diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 07528f3348..cccfaa3f7d 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -75,8 +75,6 @@ struct JointShapeInfo { class Joint { public: JointShapeInfo shapeInfo; - QVector freeLineage; - bool isFree; int parentIndex; float distanceToParent; diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index 5f4a1b4f04..20715cfed7 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -57,8 +57,6 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu auto& jointRotationOffsets = output.edit1(); auto& jointIndices = output.edit2(); - // Get which joints are free from FST file mappings - QVariantList freeJoints = mapping.values("freeJoint"); // Get joint renames auto jointNameMapping = getJointNameMapping(mapping); // Apply joint metadata from FST file mappings @@ -66,19 +64,6 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu jointsOut.push_back(jointIn); auto& jointOut = jointsOut[jointsOut.size()-1]; - jointOut.isFree = freeJoints.contains(jointIn.name); - // Get the indices of all ancestors starting with the first free one (if any) - int jointIndex = jointsOut.size() - 1; - jointOut.freeLineage.append(jointIndex); - int lastFreeIndex = jointOut.isFree ? 0 : -1; - for (int index = jointOut.parentIndex; index != -1; index = jointsOut.at(index).parentIndex) { - if (jointsOut.at(index).isFree) { - lastFreeIndex = jointOut.freeLineage.size(); - } - jointOut.freeLineage.append(index); - } - jointOut.freeLineage.remove(lastFreeIndex + 1, jointOut.freeLineage.size() - lastFreeIndex - 1); - if (jointNameMapping.contains(jointNameMapping.key(jointIn.name))) { jointOut.name = jointNameMapping.key(jointIn.name); } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index da8dceb176..0206bd6963 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1116,10 +1116,6 @@ int Model::getParentJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? getHFMModel().joints.at(jointIndex).parentIndex : -1; } -int Model::getLastFreeJointIndex(int jointIndex) const { - return (isActive() && jointIndex != -1) ? getHFMModel().joints.at(jointIndex).freeLineage.last() : -1; -} - void Model::setTextures(const QVariantMap& textures) { if (isLoaded()) { _needsFixupInScene = true; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 16e08c2b23..aadfca78ba 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -379,9 +379,6 @@ protected: /// Clear the joint states void clearJointState(int index); - /// Returns the index of the last free ancestor of the indexed joint, or -1 if not found. - int getLastFreeJointIndex(int jointIndex) const; - /// \param jointIndex index of joint in model structure /// \param position[out] position of joint in model-frame /// \return true if joint exists From e32a3ba20d0dde5b28c9efda9c2495dd477b386e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 30 Jan 2019 13:19:45 -0800 Subject: [PATCH 08/62] organize new avatar intersection --- interface/src/avatar/AvatarManager.cpp | 100 +++++++++++++------------ 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1eb87c16f0..376c4e7931 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -652,28 +652,25 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic PROFILE_RANGE(simulation_physics, __FUNCTION__); - float distance = (float)INT_MAX; // with FLT_MAX bullet rayTest does not return results + float bulletDistance = (float)INT_MAX; // with FLT_MAX bullet rayTest does not return results glm::vec3 rayDirection = glm::normalize(ray.direction); - std::vector physicsResults = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(rayDirection), distance, QVector()); + std::vector physicsResults = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(rayDirection), bulletDistance, QVector()); if (physicsResults.size() > 0) { glm::vec3 rayDirectionInv = { rayDirection.x != 0.0f ? 1.0f / rayDirection.x : INFINITY, rayDirection.y != 0.0f ? 1.0f / rayDirection.y : INFINITY, rayDirection.z != 0.0f ? 1.0f / rayDirection.z : INFINITY }; - MyCharacterController::RayAvatarResult rayAvatarResult; - AvatarPointer avatar = nullptr; - - BoxFace face = BoxFace::UNKNOWN_FACE; - glm::vec3 surfaceNormal; - QVariantMap extraInfo; - for (auto &hit : physicsResults) { auto avatarID = hit._intersectWithAvatar; if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatarID)) || (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatarID))) { continue; } - + + MyCharacterController::RayAvatarResult rayAvatarResult; + BoxFace face = BoxFace::UNKNOWN_FACE; + QVariantMap extraInfo; + AvatarPointer avatar = nullptr; if (_myAvatar->getSessionUUID() != avatarID) { auto avatarMap = getHashCopy(); AvatarHash::iterator itr = avatarMap.find(avatarID); @@ -683,44 +680,43 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic } else { avatar = _myAvatar; } + if (!hit._isBound) { rayAvatarResult = hit; } else if (avatar) { auto &multiSpheres = avatar->getMultiSphereShapes(); if (multiSpheres.size() > 0) { - std::vector boxHits; + MyCharacterController::RayAvatarResult boxHit; + boxHit._distance = FLT_MAX; + for (size_t i = 0; i < hit._boundJoints.size(); i++) { assert(hit._boundJoints[i] < multiSpheres.size()); auto &mSphere = multiSpheres[hit._boundJoints[i]]; if (mSphere.isValid()) { float boundDistance = FLT_MAX; - BoxFace face; - glm::vec3 surfaceNormal; + BoxFace boundFace = BoxFace::UNKNOWN_FACE; + glm::vec3 boundSurfaceNormal; auto &bbox = mSphere.getBoundingBox(); - if (bbox.findRayIntersection(ray.origin, rayDirection, rayDirectionInv, boundDistance, face, surfaceNormal)) { - MyCharacterController::RayAvatarResult boxHit; - boxHit._distance = boundDistance; - boxHit._intersect = true; - boxHit._intersectionNormal = surfaceNormal; - boxHit._intersectionPoint = ray.origin + boundDistance * rayDirection; - boxHit._intersectWithAvatar = avatarID; - boxHit._intersectWithJoint = mSphere.getJointIndex(); - boxHits.push_back(boxHit); + if (bbox.findRayIntersection(ray.origin, rayDirection, rayDirectionInv, boundDistance, boundFace, boundSurfaceNormal)) { + if (boundDistance < boxHit._distance) { + boxHit._intersect = true; + boxHit._intersectWithAvatar = avatarID; + boxHit._intersectWithJoint = mSphere.getJointIndex(); + boxHit._distance = boundDistance; + boxHit._intersectionPoint = ray.origin + boundDistance * rayDirection; + boxHit._intersectionNormal = boundSurfaceNormal; + face = boundFace; + } } } } - if (boxHits.size() > 0) { - if (boxHits.size() > 1) { - std::sort(boxHits.begin(), boxHits.end(), [](const MyCharacterController::RayAvatarResult& hitA, - const MyCharacterController::RayAvatarResult& hitB) { - return hitA._distance < hitB._distance; - }); - } - rayAvatarResult = boxHits[0]; + if (boxHit._distance < FLT_MAX) { + rayAvatarResult = boxHit; } } } - if (pickAgainstMesh) { + + if (rayAvatarResult._intersect && pickAgainstMesh) { glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint); glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayDirection, rayAvatarResult._intersectWithJoint); @@ -734,29 +730,35 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic auto defaultFrameRayPoint = jointPosition + jointOrientation * localRayPoint; auto defaultFrameRayDirection = defaultFrameRayPoint - defaultFrameRayOrigin; - if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, distance, face, surfaceNormal, extraInfo, true, false)) { - auto newDistance = glm::length(vec3FromVariant(extraInfo["worldIntersectionPoint"]) - defaultFrameRayOrigin); - rayAvatarResult._distance = newDistance; - rayAvatarResult._intersectionPoint = ray.origin + newDistance * rayDirection; - rayAvatarResult._intersectionNormal = surfaceNormal; - extraInfo["worldIntersectionPoint"] = vec3toVariant(rayAvatarResult._intersectionPoint); - break; + float subMeshDistance = FLT_MAX; + BoxFace subMeshFace = BoxFace::UNKNOWN_FACE; + glm::vec3 subMeshSurfaceNormal; + QVariantMap subMeshExtraInfo; + if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, subMeshDistance, subMeshFace, subMeshSurfaceNormal, subMeshExtraInfo, true, false)) { + rayAvatarResult._distance = subMeshDistance; + rayAvatarResult._intersectionPoint = ray.origin + subMeshDistance * rayDirection; + rayAvatarResult._intersectionNormal = subMeshSurfaceNormal; + face = subMeshFace; + extraInfo = subMeshExtraInfo; + } else { + rayAvatarResult._intersect = false; } - } else if (rayAvatarResult._intersect){ + } + + if (rayAvatarResult._intersect) { + result.intersects = true; + result.avatarID = rayAvatarResult._intersectWithAvatar; + result.distance = rayAvatarResult._distance; + result.face = face; + result.intersection = rayAvatarResult._intersectionPoint; + result.surfaceNormal = rayAvatarResult._intersectionNormal; + result.jointIndex = rayAvatarResult._intersectWithJoint; + result.extraInfo = extraInfo; break; } } - if (rayAvatarResult._intersect) { - result.intersects = true; - result.avatarID = rayAvatarResult._intersectWithAvatar; - result.distance = rayAvatarResult._distance; - result.surfaceNormal = rayAvatarResult._intersectionNormal; - result.jointIndex = rayAvatarResult._intersectWithJoint; - result.intersection = ray.origin + rayAvatarResult._distance * rayDirection; - result.extraInfo = extraInfo; - result.face = face; - } } + return result; } From 0e35458f9eeb3f00f71886657b93f46238030552 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 30 Jan 2019 13:27:47 -0800 Subject: [PATCH 09/62] use precisionPicking in RayPick::getAvatarIntersection --- interface/src/raypick/RayPick.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 24ba4435e2..d476357bab 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -56,7 +56,8 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { } PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { - RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs(), true); + bool precisionPicking = !(getFilter().isCoarse() || DependencyManager::get()->getForceCoarsePicking()); + RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs(), precisionPicking); if (avatarRes.intersects) { return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); } else { From 6cbc0fad7f4304a8189af1f4e8d37de8f96dac1a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 30 Jan 2019 15:33:14 -0800 Subject: [PATCH 10/62] fixes --- interface/src/avatar/AvatarManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 376c4e7931..0b33220c01 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -718,7 +718,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic if (rayAvatarResult._intersect && pickAgainstMesh) { glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint); - glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayDirection, rayAvatarResult._intersectWithJoint); + glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayAvatarResult._distance * rayDirection, rayAvatarResult._intersectWithJoint); auto avatarOrientation = avatar->getWorldOrientation(); auto avatarPosition = avatar->getWorldPosition(); @@ -728,7 +728,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic auto defaultFrameRayOrigin = jointPosition + jointOrientation * localRayOrigin; auto defaultFrameRayPoint = jointPosition + jointOrientation * localRayPoint; - auto defaultFrameRayDirection = defaultFrameRayPoint - defaultFrameRayOrigin; + auto defaultFrameRayDirection = glm::normalize(defaultFrameRayPoint - defaultFrameRayOrigin); float subMeshDistance = FLT_MAX; BoxFace subMeshFace = BoxFace::UNKNOWN_FACE; @@ -750,7 +750,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic result.avatarID = rayAvatarResult._intersectWithAvatar; result.distance = rayAvatarResult._distance; result.face = face; - result.intersection = rayAvatarResult._intersectionPoint; + result.intersection = ray.origin + rayAvatarResult._distance * rayDirection; result.surfaceNormal = rayAvatarResult._intersectionNormal; result.jointIndex = rayAvatarResult._intersectWithJoint; result.extraInfo = extraInfo; From 5438bddc84989d683ad0bf349c1e9fb2c4db1f08 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 31 Jan 2019 09:43:30 -0700 Subject: [PATCH 11/62] Fix assertion on shapeInfo when creating multisphere with empty data --- .../src/avatars-renderer/Avatar.cpp | 18 ++++++++++-------- libraries/shared/src/ShapeInfo.cpp | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 07c1ca9a32..ba5529e1c0 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1733,15 +1733,17 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { void Avatar::computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex) { if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) { auto& data = _multiSphereShapes[jointIndex].getSpheresData(); - std::vector positions; - std::vector radiuses; - positions.reserve(data.size()); - radiuses.reserve(data.size()); - for (auto& sphere : data) { - positions.push_back(sphere._position); - radiuses.push_back(sphere._radius); + if (data.size() > 0) { + std::vector positions; + std::vector radiuses; + positions.reserve(data.size()); + radiuses.reserve(data.size()); + for (auto& sphere : data) { + positions.push_back(sphere._position); + radiuses.push_back(sphere._radius); + } + shapeInfo.setMultiSphere(positions, radiuses); } - shapeInfo.setMultiSphere(positions, radiuses); } } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 564d79bfda..c256cf2b76 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -152,7 +152,8 @@ void ShapeInfo::setSphere(float radius) { void ShapeInfo::setMultiSphere(const std::vector& centers, const std::vector& radiuses) { _url = ""; _type = SHAPE_TYPE_MULTISPHERE; - assert(centers.size() == radiuses.size() && centers.size() > 0); + assert(centers.size() == radiuses.size()); + assert(centers.size() > 0); for (size_t i = 0; i < centers.size(); i++) { SphereData sphere = SphereData(centers[i], radiuses[i]); _sphereCollection.push_back(sphere); From 15066faaf7a33147d7ea60f7de1cfc555fbb458c Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 31 Jan 2019 19:37:21 +0100 Subject: [PATCH 12/62] ignore pickRay for zone shape visualizers --- scripts/system/modules/entityShapeVisualizer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js index fe950c2e2b..fdf8ee81e7 100644 --- a/scripts/system/modules/entityShapeVisualizer.js +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -135,6 +135,7 @@ EntityShape.prototype = { overlayProperties.canCastShadows = false; overlayProperties.parentID = this.entityID; overlayProperties.collisionless = true; + overlayProperties.ignorePickIntersection = true; this.entity = Entities.addEntity(overlayProperties, "local"); var PROJECTED_MATERIALS = false; this.materialEntity = Entities.addEntity({ From 80f5cbc8ab0331ec057e8f2760d91bcd932b26e2 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 31 Jan 2019 22:12:35 +0100 Subject: [PATCH 13/62] also ignore picking on the material entity --- scripts/system/modules/entityShapeVisualizer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js index fdf8ee81e7..da28369cdd 100644 --- a/scripts/system/modules/entityShapeVisualizer.js +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -147,6 +147,7 @@ EntityShape.prototype = { priority: 1, materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", materialURL: Script.resolvePath("../assets/images/materials/GridPattern.json"), + ignorePickIntersection: true, }, "local"); }, update: function() { From ae09aec5d9c57ed42a214115e626f4bc02ac47b0 Mon Sep 17 00:00:00 2001 From: raveenajain Date: Thu, 31 Jan 2019 13:36:53 -0800 Subject: [PATCH 14/62] embedded model geometry --- libraries/fbx/src/GLTFSerializer.cpp | 18 +++++++++++++++--- libraries/fbx/src/GLTFSerializer.h | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) mode change 100644 => 100755 libraries/fbx/src/GLTFSerializer.cpp mode change 100644 => 100755 libraries/fbx/src/GLTFSerializer.h diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp old mode 100644 new mode 100755 index 96c236f703..6db298ae58 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -939,10 +939,15 @@ HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHas } bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) { - QUrl binaryUrl = _url.resolved(url); - bool success; - std::tie(success, outdata) = requestData(binaryUrl); + + if (url.contains("data:application/octet-stream;base64,") || url.contains("data:image/png;base64,") || url.contains("data:image/jpeg;base64,")) { + QString binaryUrl = url.split(",")[1]; + std::tie(success, outdata) = requestEmbeddedData(binaryUrl); + } else { + QUrl binaryUrl = _url.resolved(url); + std::tie(success, outdata) = requestData(binaryUrl); + } return success; } @@ -975,6 +980,13 @@ std::tuple GLTFSerializer::requestData(QUrl& url) { } } +std::tuple GLTFSerializer::requestEmbeddedData(QString binaryUrl) { + QByteArray urlBin = binaryUrl.toUtf8(); + QByteArray result = QByteArray::fromBase64(urlBin); + + return std::make_tuple(true, result); +} + QNetworkReply* GLTFSerializer::request(QUrl& url, bool isTest) { if (!qApp) { diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h old mode 100644 new mode 100755 index 5fca77c4fd..fba03caa6b --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -772,6 +772,7 @@ private: QVector& out_vertices, QVector& out_normals); std::tuple requestData(QUrl& url); + std::tuple requestEmbeddedData(QString binaryUrl); QNetworkReply* request(QUrl& url, bool isTest); bool doesResourceExist(const QString& url); From 997660d430b6341a78a613d07916d76425a5ea6e Mon Sep 17 00:00:00 2001 From: raveenajain Date: Thu, 31 Jan 2019 13:50:17 -0800 Subject: [PATCH 15/62] review changes --- libraries/fbx/src/GLTFSerializer.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index c2fdc4f0bd..01e3cce560 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -843,14 +843,9 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; continue; } - if (accessor.type == 3) { - for (int n = 0; n < colors.size(); n = n + 4) { - mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); - } - } else { - for (int n = 0; n < colors.size(); n = n + 3) { - mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); - } + int stride = (accessor.type == GLTFAccessorType::VEC4) ? 4 : 3; + for (int n = 0; n < colors.size() - 3; n += stride) { + mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); } } else if (key == "TEXCOORD_0") { QVector texcoords; From d0fb09a3bd2a792d8cef2717a4beff8b7808b3c4 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 31 Jan 2019 14:58:58 -0800 Subject: [PATCH 16/62] Assign lowest available suffix when display-names collide --- assignment-client/src/avatars/AvatarMixer.cpp | 62 +++++++++++++++---- assignment-client/src/avatars/AvatarMixer.h | 20 +++++- libraries/avatars/src/AvatarData.cpp | 9 ++- 3 files changed, 74 insertions(+), 17 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 500772c1b5..f885b8110d 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -38,6 +38,19 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; // FIXME - what we'd actually like to do is send to users at ~50% of their present rate down to 30hz. Assume 90 for now. const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; +const QRegularExpression AvatarMixer::suffixedNamePattern { R"(^\s*(.+)\s*_(\d)+\s*$)" }; + +// Lexicographic comparison: +bool AvatarMixer::SessionDisplayName::operator<(const SessionDisplayName& rhs) const { + if (_baseName < rhs._baseName) { + return true; + } else if (rhs._baseName < _baseName) { + return false; + } else { + return _suffix < rhs._suffix; + } +} + AvatarMixer::AvatarMixer(ReceivedMessage& message) : ThreadedAssignment(message), _slavePool(&_slaveSharedData) @@ -313,27 +326,40 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { bool sendIdentity = false; if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) { AvatarData& avatar = nodeData->getAvatar(); - const QString& existingBaseDisplayName = nodeData->getBaseDisplayName(); - if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) { - _sessionDisplayNames.remove(existingBaseDisplayName); + const QString& existingBaseDisplayName = nodeData->getAvatar().getSessionDisplayName(); + if (!existingBaseDisplayName.isEmpty()) { + SessionDisplayName existingDisplayName { existingBaseDisplayName }; + + auto suffixMatch = suffixedNamePattern.match(existingBaseDisplayName); + if (suffixMatch.hasMatch()) { + existingDisplayName._baseName = suffixMatch.captured(1); + existingDisplayName._suffix = suffixMatch.captured(2).toInt(); + } + _sessionDisplayNames.erase(existingDisplayName); } QString baseName = avatar.getDisplayName().trimmed(); const QRegularExpression curses { "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?). baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk. - const QRegularExpression trailingDigits { "\\s*(_\\d+\\s*)?(\\s*\\n[^$]*)?$" }; // trailing whitespace "_123" and any subsequent lines + static const QRegularExpression trailingDigits { R"(\s*(_\d+\s*)?(\s*\n[^$]*)?$)" }; // trailing whitespace "_123" and any subsequent lines baseName = baseName.remove(trailingDigits); if (baseName.isEmpty()) { baseName = "anonymous"; } - QPair& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want. - int& highWater = soFar.first; - nodeData->setBaseDisplayName(baseName); - QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName; + SessionDisplayName newDisplayName { baseName }; + auto nameIter = _sessionDisplayNames.lower_bound(newDisplayName); + if (nameIter != _sessionDisplayNames.end() && nameIter->_baseName == baseName) { + // Existing instance(s) of name; find first free suffix + while (*nameIter == newDisplayName && ++newDisplayName._suffix && ++nameIter != _sessionDisplayNames.end()) + ; + } + + _sessionDisplayNames.insert(newDisplayName); + QString sessionDisplayName = (newDisplayName._suffix > 0) ? baseName + "_" + QString::number(newDisplayName._suffix) : baseName; avatar.setSessionDisplayName(sessionDisplayName); - highWater++; - soFar.second++; // refcount + nodeData->setBaseDisplayName(baseName); + nodeData->flagIdentityChange(); nodeData->setAvatarSessionDisplayNameMustChange(false); sendIdentity = true; @@ -410,9 +436,19 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { QMutexLocker nodeDataLocker(&avatarNode->getLinkedData()->getMutex()); AvatarMixerClientData* nodeData = dynamic_cast(avatarNode->getLinkedData()); const QString& baseDisplayName = nodeData->getBaseDisplayName(); - // No sense guarding against very rare case of a node with no entry, as this will work without the guard and do one less lookup in the common case. - if (--_sessionDisplayNames[baseDisplayName].second <= 0) { - _sessionDisplayNames.remove(baseDisplayName); + const QString& displayName = nodeData->getAvatar().getSessionDisplayName(); + SessionDisplayName exitingDisplayName { displayName }; + + auto suffixMatch = suffixedNamePattern.match(displayName); + if (suffixMatch.hasMatch()) { + exitingDisplayName._baseName = suffixMatch.captured(1); + exitingDisplayName._suffix = suffixMatch.captured(2).toInt(); + } + auto displayNameIter = _sessionDisplayNames.find(exitingDisplayName); + if (displayNameIter == _sessionDisplayNames.end()) { + qCDebug(avatars, "Exiting avatar displayname", displayName, "not found"); + } else { + _sessionDisplayNames.erase(displayNameIter); } } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 764656a2d5..2992e19b8f 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -15,6 +15,7 @@ #ifndef hifi_AvatarMixer_h #define hifi_AvatarMixer_h +#include #include #include @@ -88,7 +89,24 @@ private: RateCounter<> _broadcastRate; p_high_resolution_clock::time_point _lastDebugMessage; - QHash> _sessionDisplayNames; + + // Pair of basename + uniquifying integer suffix. + struct SessionDisplayName { + explicit SessionDisplayName(QString baseName = QString(), int suffix = 0) : + _baseName(baseName), + _suffix(suffix) { } + // Does lexicographic ordering: + bool operator<(const SessionDisplayName& rhs) const; + bool operator==(const SessionDisplayName& rhs) const { + return _baseName == rhs._baseName && _suffix == rhs._suffix; + } + + QString _baseName; + int _suffix; + }; + static const QRegularExpression suffixedNamePattern; + + std::set _sessionDisplayNames; quint64 _displayNameManagementElapsedTime { 0 }; // total time spent in broadcastAvatarData/display name management... since last stats window quint64 _ignoreCalculationElapsedTime { 0 }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4e95774efb..c733cfa291 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -632,9 +632,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // include jointData if there is room for the most minimal section. i.e. no translations or rotations. IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, AvatarDataPacket::minJointDataSize(numJoints)) { - // Allow for faux joints + translation bit-vector: - const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) - + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE; + // Minimum space required for another rotation joint - + // size of joint + following translation bit-vector + translation scale + faux joints: + const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize + + sizeof(float) + AvatarDataPacket::FAUX_JOINTS_SIZE; + auto startSection = destinationBuffer; // compute maxTranslationDimension before we send any joint data. @@ -724,6 +726,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; + // Note minSizeForJoint is conservative since there isn't a following bit-vector + scale. if (packetEnd - destinationBuffer >= minSizeForJoint) { if (!data.translationIsDefaultPose) { if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) From 3d1edf4d9eda443e49e6f1046df3dddb70c419af Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 31 Jan 2019 15:00:50 -0800 Subject: [PATCH 17/62] Make small code improvements to PrepareJointsTask --- .../model-baker/src/model-baker/PrepareJointsTask.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index 20715cfed7..3b1a57cb43 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -62,10 +62,11 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu // Apply joint metadata from FST file mappings for (const auto& jointIn : jointsIn) { jointsOut.push_back(jointIn); - auto& jointOut = jointsOut[jointsOut.size()-1]; + auto& jointOut = jointsOut.back(); - if (jointNameMapping.contains(jointNameMapping.key(jointIn.name))) { - jointOut.name = jointNameMapping.key(jointIn.name); + auto jointNameMapKey = jointNameMapping.key(jointIn.name); + if (jointNameMapping.contains(jointNameMapKey)) { + jointOut.name = jointNameMapKey; } jointIndices.insert(jointOut.name, (int)jointsOut.size()); @@ -75,11 +76,11 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu auto offsets = getJointRotationOffsets(mapping); for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { QString jointName = itr.key(); - glm::quat rotationOffset = itr.value(); int jointIndex = jointIndices.value(jointName) - 1; if (jointIndex != -1) { + glm::quat rotationOffset = itr.value(); jointRotationOffsets.insert(jointIndex, rotationOffset); + qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; } - qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; } } From e4ffc93bc254a4fb52f68ef4770efa730926daee Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 31 Jan 2019 15:19:05 -0800 Subject: [PATCH 18/62] Revert a cherry-picked workaround accidently committed This reverts commit 08b21109c1daac786a67490bfef658a359bf47bb. Stupid git - I never explicitly added this to the branch! --- libraries/avatars/src/AvatarData.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c733cfa291..4e95774efb 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -632,11 +632,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // include jointData if there is room for the most minimal section. i.e. no translations or rotations. IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, AvatarDataPacket::minJointDataSize(numJoints)) { - // Minimum space required for another rotation joint - - // size of joint + following translation bit-vector + translation scale + faux joints: - const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize + - sizeof(float) + AvatarDataPacket::FAUX_JOINTS_SIZE; - + // Allow for faux joints + translation bit-vector: + const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE; auto startSection = destinationBuffer; // compute maxTranslationDimension before we send any joint data. @@ -726,7 +724,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; - // Note minSizeForJoint is conservative since there isn't a following bit-vector + scale. if (packetEnd - destinationBuffer >= minSizeForJoint) { if (!data.translationIsDefaultPose) { if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) From 322030fccdf481c524c80da328761a350955d6f3 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 31 Jan 2019 16:07:26 -0800 Subject: [PATCH 19/62] Remove unused variable; fix use of qCDebug --- assignment-client/src/avatars/AvatarMixer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index f885b8110d..801f28c6f5 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -435,7 +435,6 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { { // decrement sessionDisplayNames table and possibly remove QMutexLocker nodeDataLocker(&avatarNode->getLinkedData()->getMutex()); AvatarMixerClientData* nodeData = dynamic_cast(avatarNode->getLinkedData()); - const QString& baseDisplayName = nodeData->getBaseDisplayName(); const QString& displayName = nodeData->getAvatar().getSessionDisplayName(); SessionDisplayName exitingDisplayName { displayName }; @@ -446,7 +445,7 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { } auto displayNameIter = _sessionDisplayNames.find(exitingDisplayName); if (displayNameIter == _sessionDisplayNames.end()) { - qCDebug(avatars, "Exiting avatar displayname", displayName, "not found"); + qCDebug(avatars) << "Exiting avatar displayname" << displayName << "not found"; } else { _sessionDisplayNames.erase(displayNameIter); } From d1d8832e7a9a9c32791c2f153738bf2dbc360f39 Mon Sep 17 00:00:00 2001 From: raveenajain Date: Fri, 1 Feb 2019 11:27:43 -0800 Subject: [PATCH 20/62] read in embedded data --- libraries/fbx/src/GLTFSerializer.cpp | 29 ++++++++++++++++++++-------- libraries/fbx/src/GLTFSerializer.h | 3 ++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 6db298ae58..2ffcd2c728 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -352,9 +352,15 @@ bool GLTFSerializer::addImage(const QJsonObject& object) { QString mime; getStringVal(object, "uri", image.uri, image.defined); + if (image.uri.contains("data:image/png;base64,")) { + image.mimeType = getImageMimeType("image/png"); + } + if (image.uri.contains("data:image/jpeg;base64,")) { + image.mimeType = getImageMimeType("image/jpeg"); + } if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); - } + } getIntVal(object, "bufferView", image.bufferView, image.defined); _file.images.push_back(image); @@ -941,9 +947,8 @@ HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHas bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) { bool success; - if (url.contains("data:application/octet-stream;base64,") || url.contains("data:image/png;base64,") || url.contains("data:image/jpeg;base64,")) { - QString binaryUrl = url.split(",")[1]; - std::tie(success, outdata) = requestEmbeddedData(binaryUrl); + if (url.contains("data:application/octet-stream;base64,")) { + std::tie(success, outdata) = std::make_tuple(true, requestEmbeddedData(url)); } else { QUrl binaryUrl = _url.resolved(url); std::tie(success, outdata) = requestData(binaryUrl); @@ -980,11 +985,13 @@ std::tuple GLTFSerializer::requestData(QUrl& url) { } } -std::tuple GLTFSerializer::requestEmbeddedData(QString binaryUrl) { - QByteArray urlBin = binaryUrl.toUtf8(); - QByteArray result = QByteArray::fromBase64(urlBin); +QByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { + QString binaryUrl = url.split(",")[1]; - return std::make_tuple(true, result); + QByteArray urlBin = binaryUrl.toUtf8(); + QByteArray data = QByteArray::fromBase64(urlBin); + + return data; } @@ -1018,11 +1025,17 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; + QString fname = QUrl(url).fileName(); QUrl textureUrl = _url.resolved(url); qCDebug(modelformat) << "fname: " << fname; fbxtex.name = fname; fbxtex.filename = textureUrl.toEncoded(); + + if (url.contains("data:image/jpeg;base64,") || url.contains("data:image/png;base64,")) { + QByteArray result = requestEmbeddedData(url); + fbxtex.content = result; + } } return fbxtex; } diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h index fba03caa6b..57ea126a7b 100755 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -772,7 +772,8 @@ private: QVector& out_vertices, QVector& out_normals); std::tuple requestData(QUrl& url); - std::tuple requestEmbeddedData(QString binaryUrl); + QByteArray requestEmbeddedData(const QString& url); + QNetworkReply* request(QUrl& url, bool isTest); bool doesResourceExist(const QString& url); From d109c0fb1b6ab3d543d30442c6375b636b29f54c Mon Sep 17 00:00:00 2001 From: raveenajain Date: Tue, 5 Feb 2019 09:23:10 -0800 Subject: [PATCH 21/62] feedback changes --- libraries/fbx/src/GLTFSerializer.cpp | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 2ffcd2c728..197b3ee14c 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -354,13 +354,11 @@ bool GLTFSerializer::addImage(const QJsonObject& object) { getStringVal(object, "uri", image.uri, image.defined); if (image.uri.contains("data:image/png;base64,")) { image.mimeType = getImageMimeType("image/png"); - } - if (image.uri.contains("data:image/jpeg;base64,")) { + } else if (image.uri.contains("data:image/jpeg;base64,")) { image.mimeType = getImageMimeType("image/jpeg"); - } - if (getStringVal(object, "mimeType", mime, image.defined)) { + } else if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); - } + } getIntVal(object, "bufferView", image.bufferView, image.defined); _file.images.push_back(image); @@ -948,7 +946,8 @@ bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) { bool success; if (url.contains("data:application/octet-stream;base64,")) { - std::tie(success, outdata) = std::make_tuple(true, requestEmbeddedData(url)); + outdata = requestEmbeddedData(url); + success = outdata.isEmpty() ? false : true; } else { QUrl binaryUrl = _url.resolved(url); std::tie(success, outdata) = requestData(binaryUrl); @@ -985,13 +984,9 @@ std::tuple GLTFSerializer::requestData(QUrl& url) { } } -QByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { - QString binaryUrl = url.split(",")[1]; - - QByteArray urlBin = binaryUrl.toUtf8(); - QByteArray data = QByteArray::fromBase64(urlBin); - - return data; +QByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { + QString binaryUrl = url.split(",")[1]; + return binaryUrl.isEmpty() ? QByteArray() : QByteArray::fromBase64(binaryUrl.toUtf8()); } @@ -1033,8 +1028,7 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { fbxtex.filename = textureUrl.toEncoded(); if (url.contains("data:image/jpeg;base64,") || url.contains("data:image/png;base64,")) { - QByteArray result = requestEmbeddedData(url); - fbxtex.content = result; + fbxtex.content = requestEmbeddedData(url); } } return fbxtex; From 21fa1878cb2904b12258cd95c6f7c7ff5d5cb9bb Mon Sep 17 00:00:00 2001 From: raveenajain Date: Tue, 5 Feb 2019 09:28:47 -0800 Subject: [PATCH 22/62] spaces --- libraries/fbx/src/GLTFSerializer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 197b3ee14c..674b4b1cf1 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -358,7 +358,7 @@ bool GLTFSerializer::addImage(const QJsonObject& object) { image.mimeType = getImageMimeType("image/jpeg"); } else if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); - } + } getIntVal(object, "bufferView", image.bufferView, image.defined); _file.images.push_back(image); @@ -984,7 +984,7 @@ std::tuple GLTFSerializer::requestData(QUrl& url) { } } -QByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { +QByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { QString binaryUrl = url.split(",")[1]; return binaryUrl.isEmpty() ? QByteArray() : QByteArray::fromBase64(binaryUrl.toUtf8()); } From 253e3554af8e5a483b44757a3a52e8ee4ac739f8 Mon Sep 17 00:00:00 2001 From: raveenajain Date: Tue, 5 Feb 2019 10:18:34 -0800 Subject: [PATCH 23/62] feedback --- libraries/fbx/src/GLTFSerializer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 674b4b1cf1..e6b4652d6a 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -356,7 +356,8 @@ bool GLTFSerializer::addImage(const QJsonObject& object) { image.mimeType = getImageMimeType("image/png"); } else if (image.uri.contains("data:image/jpeg;base64,")) { image.mimeType = getImageMimeType("image/jpeg"); - } else if (getStringVal(object, "mimeType", mime, image.defined)) { + } + if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); } getIntVal(object, "bufferView", image.bufferView, image.defined); @@ -947,7 +948,7 @@ bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) { if (url.contains("data:application/octet-stream;base64,")) { outdata = requestEmbeddedData(url); - success = outdata.isEmpty() ? false : true; + success = !outdata.isEmpty(); } else { QUrl binaryUrl = _url.resolved(url); std::tie(success, outdata) = requestData(binaryUrl); From cfaa841746ac97e3ef9f49ca0ccf087b98d4ae85 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 5 Feb 2019 12:34:42 -0800 Subject: [PATCH 24/62] Add output silence-detection to the noise gate processing --- libraries/audio/src/AudioGate.cpp | 108 +++++++++++++++++++++--------- libraries/audio/src/AudioGate.h | 9 ++- 2 files changed, 82 insertions(+), 35 deletions(-) diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index e9cdf832d2..0df46ac532 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -138,8 +138,8 @@ public: int32_t hysteresis(int32_t peak); int32_t envelope(int32_t attn); - virtual void process(int16_t* input, int16_t* output, int numFrames) = 0; - virtual void removeDC(int16_t* input, int16_t* output, int numFrames) = 0; + virtual bool process(int16_t* input, int16_t* output, int numFrames) = 0; + virtual bool removeDC(int16_t* input, int16_t* output, int numFrames) = 0; }; GateImpl::GateImpl(int sampleRate) { @@ -403,14 +403,15 @@ public: GateMono(int sampleRate) : GateImpl(sampleRate) {} // mono input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateMono::process(int16_t* input, int16_t* output, int numFrames) { +bool GateMono::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -453,15 +454,21 @@ void GateMono::process(int16_t* input, int16_t* output, int numFrames) { x = MULQ31(x, attn); // store 16-bit output - output[n] = (int16_t)saturateQ30(x); + x = saturateQ30(x); + output[n] = (int16_t)x; + + mask |= x; } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -471,8 +478,13 @@ void GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x); // store 16-bit output - output[n] = (int16_t)saturateQ30(x); + x = saturateQ30(x); + output[n] = (int16_t)x; + + mask |= x; } + + return mask != 0; } // @@ -489,14 +501,15 @@ public: GateStereo(int sampleRate) : GateImpl(sampleRate) {} // interleaved stereo input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateStereo::process(int16_t* input, int16_t* output, int numFrames) { +bool GateStereo::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -541,16 +554,23 @@ void GateStereo::process(int16_t* input, int16_t* output, int numFrames) { x1 = MULQ31(x1, attn); // store 16-bit output - output[2*n+0] = (int16_t)saturateQ30(x0); - output[2*n+1] = (int16_t)saturateQ30(x1); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + output[2*n+0] = (int16_t)x0; + output[2*n+1] = (int16_t)x1; + + mask |= (x0 | x1); } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -561,9 +581,15 @@ void GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x0, x1); // store 16-bit output - output[2*n+0] = (int16_t)saturateQ30(x0); - output[2*n+1] = (int16_t)saturateQ30(x1); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + output[2*n+0] = (int16_t)x0; + output[2*n+1] = (int16_t)x1; + + mask |= (x0 | x1); } + + return mask != 0; } // @@ -580,14 +606,15 @@ public: GateQuad(int sampleRate) : GateImpl(sampleRate) {} // interleaved quad input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateQuad::process(int16_t* input, int16_t* output, int numFrames) { +bool GateQuad::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -636,18 +663,27 @@ void GateQuad::process(int16_t* input, int16_t* output, int numFrames) { x3 = MULQ31(x3, attn); // store 16-bit output - output[4*n+0] = (int16_t)saturateQ30(x0); - output[4*n+1] = (int16_t)saturateQ30(x1); - output[4*n+2] = (int16_t)saturateQ30(x2); - output[4*n+3] = (int16_t)saturateQ30(x3); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + x2 = saturateQ30(x2); + x3 = saturateQ30(x3); + output[4*n+0] = (int16_t)x0; + output[4*n+1] = (int16_t)x1; + output[4*n+2] = (int16_t)x2; + output[4*n+3] = (int16_t)x3; + + mask |= (x0 | x1 | x2 | x3); } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -660,11 +696,19 @@ void GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x0, x1, x2, x3); // store 16-bit output - output[4*n+0] = (int16_t)saturateQ30(x0); - output[4*n+1] = (int16_t)saturateQ30(x1); - output[4*n+2] = (int16_t)saturateQ30(x2); - output[4*n+3] = (int16_t)saturateQ30(x3); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + x2 = saturateQ30(x2); + x3 = saturateQ30(x3); + output[4*n+0] = (int16_t)x0; + output[4*n+1] = (int16_t)x1; + output[4*n+2] = (int16_t)x2; + output[4*n+3] = (int16_t)x3; + + mask |= (x0 | x1 | x2 | x3); } + + return mask != 0; } // @@ -721,12 +765,12 @@ AudioGate::~AudioGate() { delete _impl; } -void AudioGate::render(int16_t* input, int16_t* output, int numFrames) { - _impl->process(input, output, numFrames); +bool AudioGate::render(int16_t* input, int16_t* output, int numFrames) { + return _impl->process(input, output, numFrames); } -void AudioGate::removeDC(int16_t* input, int16_t* output, int numFrames) { - _impl->removeDC(input, output, numFrames); +bool AudioGate::removeDC(int16_t* input, int16_t* output, int numFrames) { + return _impl->removeDC(input, output, numFrames); } void AudioGate::setThreshold(float threshold) { diff --git a/libraries/audio/src/AudioGate.h b/libraries/audio/src/AudioGate.h index d4ae3c5fe8..6fc7ca83df 100644 --- a/libraries/audio/src/AudioGate.h +++ b/libraries/audio/src/AudioGate.h @@ -18,9 +18,12 @@ public: AudioGate(int sampleRate, int numChannels); ~AudioGate(); - // interleaved int16_t input/output (in-place is allowed) - void render(int16_t* input, int16_t* output, int numFrames); - void removeDC(int16_t* input, int16_t* output, int numFrames); + // + // Process interleaved int16_t input/output (in-place is allowed). + // Returns true when output is non-zero. + // + bool render(int16_t* input, int16_t* output, int numFrames); + bool removeDC(int16_t* input, int16_t* output, int numFrames); void setThreshold(float threshold); void setRelease(float release); From 569bef50fdaea8b51abf14c8ed9afc935c84ed16 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 5 Feb 2019 14:26:57 -0800 Subject: [PATCH 25/62] AnimPose operator* optimizations --- interface/src/avatar/AvatarActionHold.cpp | 2 +- interface/src/avatar/MyAvatar.cpp | 6 +- interface/src/avatar/MySkeletonModel.cpp | 6 +- libraries/animation/src/AnimClip.cpp | 8 +- .../animation/src/AnimInverseKinematics.cpp | 14 +-- libraries/animation/src/AnimManipulator.cpp | 2 +- .../src/AnimPoleVectorConstraint.cpp | 2 +- libraries/animation/src/AnimPose.cpp | 36 ++---- libraries/animation/src/AnimPose.h | 11 +- libraries/animation/src/Rig.cpp | 9 +- .../src/avatars-renderer/Avatar.cpp | 4 +- libraries/render-utils/src/AnimDebugDraw.cpp | 8 +- .../render-utils/src/CauterizedModel.cpp | 6 +- libraries/render-utils/src/Model.cpp | 2 +- libraries/render-utils/src/Model.h | 6 +- tests/animation/src/AnimTests.cpp | 105 ++++++++++++++++-- tests/animation/src/AnimTests.h | 1 + 17 files changed, 151 insertions(+), 77 deletions(-) diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 5fb9c9a0ee..a1826076fa 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -569,7 +569,7 @@ void AvatarActionHold::lateAvatarUpdate(const AnimPose& prePhysicsRoomPose, cons } btTransform worldTrans = rigidBody->getWorldTransform(); - AnimPose worldBodyPose(glm::vec3(1), bulletToGLM(worldTrans.getRotation()), bulletToGLM(worldTrans.getOrigin())); + AnimPose worldBodyPose(1.0f, bulletToGLM(worldTrans.getRotation()), bulletToGLM(worldTrans.getOrigin())); // transform the body transform into sensor space with the prePhysics sensor-to-world matrix. // then transform it back into world uisng the postAvatarUpdate sensor-to-world matrix. diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 92d9270d20..1d3fb5a5aa 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2983,7 +2983,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { auto animSkeleton = _skeletonModel->getRig().getAnimSkeleton(); // the rig is in the skeletonModel frame - AnimPose xform(glm::vec3(1), _skeletonModel->getRotation(), _skeletonModel->getTranslation()); + AnimPose xform(1.0f, _skeletonModel->getRotation(), _skeletonModel->getTranslation()); if (_enableDebugDrawDefaultPose && animSkeleton) { glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f); @@ -3028,7 +3028,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose); if (_enableDebugDrawDetailedCollision) { - AnimPose rigToWorldPose(glm::vec3(1.0f), getWorldOrientation() * Quaternions::Y_180, getWorldPosition()); + AnimPose rigToWorldPose(1.0f, getWorldOrientation() * Quaternions::Y_180, getWorldPosition()); const int NUM_DEBUG_COLORS = 8; const glm::vec4 DEBUG_COLORS[NUM_DEBUG_COLORS] = { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), @@ -4835,7 +4835,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat swingTwistDecomposition(hipsinWorldSpace, avatarUpWorld, resultingSwingInWorld, resultingTwistInWorld); // remove scale present from sensorToWorldMatrix - followWorldPose.scale() = glm::vec3(1.0f); + followWorldPose.scale() = 1.0f; if (isActive(Rotation)) { //use the hmd reading for the hips follow diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 26d69841d0..253cc891ee 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -41,7 +41,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { if (myAvatar->isJointPinned(hipsIndex)) { Transform avatarTransform = myAvatar->getTransform(); AnimPose result = AnimPose(worldToSensorMat * avatarTransform.getMatrix() * Matrices::Y_180); - result.scale() = glm::vec3(1.0f, 1.0f, 1.0f); + result.scale() = 1.0f; return result; } @@ -108,7 +108,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Rig::ControllerParameters params; - AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f)); + AnimPose avatarToRigPose(1.0f, Quaternions::Y_180, glm::vec3(0.0f)); glm::mat4 rigToAvatarMatrix = Matrices::Y_180; glm::mat4 avatarToWorldMatrix = createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition()); @@ -127,7 +127,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // preMult 180 is necessary to convert from avatar to rig coordinates. // postMult 180 is necessary to convert head from -z forward to z forward. glm::quat headRot = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180; - params.primaryControllerPoses[Rig::PrimaryControllerType_Head] = AnimPose(glm::vec3(1.0f), headRot, glm::vec3(0.0f)); + params.primaryControllerPoses[Rig::PrimaryControllerType_Head] = AnimPose(1.0f, headRot, glm::vec3(0.0f)); params.primaryControllerFlags[Rig::PrimaryControllerType_Head] = 0; } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 1adc04ee1b..71b876ff8c 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -140,10 +140,10 @@ void AnimClip::copyFromNetworkAnim() { postRot = animSkeleton.getPostRotationPose(animJoint); // cancel out scale - preRot.scale() = glm::vec3(1.0f); - postRot.scale() = glm::vec3(1.0f); + preRot.scale() = 1.0f; + postRot.scale() = 1.0f; - AnimPose rot(glm::vec3(1.0f), hfmAnimRot, glm::vec3()); + AnimPose rot(1.0f, hfmAnimRot, glm::vec3()); // adjust translation offsets, so large translation animatons on the reference skeleton // will be adjusted when played on a skeleton with short limbs. @@ -155,7 +155,7 @@ void AnimClip::copyFromNetworkAnim() { boneLengthScale = glm::length(relDefaultPose.trans()) / glm::length(hfmZeroTrans); } - AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans() + boneLengthScale * (hfmAnimTrans - hfmZeroTrans)); + AnimPose trans = AnimPose(1.0f, glm::quat(), relDefaultPose.trans() + boneLengthScale * (hfmAnimTrans - hfmZeroTrans)); _anim[frame][skeletonJoint] = trans * preRot * rot * postRot; } diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a1809f3438..7af9e81889 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -552,7 +552,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const AnimPose accum = absolutePoses[_hipsIndex]; AnimPose baseParentPose = absolutePoses[_hipsIndex]; for (int i = (int)chainDepth - 1; i >= 0; i--) { - accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfoOut.jointInfoVec[i].rot, jointChainInfoOut.jointInfoVec[i].trans); + accum = accum * AnimPose(1.0f, jointChainInfoOut.jointInfoVec[i].rot, jointChainInfoOut.jointInfoVec[i].trans); postAbsPoses[i] = accum; if (jointChainInfoOut.jointInfoVec[i].jointIndex == topJointIndex) { topChainIndex = i; @@ -734,7 +734,7 @@ void AnimInverseKinematics::computeAndCacheSplineJointInfosForIKTarget(const Ani glm::mat3 m(u, v, glm::cross(u, v)); glm::quat rot = glm::normalize(glm::quat_cast(m)); - AnimPose pose(glm::vec3(1.0f), rot, spline(t)); + AnimPose pose(1.0f, rot, spline(t)); AnimPose offsetPose = pose.inverse() * defaultPose; SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; @@ -767,7 +767,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co const int baseIndex = _hipsIndex; // build spline from tip to base - AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose tipPose = AnimPose(1.0f, target.getRotation(), target.getTranslation()); AnimPose basePose = absolutePoses[baseIndex]; CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _headIndex) { @@ -815,7 +815,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co glm::mat3 m(u, v, glm::cross(u, v)); glm::quat rot = glm::normalize(glm::quat_cast(m)); - AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; + AnimPose desiredAbsPose = AnimPose(1.0f, rot, trans) * splineJointInfo.offsetPose; // apply flex coefficent AnimPose flexedAbsPose; @@ -965,7 +965,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } _relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose; - _relativePoses[_hipsIndex].scale() = glm::vec3(1.0f); + _relativePoses[_hipsIndex].scale() = 1.0f; } // if there is an active jointChainInfo for the hips store the post shifted hips into it. @@ -1753,7 +1753,7 @@ void AnimInverseKinematics::setSecondaryTargets(const AnimContext& context) { AnimPose rigToGeometryPose = AnimPose(glm::inverse(context.getGeometryToRigMatrix())); for (auto& iter : _secondaryTargetsInRigFrame) { AnimPose absPose = rigToGeometryPose * iter.second; - absPose.scale() = glm::vec3(1.0f); + absPose.scale() = 1.0f; AnimPose parentAbsPose; int parentIndex = _skeleton->getParentIndex(iter.first); @@ -1825,7 +1825,7 @@ void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, co const int baseIndex = _hipsIndex; // build spline - AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose tipPose = AnimPose(1.0f, target.getRotation(), target.getTranslation()); AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses); CubicHermiteSplineFunctorWithArcLength spline; diff --git a/libraries/animation/src/AnimManipulator.cpp b/libraries/animation/src/AnimManipulator.cpp index 1146cbb19a..c75c9865bb 100644 --- a/libraries/animation/src/AnimManipulator.cpp +++ b/libraries/animation/src/AnimManipulator.cpp @@ -172,5 +172,5 @@ AnimPose AnimManipulator::computeRelativePoseFromJointVar(const AnimVariantMap& break; } - return AnimPose(glm::vec3(1), relRot, relTrans); + return AnimPose(1.0f, relRot, relTrans); } diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index f017fe2348..96e4e67261 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -95,7 +95,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim AnimPose tipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex); // Look up refVector from animVars, make sure to convert into geom space. - glm::vec3 refVector = midPose.xformVectorFast(_referenceVector); + glm::vec3 refVector = midPose.xformVector(_referenceVector); float refVectorLength = glm::length(refVector); glm::vec3 axis = basePose.trans() - tipPose.trans(); diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index d77514e691..7b80e96ed5 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -14,15 +14,14 @@ #include #include "AnimUtil.h" -const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), - glm::quat(), - glm::vec3(0.0f)); +const AnimPose AnimPose::identity = AnimPose(1.0f, glm::quat(), glm::vec3(0.0f)); AnimPose::AnimPose(const glm::mat4& mat) { static const float EPSILON = 0.0001f; - _scale = extractScale(mat); + glm::vec3 scale = extractScale(mat); // quat_cast doesn't work so well with scaled matrices, so cancel it out. - glm::mat4 tmp = glm::scale(mat, 1.0f / _scale); + glm::mat4 tmp = glm::scale(mat, 1.0f / scale); + _scale = extractUniformScale(scale); _rot = glm::quat_cast(tmp); float lengthSquared = glm::length2(_rot); if (glm::abs(lengthSquared - 1.0f) > EPSILON) { @@ -40,25 +39,15 @@ glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const { return *this * rhs; } -// really slow, but accurate for transforms with non-uniform scale glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { - glm::vec3 xAxis = _rot * glm::vec3(_scale.x, 0.0f, 0.0f); - glm::vec3 yAxis = _rot * glm::vec3(0.0f, _scale.y, 0.0f); - glm::vec3 zAxis = _rot * glm::vec3(0.0f, 0.0f, _scale.z); - glm::mat3 mat(xAxis, yAxis, zAxis); - glm::mat3 transInvMat = glm::inverse(glm::transpose(mat)); - return transInvMat * rhs; -} - -// faster, but does not handle non-uniform scale correctly. -glm::vec3 AnimPose::xformVectorFast(const glm::vec3& rhs) const { return _rot * (_scale * rhs); } AnimPose AnimPose::operator*(const AnimPose& rhs) const { - glm::mat4 result; - glm_mat4u_mul(*this, rhs, result); - return AnimPose(result); + float scale = _scale * rhs._scale; + glm::quat rot = _rot * rhs._rot; + glm::vec3 trans = _trans + (_rot * (_scale * rhs._trans)); + return AnimPose(scale, rot, trans); } AnimPose AnimPose::inverse() const { @@ -71,11 +60,10 @@ AnimPose AnimPose::mirror() const { } AnimPose::operator glm::mat4() const { - glm::vec3 xAxis = _rot * glm::vec3(_scale.x, 0.0f, 0.0f); - glm::vec3 yAxis = _rot * glm::vec3(0.0f, _scale.y, 0.0f); - glm::vec3 zAxis = _rot * glm::vec3(0.0f, 0.0f, _scale.z); - return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f), - glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f)); + glm::vec3 xAxis = _rot * glm::vec3(_scale, 0.0f, 0.0f); + glm::vec3 yAxis = _rot * glm::vec3(0.0f, _scale, 0.0f); + glm::vec3 zAxis = _rot * glm::vec3(0.0f, 0.0f, _scale); + return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f), glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f)); } void AnimPose::blend(const AnimPose& srcPose, float alpha) { diff --git a/libraries/animation/src/AnimPose.h b/libraries/animation/src/AnimPose.h index 1558a6b881..89dcbaf2ab 100644 --- a/libraries/animation/src/AnimPose.h +++ b/libraries/animation/src/AnimPose.h @@ -23,12 +23,11 @@ public: explicit AnimPose(const glm::mat4& mat); explicit AnimPose(const glm::quat& rotIn) : _scale(1.0f), _rot(rotIn), _trans(0.0f) {} AnimPose(const glm::quat& rotIn, const glm::vec3& transIn) : _scale(1.0f), _rot(rotIn), _trans(transIn) {} - AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : _scale(scaleIn), _rot(rotIn), _trans(transIn) {} + AnimPose(float scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : _scale(scaleIn), _rot(rotIn), _trans(transIn) {} static const AnimPose identity; glm::vec3 xformPoint(const glm::vec3& rhs) const; glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow, but accurate for transforms with non-uniform scale - glm::vec3 xformVectorFast(const glm::vec3& rhs) const; // faster, but does not handle non-uniform scale correctly. glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint AnimPose operator*(const AnimPose& rhs) const; @@ -37,8 +36,8 @@ public: AnimPose mirror() const; operator glm::mat4() const; - const glm::vec3& scale() const { return _scale; } - glm::vec3& scale() { return _scale; } + const float scale() const { return _scale; } + float& scale() { return _scale; } const glm::quat& rot() const { return _rot; } glm::quat& rot() { return _rot; } @@ -50,13 +49,13 @@ public: private: friend QDebug operator<<(QDebug debug, const AnimPose& pose); - glm::vec3 _scale { 1.0f }; glm::quat _rot; glm::vec3 _trans; + float _scale { 1.0f }; // uniform scale only. }; inline QDebug operator<<(QDebug debug, const AnimPose& pose) { - debug << "AnimPose, trans = (" << pose.trans().x << pose.trans().y << pose.trans().z << "), rot = (" << pose.rot().x << pose.rot().y << pose.rot().z << pose.rot().w << "), scale = (" << pose.scale().x << pose.scale().y << pose.scale().z << ")"; + debug << "AnimPose, trans = (" << pose.trans().x << pose.trans().y << pose.trans().z << "), rot = (" << pose.rot().x << pose.rot().y << pose.rot().z << pose.rot().w << "), scale =" << pose.scale(); return debug; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 7842ec0804..2fdbc8addb 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -54,6 +54,11 @@ static bool isEqual(const glm::quat& p, const glm::quat& q) { return 1.0f - fabsf(glm::dot(p, q)) <= EPSILON; } +static bool isEqual(const float p, float q) { + const float EPSILON = 0.00001f; + return fabsf(p - q) <= EPSILON; +} + #define ASSERT(cond) assert(cond) // 2 meter tall dude @@ -1329,7 +1334,7 @@ static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& sh } if (slabCount == (DOP14_COUNT / 2) && minDisplacementLen != FLT_MAX) { // we are within the k-dop so push the point along the minimum displacement found - displacementOut = shapePose.xformVectorFast(minDisplacement); + displacementOut = shapePose.xformVector(minDisplacement); return true; } else { // point is outside of kdop @@ -1338,7 +1343,7 @@ static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& sh } else { // point is directly on top of shapeInfo.avgPoint. // push the point out along the x axis. - displacementOut = shapePose.xformVectorFast(shapeInfo.points[0]); + displacementOut = shapePose.xformVector(shapeInfo.points[0]); return true; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 07c1ca9a32..c195206dcb 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1972,12 +1972,12 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { auto& rig = _skeletonModel->getRig(); // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. - AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); + AnimPose modelOffsetWithoutAvatarScale(1.0f, rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. // Typically it will be the unit conversion from cm to m. - float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + float scaleFactor = geomToRigWithoutAvatarScale.scale(); int headTopJoint = rig.indexOfJoint("HeadTop_End"); int headJoint = rig.indexOfJoint("Head"); diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index bf528ee5f0..8944ae7996 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -374,7 +374,7 @@ void AnimDebugDraw::update() { glm::vec4 color = std::get<3>(iter.second); for (int i = 0; i < skeleton->getNumJoints(); i++) { - const float radius = BONE_RADIUS / (absPoses[i].scale().x * rootPose.scale().x); + const float radius = BONE_RADIUS / (absPoses[i].scale() * rootPose.scale()); // draw bone addBone(rootPose, absPoses[i], radius, color, v); @@ -394,16 +394,16 @@ void AnimDebugDraw::update() { glm::vec3 pos = std::get<1>(iter.second); glm::vec4 color = std::get<2>(iter.second); const float radius = POSE_RADIUS; - addBone(AnimPose::identity, AnimPose(glm::vec3(1), rot, pos), radius, color, v); + addBone(AnimPose::identity, AnimPose(1.0f, rot, pos), radius, color, v); } - AnimPose myAvatarPose(glm::vec3(1), DebugDraw::getInstance().getMyAvatarRot(), DebugDraw::getInstance().getMyAvatarPos()); + AnimPose myAvatarPose(1.0f, DebugDraw::getInstance().getMyAvatarRot(), DebugDraw::getInstance().getMyAvatarPos()); for (auto& iter : myAvatarMarkerMap) { glm::quat rot = std::get<0>(iter.second); glm::vec3 pos = std::get<1>(iter.second); glm::vec4 color = std::get<2>(iter.second); const float radius = POSE_RADIUS; - addBone(myAvatarPose, AnimPose(glm::vec3(1), rot, pos), radius, color, v); + addBone(myAvatarPose, AnimPose(1.0f, rot, pos), radius, color, v); } // draw rays from shared DebugDraw singleton diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 81a81c5602..b70925201a 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -122,7 +122,7 @@ void CauterizedModel::updateClusterMatrices() { if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); - Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform jointTransform(jointPose.rot(), glm::vec3(jointPose.scale()), jointPose.trans()); Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); @@ -138,7 +138,7 @@ void CauterizedModel::updateClusterMatrices() { if (!_cauterizeBoneSet.empty()) { AnimPose cauterizePose = _rig.getJointPose(_rig.indexOfJoint("Neck")); - cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f); + cauterizePose.scale() = 0.0001f; static const glm::mat4 zeroScale( glm::vec4(0.0001f, 0.0f, 0.0f, 0.0f), @@ -161,7 +161,7 @@ void CauterizedModel::updateClusterMatrices() { // not cauterized so just copy the value from the non-cauterized version. state.clusterDualQuaternions[j] = _meshStates[i].clusterDualQuaternions[j]; } else { - Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); + Transform jointTransform(cauterizePose.rot(), glm::vec3(cauterizePose.scale()), cauterizePose.trans()); Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index da8dceb176..c68c9259e5 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1368,7 +1368,7 @@ void Model::updateClusterMatrices() { if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); - Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform jointTransform(jointPose.rot(), glm::vec3(jointPose.scale()), jointPose.trans()); Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 16e08c2b23..cdd10a560a 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -298,9 +298,9 @@ public: TransformDualQuaternion() {} TransformDualQuaternion(const glm::mat4& m) { AnimPose p(m); - _scale.x = p.scale().x; - _scale.y = p.scale().y; - _scale.z = p.scale().z; + _scale.x = p.scale(); + _scale.y = p.scale(); + _scale.z = p.scale(); _scale.w = 0.0f; _dq = DualQuaternion(p.rot(), p.trans()); } diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 0cd9571e22..2a49846b6b 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -22,9 +22,11 @@ #include #include #include +#include QTEST_MAIN(AnimTests) + const float TEST_EPSILON = 0.001f; void AnimTests::initTestCase() { @@ -372,16 +374,10 @@ void AnimTests::testAnimPose() { const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); - std::vector scaleVec = { - glm::vec3(1), - glm::vec3(2.0f, 1.0f, 1.0f), - glm::vec3(1.0f, 0.5f, 1.0f), - glm::vec3(1.0f, 1.0f, 1.5f), - glm::vec3(2.0f, 0.5f, 1.5f), - glm::vec3(-2.0f, 0.5f, 1.5f), - glm::vec3(2.0f, -0.5f, 1.5f), - glm::vec3(2.0f, 0.5f, -1.5f), - glm::vec3(-2.0f, -0.5f, -1.5f), + std::vector scaleVec = { + 1.0f, + 2.0f, + 0.5f }; std::vector rotVec = { @@ -411,7 +407,7 @@ void AnimTests::testAnimPose() { for (auto& trans : transVec) { // build a matrix the old fashioned way. - glm::mat4 scaleMat = glm::scale(glm::mat4(), scale); + glm::mat4 scaleMat = glm::scale(glm::mat4(), glm::vec3(scale)); glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); glm::mat4 rawMat = rotTransMat * scaleMat; @@ -429,7 +425,7 @@ void AnimTests::testAnimPose() { for (auto& trans : transVec) { // build a matrix the old fashioned way. - glm::mat4 scaleMat = glm::scale(glm::mat4(), scale); + glm::mat4 scaleMat = glm::scale(glm::mat4(), glm::vec3(scale)); glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); glm::mat4 rawMat = rotTransMat * scaleMat; @@ -445,6 +441,91 @@ void AnimTests::testAnimPose() { } } +void AnimTests::testAnimPoseMultiply() { + const float PI = (float)M_PI; + const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); + const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + + std::vector scaleVec = { + 1.0f, + 2.0f, + 0.5f, + }; + + std::vector rotVec = { + glm::quat(), + ROT_X_90, + ROT_Y_180, + ROT_Z_30, + ROT_X_90 * ROT_Y_180 * ROT_Z_30, + -ROT_Y_180 + }; + + std::vector transVec = { + glm::vec3(), + glm::vec3(10.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 5.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 7.5f), + glm::vec3(10.0f, 5.0f, 7.5f), + glm::vec3(-10.0f, 5.0f, 7.5f), + glm::vec3(10.0f, -5.0f, 7.5f), + glm::vec3(10.0f, 5.0f, -7.5f) + }; + + const float TEST_EPSILON = 0.001f; + + std::vector matrixVec; + std::vector poseVec; + + for (auto& scale : scaleVec) { + for (auto& rot : rotVec) { + for (auto& trans : transVec) { + + // build a matrix the old fashioned way. + glm::mat4 scaleMat = glm::scale(glm::mat4(), glm::vec3(scale)); + glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); + glm::mat4 rawMat = rotTransMat * scaleMat; + + matrixVec.push_back(rawMat); + + // use an anim pose to build a matrix by parts. + AnimPose pose(scale, rot, trans); + poseVec.push_back(pose); + } + } + } + + for (int i = 0; i < matrixVec.size(); i++) { + for (int j = 0; j < matrixVec.size(); j++) { + + // multiply the matrices together + glm::mat4 matrix = matrixVec[i] * matrixVec[j]; + + // convert to matrix (note this will remove sheer from the matrix) + AnimPose resultA(matrix); + + // multiply the poses together directly + AnimPose resultB = poseVec[i] * poseVec[j]; + + /* + qDebug() << "matrixVec[" << i << "] =" << matrixVec[i]; + qDebug() << "matrixVec[" << j << "] =" << matrixVec[j]; + qDebug() << "matrixResult =" << resultA; + + qDebug() << "poseVec[" << i << "] =" << poseVec[i]; + qDebug() << "poseVec[" << j << "] =" << poseVec[j]; + qDebug() << "poseResult =" << resultB; + */ + + // compare results. + QCOMPARE_WITH_ABS_ERROR(resultA.scale(), resultB.scale(), TEST_EPSILON); + QCOMPARE_WITH_ABS_ERROR(resultA.rot(), resultB.rot(), TEST_EPSILON); + QCOMPARE_WITH_ABS_ERROR(resultA.trans(), resultB.trans(), TEST_EPSILON); + } + } +} + void AnimTests::testExpressionTokenizer() { QString str = "(10 + x) >= 20.1 && (y != !z)"; AnimExpression e("x"); diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index 439793f21d..637611e8c4 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -27,6 +27,7 @@ private slots: void testVariant(); void testAccumulateTime(); void testAnimPose(); + void testAnimPoseMultiply(); void testExpressionTokenizer(); void testExpressionParser(); void testExpressionEvaluator(); From d8644a2745cccbba5ac6041bd52c6eb22ef78a77 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 5 Feb 2019 15:06:29 -0800 Subject: [PATCH 26/62] Simplify isEqual computation for vectors used in Rig --- libraries/animation/src/Rig.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 2fdbc8addb..6cbf881157 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -39,14 +39,13 @@ static int nextRigId = 1; static std::map rigRegistry; static std::mutex rigRegistryMutex; +static bool isEqual(const float p, float q) { + const float EPSILON = 0.00001f; + return fabsf(p - q) <= EPSILON; +} + static bool isEqual(const glm::vec3& u, const glm::vec3& v) { - const float EPSILON = 0.0001f; - float uLen = glm::length(u); - if (uLen == 0.0f) { - return glm::length(v) <= EPSILON; - } else { - return (glm::length(u - v) / uLen) <= EPSILON; - } + return isEqual(u.x, v.x) && isEqual(u.y, v.y) && isEqual(u.z, v.z); } static bool isEqual(const glm::quat& p, const glm::quat& q) { @@ -54,11 +53,6 @@ static bool isEqual(const glm::quat& p, const glm::quat& q) { return 1.0f - fabsf(glm::dot(p, q)) <= EPSILON; } -static bool isEqual(const float p, float q) { - const float EPSILON = 0.00001f; - return fabsf(p - q) <= EPSILON; -} - #define ASSERT(cond) assert(cond) // 2 meter tall dude From f9afe6fe43e4885baefa3a0dd466a7525156ff5b Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 5 Feb 2019 16:39:03 -0800 Subject: [PATCH 27/62] Detect loudness and clipping on the raw audio input --- libraries/audio-client/src/AudioClient.cpp | 61 +++++++++++----------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index b2cba2351f..e738b326e6 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1075,45 +1075,25 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { void AudioClient::handleAudioInput(QByteArray& audioBuffer) { if (!_audioPaused) { - if (_muted) { - _lastInputLoudness = 0.0f; - _timeSinceLastClip = 0.0f; - } else { + + bool audioGateOpen = false; + + if (!_muted) { int16_t* samples = reinterpret_cast(audioBuffer.data()); int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); if (_isNoiseGateEnabled) { // The audio gate includes DC removal - _audioGate->render(samples, samples, numFrames); + audioGateOpen = _audioGate->render(samples, samples, numFrames); } else { - _audioGate->removeDC(samples, samples, numFrames); - } - - int32_t loudness = 0; - assert(numSamples < 65536); // int32_t loudness cannot overflow - bool didClip = false; - for (int i = 0; i < numSamples; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)samples[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / numSamples; - - if (didClip) { - _timeSinceLastClip = 0.0f; - } else if (_timeSinceLastClip >= 0.0f) { - _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + audioGateOpen = _audioGate->removeDC(samples, samples, numFrames); } emit inputReceived(audioBuffer); } - emit inputLoudnessChanged(_lastInputLoudness); - - // state machine to detect gate opening and closing - bool audioGateOpen = (_lastInputLoudness != 0.0f); + // detect gate opening and closing bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed _audioGateOpen = audioGateOpen; @@ -1186,10 +1166,29 @@ void AudioClient::handleMicAudioInput() { static int16_t networkAudioSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; while (_inputRingBuffer.samplesAvailable() >= inputSamplesRequired) { - if (_muted) { - _inputRingBuffer.shiftReadPosition(inputSamplesRequired); - } else { - _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); + + _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); + + // detect loudness and clipping on the raw input + int32_t loudness = 0; + bool didClip = false; + for (int i = 0; i < inputSamplesRequired; ++i) { + const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); + int32_t sample = std::abs((int32_t)inputAudioSamples.get()[i]); + loudness += sample; + didClip |= (sample > CLIPPING_THRESHOLD); + } + _lastInputLoudness = (float)loudness / inputSamplesRequired; + + if (didClip) { + _timeSinceLastClip = 0.0f; + } else if (_timeSinceLastClip >= 0.0f) { + _timeSinceLastClip += AudioConstants::NETWORK_FRAME_SECS; + } + + emit inputLoudnessChanged(_lastInputLoudness); + + if (!_muted) { possibleResampling(_inputToNetworkResampler, inputAudioSamples.get(), networkAudioSamples, inputSamplesRequired, numNetworkSamples, From 3ee448a89a91b3737b316ad693964fa789342d72 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 5 Feb 2019 17:32:15 -0800 Subject: [PATCH 28/62] fix scattering and working on resource cache bug --- libraries/animation/src/AnimationCache.cpp | 7 +- libraries/animation/src/AnimationCache.h | 7 +- libraries/audio/src/SoundCache.cpp | 7 +- libraries/audio/src/SoundCache.h | 5 +- .../src/RenderableModelEntityItem.cpp | 6 +- libraries/fbx/src/FBXSerializer_Material.cpp | 13 ++- libraries/fbx/src/FSTReader.cpp | 4 +- libraries/graphics/src/graphics/Material.cpp | 2 +- .../src/model-networking/MaterialCache.cpp | 8 +- .../src/model-networking/MaterialCache.h | 3 +- .../src/model-networking/ModelCache.cpp | 32 +++++-- .../src/model-networking/ModelCache.h | 12 ++- .../src/model-networking/ShaderCache.cpp | 8 +- .../src/model-networking/ShaderCache.h | 5 +- .../src/model-networking/TextureCache.cpp | 51 ++++++---- .../src/model-networking/TextureCache.h | 8 +- libraries/networking/src/ResourceCache.cpp | 95 ++++++++++++++----- libraries/networking/src/ResourceCache.h | 19 ++-- .../recording/src/recording/ClipCache.cpp | 5 +- libraries/recording/src/recording/ClipCache.h | 5 +- .../render-utils/src/RenderPipelines.cpp | 2 +- 21 files changed, 199 insertions(+), 105 deletions(-) diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index f7a7dd861a..4e988334f9 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -36,12 +36,13 @@ AnimationPointer AnimationCache::getAnimation(const QUrl& url) { return getResource(url).staticCast(); } -QSharedPointer AnimationCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { +QSharedPointer AnimationCache::createResource(const QUrl& url) { return QSharedPointer(new Animation(url), &Resource::deleter); } -Animation::Animation(const QUrl& url) : Resource(url) {} +QSharedPointer AnimationCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new Animation(*resource.staticCast().data()), &Resource::deleter); +} AnimationReader::AnimationReader(const QUrl& url, const QByteArray& data) : _url(url), diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 2f8168625e..eea64475df 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -34,9 +34,9 @@ public: Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); protected: + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; private: explicit AnimationCache(QObject* parent = NULL); virtual ~AnimationCache() { } @@ -62,7 +62,8 @@ class Animation : public Resource { public: - explicit Animation(const QUrl& url); + Animation(const Animation& other) : Resource(other), _hfmModel(other._hfmModel) {} + Animation(const QUrl& url) : Resource(url) {} QString getType() const override { return "Animation"; } diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index 845fd6ab4f..343de46e9a 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -33,9 +33,12 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) { return getResource(url).staticCast(); } -QSharedPointer SoundCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { +QSharedPointer SoundCache::createResource(const QUrl& url) { auto resource = QSharedPointer(new Sound(url), &Resource::deleter); resource->setLoadPriority(this, SOUNDS_LOADING_PRIORITY); return resource; } + +QSharedPointer SoundCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new Sound(*resource.staticCast().data()), &Resource::deleter); +} \ No newline at end of file diff --git a/libraries/audio/src/SoundCache.h b/libraries/audio/src/SoundCache.h index 64d392a41d..48c3354877 100644 --- a/libraries/audio/src/SoundCache.h +++ b/libraries/audio/src/SoundCache.h @@ -24,8 +24,9 @@ public: Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + private: SoundCache(QObject* parent = NULL); }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7e01af04dd..9515ef94b5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -280,11 +280,7 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3 } void RenderableModelEntityItem::fetchCollisionGeometryResource() { - QUrl hullURL(getCollisionShapeURL()); - QUrlQuery queryArgs(hullURL); - queryArgs.addQueryItem("collision-hull", ""); - hullURL.setQuery(queryArgs); - _compoundShapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); + _compoundShapeResource = DependencyManager::get()->getCollisionGeometryResource(getCollisionShapeURL()); } bool RenderableModelEntityItem::computeShapeFailedToLoad() { diff --git a/libraries/fbx/src/FBXSerializer_Material.cpp b/libraries/fbx/src/FBXSerializer_Material.cpp index 0a1d15b72a..9caf713e75 100644 --- a/libraries/fbx/src/FBXSerializer_Material.cpp +++ b/libraries/fbx/src/FBXSerializer_Material.cpp @@ -76,13 +76,12 @@ HFMTexture FBXSerializer::getTexture(const QString& textureID, const QString& ma } void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { - - QString materialMapString = mapping.value("materialMap").toString(); - QJsonDocument materialMapDocument = QJsonDocument::fromJson(materialMapString.toUtf8()); - QJsonObject materialMap = materialMapDocument.object(); - if (!materialMapString.isEmpty()) { - if (materialMapDocument.isEmpty() || materialMap.isEmpty()) { - qCDebug(modelformat) << "fbx Material Map found but did not produce valid JSON:" << materialMapString; + QJsonObject materialMap; + if (mapping.contains("materialMap")) { + QByteArray materialMapValue = mapping.value("materialMap").toByteArray(); + materialMap = QJsonDocument::fromJson(materialMapValue).object(); + if (materialMap.isEmpty()) { + qCDebug(modelformat) << "fbx Material Map found but did not produce valid JSON:" << materialMapValue; } } for (QHash::iterator it = _hfmMaterials.begin(); it != _hfmMaterials.end(); it++) { diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 43806560dc..0ca1c6c9e6 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -88,7 +88,7 @@ QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; QBuffer buffer; buffer.open(QIODevice::WriteOnly); - + for (auto key : PREFERED_ORDER) { auto it = mapping.find(key); if (it != mapping.constEnd()) { @@ -104,7 +104,7 @@ QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { } } } - + for (auto it = mapping.constBegin(); it != mapping.constEnd(); it++) { if (!PREFERED_ORDER.contains(it.key())) { writeVariant(buffer, it); diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index 7befb7e053..c19b139936 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -105,7 +105,7 @@ void Material::setMetallic(float metallic) { void Material::setScattering(float scattering) { scattering = glm::clamp(scattering, 0.0f, 1.0f); - _key.setMetallic(scattering > 0.0f); + _key.setScattering(scattering > 0.0f); _scattering = scattering; } diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index b6550a5e9e..7dcd7b8a61 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -417,9 +417,13 @@ MaterialCache& MaterialCache::instance() { } NetworkMaterialResourcePointer MaterialCache::getMaterial(const QUrl& url) { - return ResourceCache::getResource(url, QUrl(), nullptr).staticCast(); + return ResourceCache::getResource(url, QUrl()).staticCast(); } -QSharedPointer MaterialCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { +QSharedPointer MaterialCache::createResource(const QUrl& url) { return QSharedPointer(new NetworkMaterialResource(url), &Resource::deleter); +} + +QSharedPointer MaterialCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new NetworkMaterialResource(*resource.staticCast().data()), &Resource::deleter); } \ No newline at end of file diff --git a/libraries/model-networking/src/model-networking/MaterialCache.h b/libraries/model-networking/src/model-networking/MaterialCache.h index 074cd6c98d..6abadfc030 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.h +++ b/libraries/model-networking/src/model-networking/MaterialCache.h @@ -53,7 +53,8 @@ public: NetworkMaterialResourcePointer getMaterial(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; }; #endif diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 8c541040a7..7515dad256 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -107,7 +107,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { } auto modelCache = DependencyManager::get(); - GeometryExtra extra{ _mapping, _textureBaseUrl, false }; + GeometryExtra extra { _mapping, _textureBaseUrl, false }; // Get the raw GeometryResource _geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast(); @@ -253,13 +253,19 @@ void GeometryReader::run() { class GeometryDefinitionResource : public GeometryResource { Q_OBJECT public: - GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) : - GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _modelLoader(modelLoader), _mapping(mapping), _combineParts(combineParts) {} + GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url) : GeometryResource(url), _modelLoader(modelLoader) {} + GeometryDefinitionResource(const GeometryDefinitionResource& other) : + GeometryResource(other), + _modelLoader(other._modelLoader), + _mapping(other._mapping), + _combineParts(other._combineParts) {} QString getType() const override { return "GeometryDefinition"; } virtual void downloadFinished(const QByteArray& data) override; + void setExtra(void* extra) override; + protected: Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel); @@ -269,6 +275,13 @@ private: bool _combineParts; }; +void GeometryDefinitionResource::setExtra(void* extra) { + const GeometryExtra* geometryExtra = static_cast(extra); + _mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); + _textureBaseUrl = resolveTextureBaseUrl(_url, geometryExtra ? geometryExtra->textureBaseUrl : QUrl()); + _combineParts = geometryExtra ? geometryExtra->combineParts : true; +} + void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { if (_url != _effectiveBaseURL) { _url = _effectiveBaseURL; @@ -323,22 +336,21 @@ ModelCache::ModelCache() { modelFormatRegistry->addFormat(GLTFSerializer()); } -QSharedPointer ModelCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { +QSharedPointer ModelCache::createResource(const QUrl& url) { Resource* resource = nullptr; if (url.path().toLower().endsWith(".fst")) { resource = new GeometryMappingResource(url); } else { - const GeometryExtra* geometryExtra = static_cast(extra); - auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); - auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl(); - bool combineParts = geometryExtra ? geometryExtra->combineParts : true; - resource = new GeometryDefinitionResource(_modelLoader, url, mapping, textureBaseUrl, combineParts); + resource = new GeometryDefinitionResource(_modelLoader, url); } return QSharedPointer(resource, &Resource::deleter); } +QSharedPointer ModelCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new GeometryDefinitionResource(*resource.staticCast().data()), &Resource::deleter); +} + GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { bool combineParts = true; diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 1018bdecd5..497cae86a3 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -82,8 +82,12 @@ class GeometryResource : public Resource, public Geometry { public: using Pointer = QSharedPointer; - GeometryResource(const QUrl& url, const QUrl& textureBaseUrl = QUrl()) : - Resource(url), _textureBaseUrl(textureBaseUrl) {} + GeometryResource(const QUrl& url) : Resource(url) {} + GeometryResource(const GeometryResource& other) : + Resource(other), + Geometry(other), + _textureBaseUrl(other._textureBaseUrl), + _isCacheable(other._isCacheable) {} virtual bool areTexturesLoaded() const override { return isLoaded() && Geometry::areTexturesLoaded(); } @@ -153,8 +157,8 @@ public: protected: friend class GeometryMappingResource; - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; private: ModelCache(); diff --git a/libraries/model-networking/src/model-networking/ShaderCache.cpp b/libraries/model-networking/src/model-networking/ShaderCache.cpp index bf7ade07f7..b774ca36c5 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.cpp +++ b/libraries/model-networking/src/model-networking/ShaderCache.cpp @@ -21,11 +21,13 @@ ShaderCache& ShaderCache::instance() { } NetworkShaderPointer ShaderCache::getShader(const QUrl& url) { - return ResourceCache::getResource(url, QUrl(), nullptr).staticCast(); + return ResourceCache::getResource(url, QUrl()).staticCast(); } -QSharedPointer ShaderCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { +QSharedPointer ShaderCache::createResource(const QUrl& url) { return QSharedPointer(new NetworkShader(url), &Resource::deleter); } +QSharedPointer ShaderCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new NetworkShader(*resource.staticCast().data()), &Resource::deleter); +} diff --git a/libraries/model-networking/src/model-networking/ShaderCache.h b/libraries/model-networking/src/model-networking/ShaderCache.h index bd78e6e7e3..fe9edd7ddf 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.h +++ b/libraries/model-networking/src/model-networking/ShaderCache.h @@ -14,6 +14,7 @@ class NetworkShader : public Resource { public: NetworkShader(const QUrl& url); + NetworkShader(const NetworkShader& other) : Resource(other), _source(other._source) {} QString getType() const override { return "NetworkShader"; } @@ -31,8 +32,8 @@ public: NetworkShaderPointer getShader(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; }; #endif diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 4c30dc6d93..2f29eaf385 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -305,42 +305,52 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te return gpu::TexturePointer(loader(std::move(image), path.toStdString(), shouldCompress, target, false)); } -QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { - const TextureExtra* textureExtra = static_cast(extra); - auto type = textureExtra ? textureExtra->type : image::TextureUsage::DEFAULT_TEXTURE; - auto content = textureExtra ? textureExtra->content : QByteArray(); - auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS; - NetworkTexture* texture = new NetworkTexture(url, type, content, maxNumPixels); - return QSharedPointer(texture, &Resource::deleter); +QSharedPointer TextureCache::createResource(const QUrl& url) { + return QSharedPointer(new NetworkTexture(url), &Resource::deleter); +} + +QSharedPointer TextureCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new NetworkTexture(*resource.staticCast().data()), &Resource::deleter); } int networkTexturePointerMetaTypeId = qRegisterMetaType>(); NetworkTexture::NetworkTexture(const QUrl& url) : -Resource(url), -_type(), -_maxNumPixels(100) + Resource(url), + _type(), + _maxNumPixels(100) { _textureSource = std::make_shared(url); _lowestRequestedMipLevel = 0; _loaded = true; } +NetworkTexture::NetworkTexture(const NetworkTexture& other) : + Resource(other), + _type(other._type), + _currentlyLoadingResourceType(other._currentlyLoadingResourceType), + _originalWidth(other._originalWidth), + _originalHeight(other._originalHeight), + _width(other._width), + _height(other._height), + _maxNumPixels(other._maxNumPixels) +{ +} + static bool isLocalUrl(const QUrl& url) { auto scheme = url.scheme(); return (scheme == HIFI_URL_SCHEME_FILE || scheme == URL_SCHEME_QRC || scheme == RESOURCE_SCHEME); } -NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) : - Resource(url), - _type(type), - _maxNumPixels(maxNumPixels) -{ - _textureSource = std::make_shared(url, (int)type); +void NetworkTexture::setExtra(void* extra) { + const TextureExtra* textureExtra = static_cast(extra); + _type = textureExtra ? textureExtra->type : image::TextureUsage::DEFAULT_TEXTURE; + _maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS; + + _textureSource = std::make_shared(_url, (int)_type); _lowestRequestedMipLevel = 0; - auto fileNameLowercase = url.fileName().toLower(); + auto fileNameLowercase = _url.fileName().toLower(); if (fileNameLowercase.endsWith(TEXTURE_META_EXTENSION)) { _currentlyLoadingResourceType = ResourceType::META; } else if (fileNameLowercase.endsWith(".ktx")) { @@ -351,17 +361,18 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, _shouldFailOnRedirect = _currentlyLoadingResourceType != ResourceType::KTX; - if (type == image::TextureUsage::CUBE_TEXTURE) { + if (_type == image::TextureUsage::CUBE_TEXTURE) { setLoadPriority(this, SKYBOX_LOAD_PRIORITY); } else if (_currentlyLoadingResourceType == ResourceType::KTX) { setLoadPriority(this, HIGH_MIPS_LOAD_PRIORITY); } - if (!url.isValid()) { + if (!_url.isValid()) { _loaded = true; } // if we have content, load it after we have our self pointer + auto content = textureExtra ? textureExtra->content : QByteArray(); if (!content.isEmpty()) { _startedLoading = true; QMetaObject::invokeMethod(this, "downloadFinished", Qt::QueuedConnection, Q_ARG(const QByteArray&, content)); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 3933e3ae56..d744d060b6 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -46,7 +46,7 @@ class NetworkTexture : public Resource, public Texture { public: NetworkTexture(const QUrl& url); - NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels); + NetworkTexture(const NetworkTexture& other); ~NetworkTexture() override; QString getType() const override { return "NetworkTexture"; } @@ -63,6 +63,8 @@ public: Q_INVOKABLE void setOriginalDescriptor(ktx::KTXDescriptor* descriptor) { _originalKtxDescriptor.reset(descriptor); } + void setExtra(void* extra) override; + signals: void networkTextureCreated(const QWeakPointer& self); @@ -201,8 +203,8 @@ protected: // Overload ResourceCache::prefetch to allow specifying texture type for loads Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; private: friend class ImageReader; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 20fe05e7b8..43fc93ffc5 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -265,16 +265,17 @@ ResourceCache::~ResourceCache() { void ResourceCache::clearATPAssets() { { QWriteLocker locker(&_resourcesLock); - for (auto& url : _resources.keys()) { + QList urls = _resources.keys(); + for (auto& url : urls) { // If this is an ATP resource if (url.scheme() == URL_SCHEME_ATP) { - - // Remove it from the resource hash - auto resource = _resources.take(url); - if (auto strongRef = resource.lock()) { - // Make sure the resource won't reinsert itself - strongRef->setCache(nullptr); - _totalResourcesSize -= strongRef->getBytes(); + auto resourcesWithExtraHash = _resources.take(url); + for (auto& resource : resourcesWithExtraHash) { + if (auto strongRef = resource.lock()) { + // Make sure the resource won't reinsert itself + strongRef->setCache(nullptr); + _totalResourcesSize -= strongRef->getBytes(); + } } } } @@ -297,16 +298,20 @@ void ResourceCache::refreshAll() { clearUnusedResources(); resetUnusedResourceCounter(); - QHash> resources; + QHash>> allResources; { QReadLocker locker(&_resourcesLock); - resources = _resources; + allResources = _resources; } // Refresh all remaining resources in use - foreach (QSharedPointer resource, resources) { - if (resource) { - resource->refresh(); + // FIXME: this will trigger multiple refreshes for the same resource if they have different hashes + for (auto& resourcesWithExtraHash : allResources) { + for (auto& resourceWeak : resourcesWithExtraHash) { + auto resource = resourceWeak.lock(); + if (resource) { + resource->refresh(); + } } } } @@ -338,39 +343,59 @@ void ResourceCache::setRequestLimit(uint32_t limit) { } } -QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra) { +QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra, int extraHash) { QSharedPointer resource; + if (extra && extraHash < 0) { + qDebug() << "ResourceCache::getResource: ERROR! Non-null extra, but invalid extraHash"; + return resource; + } + { QReadLocker locker(&_resourcesLock); - resource = _resources.value(url).lock(); + auto& resourcesWithExtraHash = _resources[url]; + auto resourcesWithExtraHashIter = resourcesWithExtraHash.find(extraHash); + if (resourcesWithExtraHashIter != resourcesWithExtraHash.end()) { + // We've seen this extra info before + resource = resourcesWithExtraHashIter.value().lock(); + } else if (resourcesWithExtraHash.size() > 0.0f) { + // We haven't seen this extra info before, but we've already downloaded the resource. We need a new copy of this object (with any old hash). + resource = createResourceCopy(resourcesWithExtraHash.begin().value().lock()); + resource->setExtra(extra); + resource->setExtraHash(extraHash); + resource->setSelf(resource); + resource->setCache(this); + resource->moveToThread(qApp->thread()); + connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); + resourcesWithExtraHash.insert(extraHash, resource); + removeUnusedResource(resource); + resource->ensureLoading(); + } } if (resource) { removeUnusedResource(resource); } - if (!resource && !url.isValid() && !url.isEmpty() && fallback.isValid()) { - resource = getResource(fallback, QUrl()); + if (!resource && (!url.isValid() || url.isEmpty()) && fallback.isValid()) { + resource = getResource(fallback, QUrl(), extra, extraHash); } if (!resource) { - resource = createResource( - url, - fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer(), - extra); + resource = createResource(url); + resource->setExtra(extra); + resource->setExtraHash(extraHash); resource->setSelf(resource); resource->setCache(this); resource->moveToThread(qApp->thread()); connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); { QWriteLocker locker(&_resourcesLock); - _resources.insert(url, resource); + _resources[url].insert(extraHash, resource); } removeUnusedResource(resource); resource->ensureLoading(); } - DependencyManager::get()->update( - resource->getURL(), -1, "ResourceCache::getResource"); + DependencyManager::get()->update(resource->getURL(), -1, "ResourceCache::getResource"); return resource; } @@ -527,6 +552,26 @@ bool ResourceCache::attemptHighestPriorityRequest() { static int requestID = 0; +Resource::Resource(const Resource& other) : + _url(other._url), + _extraHash(other._extraHash), + _effectiveBaseURL(other._effectiveBaseURL), + _activeUrl(other._activeUrl), + _requestByteRange(other._requestByteRange), + _shouldFailOnRedirect(other._shouldFailOnRedirect), + _startedLoading(other._startedLoading), + _failedToLoad(other._failedToLoad), + _loaded(other._loaded), + _loadPriorities(other._loadPriorities), + _bytesReceived(other._bytesReceived), + _bytesTotal(other._bytesTotal), + _bytes(other._bytes), + _requestID(++requestID) { + if (!other._loaded) { + _startedLoading = false; + } +} + Resource::Resource(const QUrl& url) : _url(url), _activeUrl(url), @@ -678,7 +723,7 @@ void Resource::setSize(const qint64& bytes) { void Resource::reinsert() { QWriteLocker locker(&_cache->_resourcesLock); - _cache->_resources.insert(_url, _self); + _cache->_resources[_url].insert(_extraHash, _self); } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 275684f73e..dd60e3c110 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -239,8 +239,7 @@ protected slots: /// returns an empty smart pointer and loads its asynchronously. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param extra extra data to pass to the creator, if appropriate - QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), - void* extra = NULL); + QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), void* extra = NULL, int extraHash = -1); private slots: void clearATPAssets(); @@ -254,8 +253,8 @@ protected: Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } /// Creates a new resource. - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) = 0; + virtual QSharedPointer createResource(const QUrl& url) = 0; + virtual QSharedPointer createResourceCopy(const QSharedPointer& resource) = 0; void addUnusedResource(const QSharedPointer& resource); void removeUnusedResource(const QSharedPointer& resource); @@ -278,7 +277,7 @@ private: void resetResourceCounters(); // Resources - QHash> _resources; + QHash>> _resources; QReadWriteLock _resourcesLock { QReadWriteLock::Recursive }; int _lastLRUKey = 0; @@ -359,7 +358,8 @@ class Resource : public QObject { Q_OBJECT public: - + + Resource(const Resource& other); Resource(const QUrl& url); virtual ~Resource(); @@ -415,6 +415,9 @@ public: unsigned int getDownloadAttempts() { return _attempts; } unsigned int getDownloadAttemptsRemaining() { return _attemptsRemaining; } + virtual void setExtra(void* extra) {}; + void setExtraHash(int extraHash) { _extraHash = extraHash; } + signals: /// Fired when the resource begins downloading. void loading(); @@ -469,7 +472,7 @@ protected: virtual bool handleFailedRequest(ResourceRequest::Result result); QUrl _url; - QUrl _effectiveBaseURL{ _url }; + QUrl _effectiveBaseURL { _url }; QUrl _activeUrl; ByteRange _requestByteRange; bool _shouldFailOnRedirect { false }; @@ -492,6 +495,8 @@ protected: int _requestID; ResourceRequest* _request{ nullptr }; + int _extraHash { -1 }; + public slots: void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal); void handleReplyFinished(); diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index c63350de7f..c08dd40ad8 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -48,8 +48,11 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { return getResource(url).staticCast(); } -QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { +QSharedPointer ClipCache::createResource(const QUrl& url) { qCDebug(recordingLog) << "Loading recording at" << url; return QSharedPointer(new NetworkClipLoader(url), &Resource::deleter); } +QSharedPointer ClipCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new NetworkClipLoader(*resource.staticCast().data()), &Resource::deleter); +} \ No newline at end of file diff --git a/libraries/recording/src/recording/ClipCache.h b/libraries/recording/src/recording/ClipCache.h index 2c3465e725..202cd2f00e 100644 --- a/libraries/recording/src/recording/ClipCache.h +++ b/libraries/recording/src/recording/ClipCache.h @@ -33,6 +33,8 @@ class NetworkClipLoader : public Resource { Q_OBJECT public: NetworkClipLoader(const QUrl& url); + NetworkClipLoader(const NetworkClipLoader& other) : Resource(other), _clip(other._clip) {} + virtual void downloadFinished(const QByteArray& data) override; ClipPointer getClip() { return _clip; } bool completed() { return _failedToLoad || isLoaded(); } @@ -54,7 +56,8 @@ public slots: NetworkClipLoaderPointer getClipLoader(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; private: ClipCache(QObject* parent = nullptr); diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 85bdf0fadc..5f3763ac2a 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -579,7 +579,7 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial } else { forceDefault = true; } - schemaKey.setScattering(true); + schemaKey.setScatteringMap(true); } break; case graphics::MaterialKey::EMISSIVE_MAP_BIT: From a959d695545b30843c058c1d561eab08f10d070f Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 5 Feb 2019 18:10:32 -0800 Subject: [PATCH 29/62] Make AnimSkeleton::getParentIndex() more cache coherent --- libraries/animation/src/AnimSkeleton.cpp | 27 +++++++++++++----------- libraries/animation/src/AnimSkeleton.h | 6 +++++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index cc48308f17..844ad5aef4 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -76,7 +76,7 @@ int AnimSkeleton::getChainDepth(int jointIndex) const { int index = jointIndex; do { chainDepth++; - index = _joints[index].parentIndex; + index = _parentIndices[index]; } while (index != -1); return chainDepth; } else { @@ -102,17 +102,12 @@ const AnimPose& AnimSkeleton::getPostRotationPose(int jointIndex) const { return _relativePostRotationPoses[jointIndex]; } -int AnimSkeleton::getParentIndex(int jointIndex) const { - return _joints[jointIndex].parentIndex; -} - std::vector AnimSkeleton::getChildrenOfJoint(int jointIndex) const { // Children and grandchildren, etc. std::vector result; if (jointIndex != -1) { - for (int i = jointIndex + 1; i < (int)_joints.size(); i++) { - if (_joints[i].parentIndex == jointIndex - || (std::find(result.begin(), result.end(), _joints[i].parentIndex) != result.end())) { + for (int i = jointIndex + 1; i < (int)_parentIndices.size(); i++) { + if (_parentIndices[i] == jointIndex || (std::find(result.begin(), result.end(), _parentIndices[i]) != result.end())) { result.push_back(i); } } @@ -128,7 +123,7 @@ AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& relati if (jointIndex < 0 || jointIndex >= (int)relativePoses.size() || jointIndex >= _jointsSize) { return AnimPose::identity; } else { - return getAbsolutePose(_joints[jointIndex].parentIndex, relativePoses) * relativePoses[jointIndex]; + return getAbsolutePose(_parentIndices[jointIndex], relativePoses) * relativePoses[jointIndex]; } } @@ -136,7 +131,7 @@ void AnimSkeleton::convertRelativePosesToAbsolute(AnimPoseVec& poses) const { // poses start off relative and leave in absolute frame int lastIndex = std::min((int)poses.size(), _jointsSize); for (int i = 0; i < lastIndex; ++i) { - int parentIndex = _joints[i].parentIndex; + int parentIndex = _parentIndices[i]; if (parentIndex != -1) { poses[i] = poses[parentIndex] * poses[i]; } @@ -147,7 +142,7 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const { // poses start off absolute and leave in relative frame int lastIndex = std::min((int)poses.size(), _jointsSize); for (int i = lastIndex - 1; i >= 0; --i) { - int parentIndex = _joints[i].parentIndex; + int parentIndex = _parentIndices[i]; if (parentIndex != -1) { poses[i] = poses[parentIndex].inverse() * poses[i]; } @@ -158,7 +153,7 @@ void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& ro // poses start off absolute and leave in relative frame int lastIndex = std::min((int)rotations.size(), _jointsSize); for (int i = lastIndex - 1; i >= 0; --i) { - int parentIndex = _joints[i].parentIndex; + int parentIndex = _parentIndices[i]; if (parentIndex != -1) { rotations[i] = glm::inverse(rotations[parentIndex]) * rotations[i]; } @@ -197,6 +192,14 @@ void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets) { _joints = joints; + + // build a seperate vector of parentIndices for cache coherency + // AnimSkeleton::getParentIndex is called very frequently in tight loops. + _parentIndices.reserve(_joints.size()); + for (auto& joint : _joints) { + _parentIndices.push_back(joint.parentIndex); + } + _jointsSize = (int)joints.size(); // build a cache of bind poses diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 14f39eedbc..0eefbf973e 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -43,7 +43,10 @@ public: // get post transform which might include FBX offset transformations const AnimPose& getPostRotationPose(int jointIndex) const; - int getParentIndex(int jointIndex) const; + int getParentIndex(int jointIndex) const { + return _parentIndices[jointIndex]; + } + std::vector getChildrenOfJoint(int jointIndex) const; AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& relativePoses) const; @@ -69,6 +72,7 @@ protected: void buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets); std::vector _joints; + std::vector _parentIndices; int _jointsSize { 0 }; AnimPoseVec _relativeDefaultPoses; AnimPoseVec _absoluteDefaultPoses; From 708309fa63e5201f1695dcd054e2cc7973c1ffae Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 5 Feb 2019 18:11:30 -0800 Subject: [PATCH 30/62] CubicHermiteSplineWithArcLength optimization --- libraries/shared/src/CubicHermiteSpline.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index da2ed26de4..cdbc64308d 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -60,7 +60,7 @@ protected: class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor { public: - enum Constants { NUM_SUBDIVISIONS = 30 }; + enum Constants { NUM_SUBDIVISIONS = 15 }; CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() { memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1)); @@ -71,11 +71,13 @@ public: float alpha = 0.0f; float accum = 0.0f; _values[0] = 0.0f; + glm::vec3 prevValue = this->operator()(alpha); for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) { - accum += glm::distance(this->operator()(alpha), - this->operator()(alpha + DELTA)); + glm::vec3 nextValue = this->operator()(alpha + DELTA); + accum += glm::distance(prevValue, nextValue); alpha += DELTA; _values[i] = accum; + prevValue = nextValue; } } From 87498b3dd2ee7bf63886bb3d8f7c18e85be43888 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 5 Feb 2019 18:15:05 -0800 Subject: [PATCH 31/62] Avoid dynamic_cast in getAnimInverseKinematicsNode --- libraries/animation/src/Rig.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 6cbf881157..d09de36a14 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -493,10 +493,8 @@ std::shared_ptr Rig::getAnimInverseKinematicsNode() const std::shared_ptr result; if (_animNode) { _animNode->traverse([&](AnimNode::Pointer node) { - // only report clip nodes as valid roles. - auto ikNode = std::dynamic_pointer_cast(node); - if (ikNode) { - result = ikNode; + if (node->getType() == AnimNodeType::InverseKinematics) { + result = std::dynamic_pointer_cast(node); return false; } else { return true; From 5c7e81584c204a3556e65073abb82f342b655540 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 5 Feb 2019 18:23:58 -0800 Subject: [PATCH 32/62] AnimPose::inverse() optimization --- libraries/animation/src/AnimPose.cpp | 5 ++- tests/animation/src/AnimTests.cpp | 54 ++++++++++++++++++++++++++++ tests/animation/src/AnimTests.h | 1 + 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index 7b80e96ed5..366d863c3d 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -51,7 +51,10 @@ AnimPose AnimPose::operator*(const AnimPose& rhs) const { } AnimPose AnimPose::inverse() const { - return AnimPose(glm::inverse(static_cast(*this))); + float invScale = 1.0f / _scale; + glm::quat invRot = glm::inverse(_rot); + glm::vec3 invTrans = invScale * (invRot * -_trans); + return AnimPose(invScale, invRot, invTrans); } // mirror about x-axis without applying negative scale. diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 2a49846b6b..b1926efb71 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -526,6 +526,60 @@ void AnimTests::testAnimPoseMultiply() { } } +void AnimTests::testAnimPoseInverse() { + const float PI = (float)M_PI; + const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); + const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + + std::vector scaleVec = { + 1.0f, + 2.0f, + 0.5f + }; + + std::vector rotVec = { + glm::quat(), + ROT_X_90, + ROT_Y_180, + ROT_Z_30, + ROT_X_90 * ROT_Y_180 * ROT_Z_30, + -ROT_Y_180 + }; + + std::vector transVec = { + glm::vec3(), + glm::vec3(10.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 5.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 7.5f), + glm::vec3(10.0f, 5.0f, 7.5f), + glm::vec3(-10.0f, 5.0f, 7.5f), + glm::vec3(10.0f, -5.0f, 7.5f), + glm::vec3(10.0f, 5.0f, -7.5f) + }; + + const float TEST_EPSILON = 0.001f; + + for (auto& scale : scaleVec) { + for (auto& rot : rotVec) { + for (auto& trans : transVec) { + + // build a matrix the old fashioned way. + glm::mat4 scaleMat = glm::scale(glm::mat4(), glm::vec3(scale)); + glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); + glm::mat4 rawMat = glm::inverse(rotTransMat * scaleMat); + + // use an anim pose to build a matrix by parts. + AnimPose pose(scale, rot, trans); + glm::mat4 poseMat = pose.inverse(); + + QCOMPARE_WITH_ABS_ERROR(rawMat, poseMat, TEST_EPSILON); + } + } + } +} + + void AnimTests::testExpressionTokenizer() { QString str = "(10 + x) >= 20.1 && (y != !z)"; AnimExpression e("x"); diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index 637611e8c4..326545b0a9 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -28,6 +28,7 @@ private slots: void testAccumulateTime(); void testAnimPose(); void testAnimPoseMultiply(); + void testAnimPoseInverse(); void testExpressionTokenizer(); void testExpressionParser(); void testExpressionEvaluator(); From 2247dd3471c07288a51f1ed8e7fd6342c4f8a91e Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 6 Feb 2019 18:24:40 +0100 Subject: [PATCH 33/62] local compare in entity list sorting --- scripts/system/html/js/entityList.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index f059b91e81..b19873a049 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -681,6 +681,9 @@ function loaded() { if (isNullOrEmpty(valueB)) { return (isDefaultSort ? -1 : 1) * (isAscendingSort ? 1 : -1); } + if (typeof(valueA) === "string") { + return valueA.localeCompare(valueB); + } return valueA < valueB ? -1 : 1; }); }); From 4c9fb168c73a0353c9862034259f2827ce1d579b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 6 Feb 2019 10:55:36 -0800 Subject: [PATCH 34/62] enable backface culling on procedurals --- libraries/procedural/src/procedural/Procedural.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 05cbde374d..ff8c270371 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -104,13 +104,13 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { //} Procedural::Procedural() { - _opaqueState->setCullMode(gpu::State::CULL_BACK); + _opaqueState->setCullMode(gpu::State::CULL_NONE); _opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); _opaqueState->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _transparentState->setCullMode(gpu::State::CULL_BACK); + _transparentState->setCullMode(gpu::State::CULL_NONE); _transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); _transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, From 91937041731f2908df99afffccace23db729189b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 5 Feb 2019 18:27:27 -0800 Subject: [PATCH 35/62] trying to get hashes working --- .../src/model-networking/MaterialCache.cpp | 2 +- .../src/model-networking/ModelCache.cpp | 50 +++++++++++++++++-- .../src/model-networking/ShaderCache.cpp | 2 +- .../src/model-networking/TextureCache.cpp | 22 +++++++- libraries/networking/src/ResourceCache.cpp | 24 ++++----- libraries/networking/src/ResourceCache.h | 19 +++---- 6 files changed, 89 insertions(+), 30 deletions(-) diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index 7dcd7b8a61..aaa9767397 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -417,7 +417,7 @@ MaterialCache& MaterialCache::instance() { } NetworkMaterialResourcePointer MaterialCache::getMaterial(const QUrl& url) { - return ResourceCache::getResource(url, QUrl()).staticCast(); + return ResourceCache::getResource(url).staticCast(); } QSharedPointer MaterialCache::createResource(const QUrl& url) { diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 7515dad256..1535f5cfad 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -40,6 +40,50 @@ public: bool combineParts; }; +// From: https://stackoverflow.com/questions/41145012/how-to-hash-qvariant +class QVariantHasher { +public: + QVariantHasher() : buff(&bb), ds(&buff) { + bb.reserve(1000); + buff.open(QIODevice::WriteOnly); + } + uint hash(const QVariant& v) { + buff.seek(0); + ds << v; + return qHashBits(bb.constData(), buff.pos()); + } +private: + QByteArray bb; + QBuffer buff; + QDataStream ds; +}; + +namespace std { + template <> + struct hash { + size_t operator()(const QVariantHash& a) const { + QVariantHasher hasher; + return hasher.hash(a); + } + }; + + template <> + struct hash { + size_t operator()(const QUrl& a) const { + return qHash(a); + } + }; + + template <> + struct hash { + size_t operator()(const GeometryExtra& a) const { + size_t result = 0; + hash_combine(result, a.mapping, a.textureBaseUrl, a.combineParts); + return result; + } + }; +} + QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) { return textureBaseUrl.isValid() ? textureBaseUrl : url; } @@ -110,7 +154,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { GeometryExtra extra { _mapping, _textureBaseUrl, false }; // Get the raw GeometryResource - _geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast(); + _geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash()(extra)).staticCast(); // Avoid caching nested resources - their references will be held by the parent _geometryResource->_isCacheable = false; @@ -355,7 +399,7 @@ 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(); + GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -368,7 +412,7 @@ GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& u const QVariantHash& mapping, const QUrl& textureBaseUrl) { bool combineParts = false; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast(); + GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); diff --git a/libraries/model-networking/src/model-networking/ShaderCache.cpp b/libraries/model-networking/src/model-networking/ShaderCache.cpp index b774ca36c5..8d060c42f2 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.cpp +++ b/libraries/model-networking/src/model-networking/ShaderCache.cpp @@ -21,7 +21,7 @@ ShaderCache& ShaderCache::instance() { } NetworkShaderPointer ShaderCache::getShader(const QUrl& url) { - return ResourceCache::getResource(url, QUrl()).staticCast(); + return ResourceCache::getResource(url).staticCast(); } QSharedPointer ShaderCache::createResource(const QUrl& url) { diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 2f29eaf385..910de258f9 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -194,10 +194,28 @@ public: int maxNumPixels; }; +namespace std { + template <> + struct hash { + size_t operator()(const QByteArray& a) const { + return qHash(a); + } + }; + + template <> + struct hash { + size_t operator()(const TextureExtra& a) const { + size_t result = 0; + hash_combine(result, (int)a.type, a.content, a.maxNumPixels); + return result; + } + }; +} + ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNumPixels) { auto byteArray = QByteArray(); TextureExtra extra = { (image::TextureUsage::Type)type, byteArray, maxNumPixels }; - return ResourceCache::prefetch(url, &extra); + return ResourceCache::prefetch(url, &extra, std::hash()(extra)); } NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) { @@ -211,7 +229,7 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUs modifiedUrl.setQuery(query.toString()); } TextureExtra extra = { type, content, maxNumPixels }; - return ResourceCache::getResource(modifiedUrl, QUrl(), &extra).staticCast(); + return ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash()(extra)).staticCast(); } gpu::TexturePointer TextureCache::getTextureByHash(const std::string& hash) { diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 43fc93ffc5..cb7b8c7c82 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -158,8 +158,8 @@ void ScriptableResourceCache::updateTotalSize(const qint64& deltaSize) { _resourceCache->updateTotalSize(deltaSize); } -ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra) { - return _resourceCache->prefetch(url, extra); +ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra, size_t extraHash) { + return _resourceCache->prefetch(url, extra, extraHash); } @@ -211,20 +211,20 @@ void ScriptableResource::disconnectHelper() { } } -ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { +ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra, size_t extraHash) { ScriptableResource* result = nullptr; if (QThread::currentThread() != thread()) { // Must be called in thread to ensure getResource returns a valid pointer BLOCKING_INVOKE_METHOD(this, "prefetch", Q_RETURN_ARG(ScriptableResource*, result), - Q_ARG(QUrl, url), Q_ARG(void*, extra)); + Q_ARG(QUrl, url), Q_ARG(void*, extra), Q_ARG(size_t, extraHash)); return result; } result = new ScriptableResource(url); - auto resource = getResource(url, QUrl(), extra); + auto resource = getResource(url, QUrl(), extra, extraHash); result->_resource = resource; result->setObjectName(url.toString()); @@ -298,7 +298,7 @@ void ResourceCache::refreshAll() { clearUnusedResources(); resetUnusedResourceCounter(); - QHash>> allResources; + QHash>> allResources; { QReadLocker locker(&_resourcesLock); allResources = _resources; @@ -343,13 +343,8 @@ void ResourceCache::setRequestLimit(uint32_t limit) { } } -QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra, int extraHash) { +QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash) { QSharedPointer resource; - if (extra && extraHash < 0) { - qDebug() << "ResourceCache::getResource: ERROR! Non-null extra, but invalid extraHash"; - return resource; - } - { QReadLocker locker(&_resourcesLock); auto& resourcesWithExtraHash = _resources[url]; @@ -553,8 +548,8 @@ bool ResourceCache::attemptHighestPriorityRequest() { static int requestID = 0; Resource::Resource(const Resource& other) : + QObject(), _url(other._url), - _extraHash(other._extraHash), _effectiveBaseURL(other._effectiveBaseURL), _activeUrl(other._activeUrl), _requestByteRange(other._requestByteRange), @@ -566,7 +561,8 @@ Resource::Resource(const Resource& other) : _bytesReceived(other._bytesReceived), _bytesTotal(other._bytesTotal), _bytes(other._bytes), - _requestID(++requestID) { + _requestID(++requestID), + _extraHash(other._extraHash) { if (!other._loaded) { _startedLoading = false; } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index dd60e3c110..740bdadc48 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -231,15 +231,16 @@ protected slots: // Prefetches a resource to be held by the QScriptEngine. // Left as a protected member so subclasses can overload prefetch // and delegate to it (see TextureCache::prefetch(const QUrl&, int). - ScriptableResource* prefetch(const QUrl& url, void* extra); + ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); // FIXME: The return type is not recognized by JavaScript. /// Loads a resource from the specified URL and returns it. /// If the caller is on a different thread than the ResourceCache, /// returns an empty smart pointer and loads its asynchronously. /// \param fallback a fallback URL to load if the desired one is unavailable - /// \param extra extra data to pass to the creator, if appropriate - QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), void* extra = NULL, int extraHash = -1); + // FIXME: std::numeric_limits::max() could be a valid extraHash + QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl()) { return getResource(url, fallback, nullptr, std::numeric_limits::max()); } + QSharedPointer getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash); private slots: void clearATPAssets(); @@ -250,7 +251,7 @@ protected: // which should be a QScriptEngine with ScriptableResource registered, so that // the QScriptEngine will delete the pointer when it is garbage collected. // JSDoc is provided on more general function signature. - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits::max()); } /// Creates a new resource. virtual QSharedPointer createResource(const QUrl& url) = 0; @@ -277,7 +278,7 @@ private: void resetResourceCounters(); // Resources - QHash>> _resources; + QHash>> _resources; QReadWriteLock _resourcesLock { QReadWriteLock::Recursive }; int _lastLRUKey = 0; @@ -331,10 +332,10 @@ public: * Prefetches a resource. * @function ResourceCache.prefetch * @param {string} url - URL of the resource to prefetch. - * @param {object} [extra=null] * @returns {ResourceObject} */ - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra = nullptr); + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits::max()); } + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); signals: @@ -416,7 +417,7 @@ public: unsigned int getDownloadAttemptsRemaining() { return _attemptsRemaining; } virtual void setExtra(void* extra) {}; - void setExtraHash(int extraHash) { _extraHash = extraHash; } + void setExtraHash(size_t extraHash) { _extraHash = extraHash; } signals: /// Fired when the resource begins downloading. @@ -495,7 +496,7 @@ protected: int _requestID; ResourceRequest* _request{ nullptr }; - int _extraHash { -1 }; + size_t _extraHash; public slots: void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal); From 918e2fc7ab7a3f665ef8d34903602b8a6f7a00f4 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 6 Feb 2019 16:08:30 -0800 Subject: [PATCH 36/62] Proper detection of digital clipping on the unprocessed audio input. Triggered when 3+ consecutive samples > -0.1 dBFS. --- libraries/audio-client/src/AudioClient.cpp | 64 ++++++++++++++++++---- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index e738b326e6..cd1363a84d 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -170,6 +170,57 @@ static void channelDownmix(int16_t* source, int16_t* dest, int numSamples) { } } +static float computeLoudness(int16_t* samples, int numSamples, int numChannels, bool& isClipping) { + + const int32_t CLIPPING_THRESHOLD = 32392; // -0.1 dBFS + const int32_t CLIPPING_DETECTION = 3; // consecutive samples over threshold + + float scale = numSamples ? 1.0f / numSamples : 0.0f; + + int32_t loudness = 0; + isClipping = false; + + if (numChannels == 2) { + int32_t oversLeft = 0; + int32_t oversRight = 0; + + for (int i = 0; i < numSamples/2; i++) { + int32_t left = std::abs((int32_t)samples[2*i+0]); + int32_t right = std::abs((int32_t)samples[2*i+1]); + + loudness += left; + loudness += right; + + if (left > CLIPPING_THRESHOLD) { + isClipping |= (++oversLeft >= CLIPPING_DETECTION); + } else { + oversLeft = 0; + } + if (right > CLIPPING_THRESHOLD) { + isClipping |= (++oversRight >= CLIPPING_DETECTION); + } else { + oversRight = 0; + } + } + } else { + int32_t overs = 0; + + for (int i = 0; i < numSamples; i++) { + int32_t sample = std::abs((int32_t)samples[i]); + + loudness += sample; + + if (sample > CLIPPING_THRESHOLD) { + isClipping |= (++overs >= CLIPPING_DETECTION); + } else { + overs = 0; + } + } + } + + return (float)loudness * scale; +} + static inline float convertToFloat(int16_t sample) { return (float)sample * (1 / 32768.0f); } @@ -1170,17 +1221,10 @@ void AudioClient::handleMicAudioInput() { _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); // detect loudness and clipping on the raw input - int32_t loudness = 0; - bool didClip = false; - for (int i = 0; i < inputSamplesRequired; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)inputAudioSamples.get()[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / inputSamplesRequired; + bool isClipping = false; + _lastInputLoudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); - if (didClip) { + if (isClipping) { _timeSinceLastClip = 0.0f; } else if (_timeSinceLastClip >= 0.0f) { _timeSinceLastClip += AudioConstants::NETWORK_FRAME_SECS; From ba00f95f72f05d6eb38c9d4ab51b8bc3a1e78d6c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 6 Feb 2019 18:23:50 -0800 Subject: [PATCH 37/62] Expose clipping status to audio scripting interface --- interface/src/scripting/Audio.cpp | 23 ++++++++++++++++++---- interface/src/scripting/Audio.h | 14 ++++++++++++- libraries/audio-client/src/AudioClient.cpp | 3 ++- libraries/audio-client/src/AudioClient.h | 2 +- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 524170c7a5..9ccb698da0 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -150,18 +150,33 @@ float Audio::getInputLevel() const { }); } -void Audio::onInputLoudnessChanged(float loudness) { +bool Audio::isClipping() const { + return resultWithReadLock([&] { + return _isClipping; + }); +} + +void Audio::onInputLoudnessChanged(float loudness, bool isClipping) { float level = loudnessToLevel(loudness); - bool changed = false; + bool levelChanged = false; + bool isClippingChanged = false; + withWriteLock([&] { if (_inputLevel != level) { _inputLevel = level; - changed = true; + levelChanged = true; + } + if (_isClipping != isClipping) { + _isClipping = isClipping; + isClippingChanged = true; } }); - if (changed) { + if (levelChanged) { emit inputLevelChanged(level); } + if (isClippingChanged) { + emit clippingChanged(isClipping); + } } QString Audio::getContext() const { diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 4c4bf6dd60..63758d0633 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -41,6 +41,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * above the noise floor. * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – * 1.0 (the onset of clipping). Read-only. + * @property {boolean} clipping - true if the audio input is clipping, otherwise false. * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some * devices, and others might only support values of 0.0 and 1.0. @@ -58,6 +59,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged) + Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) @@ -74,6 +76,7 @@ public: bool noiseReductionEnabled() const; float getInputVolume() const; float getInputLevel() const; + bool isClipping() const; QString getContext() const; void showMicMeter(bool show); @@ -217,6 +220,14 @@ signals: */ void inputLevelChanged(float level); + /**jsdoc + * Triggered when the clipping state of the input audio changes. + * @function Audio.clippingChanged + * @param {boolean} isClipping - true if the audio input is clipping, otherwise false. + * @returns {Signal} + */ + void clippingChanged(bool isClipping); + /**jsdoc * Triggered when the current context of the audio changes. * @function Audio.contextChanged @@ -237,7 +248,7 @@ private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); void setInputVolume(float volume); - void onInputLoudnessChanged(float loudness); + void onInputLoudnessChanged(float loudness, bool isClipping); protected: // Audio must live on a separate thread from AudioClient to avoid deadlocks @@ -247,6 +258,7 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; + bool _isClipping { false }; bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index cd1363a84d..c9cf1646ec 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1229,8 +1229,9 @@ void AudioClient::handleMicAudioInput() { } else if (_timeSinceLastClip >= 0.0f) { _timeSinceLastClip += AudioConstants::NETWORK_FRAME_SECS; } + isClipping = (_timeSinceLastClip >= 0.0f) && (_timeSinceLastClip < 2.0f); // 2 second hold time - emit inputLoudnessChanged(_lastInputLoudness); + emit inputLoudnessChanged(_lastInputLoudness, isClipping); if (!_muted) { possibleResampling(_inputToNetworkResampler, diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index f3e1ad9a52..94ed2ce132 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -248,7 +248,7 @@ signals: void noiseReductionChanged(bool noiseReductionEnabled); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); - void inputLoudnessChanged(float loudness); + void inputLoudnessChanged(float loudness, bool isClipping); void outputBytesToNetwork(int numBytes); void inputBytesFromNetwork(int numBytes); void noiseGateOpened(); From 7fe0e5909e7ef4e13eaeb8cfe7b94e5c40f36465 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 7 Feb 2019 10:14:10 -0800 Subject: [PATCH 38/62] fix black albedo coloring --- libraries/graphics/src/graphics/Material.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index 7befb7e053..7743c4bf50 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -87,7 +87,7 @@ void Material::setUnlit(bool value) { } void Material::setAlbedo(const glm::vec3& albedo, bool isSRGB) { - _key.setAlbedo(glm::any(glm::greaterThan(albedo, glm::vec3(0.0f)))); + _key.setAlbedo(true); _albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo); } From f8608464fabcfc6c7abffde25e679f58c747cb84 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 7 Feb 2019 11:33:44 -0800 Subject: [PATCH 39/62] warning fixes --- libraries/animation/src/AnimPose.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimPose.h b/libraries/animation/src/AnimPose.h index 89dcbaf2ab..4d6dee1987 100644 --- a/libraries/animation/src/AnimPose.h +++ b/libraries/animation/src/AnimPose.h @@ -21,9 +21,9 @@ class AnimPose { public: AnimPose() {} explicit AnimPose(const glm::mat4& mat); - explicit AnimPose(const glm::quat& rotIn) : _scale(1.0f), _rot(rotIn), _trans(0.0f) {} - AnimPose(const glm::quat& rotIn, const glm::vec3& transIn) : _scale(1.0f), _rot(rotIn), _trans(transIn) {} - AnimPose(float scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : _scale(scaleIn), _rot(rotIn), _trans(transIn) {} + explicit AnimPose(const glm::quat& rotIn) : _rot(rotIn), _trans(0.0f), _scale(1.0f) {} + AnimPose(const glm::quat& rotIn, const glm::vec3& transIn) : _rot(rotIn), _trans(transIn), _scale(1.0f) {} + AnimPose(float scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : _rot(rotIn), _trans(transIn), _scale(scaleIn) {} static const AnimPose identity; glm::vec3 xformPoint(const glm::vec3& rhs) const; @@ -36,7 +36,7 @@ public: AnimPose mirror() const; operator glm::mat4() const; - const float scale() const { return _scale; } + float scale() const { return _scale; } float& scale() { return _scale; } const glm::quat& rot() const { return _rot; } From d04445c244397be01411b9b28f7002628feb6544 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 7 Feb 2019 12:56:55 -0800 Subject: [PATCH 40/62] Qml Marketplace - remove some quest incompatibilities and disable links for quest --- .../hifi/commerce/marketplace/MarketplaceItem.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 24ef528673..0a57e56099 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -14,8 +14,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtGraphicalEffects 1.0 -import QtWebEngine 1.5 import stylesUit 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls @@ -424,13 +422,13 @@ Rectangle { elide: Text.ElideRight size: 14 - color: model.link ? hifi.colors.blueHighlight : hifi.colors.baseGray + color: (model.link && root.supports3DHTML)? hifi.colors.blueHighlight : hifi.colors.baseGray verticalAlignment: Text.AlignVCenter MouseArea { anchors.fill: parent onClicked: { - if (model.link) { + if (model.link && root.supports3DHTML) { sendToScript({method: 'marketplace_open_link', link: model.link}); } } @@ -571,12 +569,14 @@ Rectangle { text: root.description size: 14 color: hifi.colors.lightGray - linkColor: hifi.colors.blueHighlight + linkColor: root.supports3DHTML ? hifi.colors.blueHighlight : hifi.colors.lightGray verticalAlignment: Text.AlignVCenter textFormat: Text.RichText wrapMode: Text.Wrap onLinkActivated: { - sendToScript({method: 'marketplace_open_link', link: link}); + if (root.supports3DHTML) { + sendToScript({method: 'marketplace_open_link', link: link}); + } } onHeightChanged: { footer.evalHeight(); } From d71fb2a10085bf760a60b7983526f08ee55877f1 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 7 Feb 2019 14:56:15 -0800 Subject: [PATCH 41/62] moved missed QtWebEngine references to +webengines --- .../resources/qml/+webengine/QmlWebWindow.qml | 108 +++++ interface/resources/qml/QmlWebWindow.qml | 43 -- .../qml/controlsUit/+webengine/WebSpinner.qml | 24 + .../resources/qml/controlsUit/WebSpinner.qml | 8 +- .../qml/hifi/+webengine/DesktopWebEngine.qml | 44 ++ interface/resources/qml/hifi/Desktop.qml | 42 +- .../resources/qml/hifi/DesktopWebEngine.qml | 17 + interface/resources/qml/hifi/WebBrowser.qml | 443 ------------------ .../resources/qml/hifi/avatarapp/Spinner.qml | 1 - .../commerce/marketplace/MarketplaceItem.qml | 1 - .../qml/hifi/tablet/BlocksWebView.qml | 2 - .../resources/qml/hifi/tablet/TabletMenu.qml | 2 - .../qml/hifi/tablet/TabletWebView.qml | 2 - .../qml/hifi/tablet/WindowWebView.qml | 2 - 14 files changed, 208 insertions(+), 531 deletions(-) create mode 100644 interface/resources/qml/+webengine/QmlWebWindow.qml create mode 100644 interface/resources/qml/controlsUit/+webengine/WebSpinner.qml create mode 100644 interface/resources/qml/hifi/+webengine/DesktopWebEngine.qml create mode 100644 interface/resources/qml/hifi/DesktopWebEngine.qml delete mode 100644 interface/resources/qml/hifi/WebBrowser.qml diff --git a/interface/resources/qml/+webengine/QmlWebWindow.qml b/interface/resources/qml/+webengine/QmlWebWindow.qml new file mode 100644 index 0000000000..2e3718f6f5 --- /dev/null +++ b/interface/resources/qml/+webengine/QmlWebWindow.qml @@ -0,0 +1,108 @@ +// +// QmlWebWindow.qml +// +// Created by Bradley Austin Davis on 17 Dec 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtWebEngine 1.1 +import QtWebChannel 1.0 + +import "qrc:////qml//windows" as Windows +import controlsUit 1.0 as Controls +import stylesUit 1.0 + +Windows.ScrollingWindow { + id: root + HifiConstants { id: hifi } + title: "WebWindow" + resizable: true + shown: false + // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer + destroyOnCloseButton: false + property alias source: webview.url + property alias scriptUrl: webview.userScriptUrl + + // This is for JS/QML communication, which is unused in a WebWindow, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + + signal moved(vector2d position); + signal resized(size size); + + function notifyMoved() { + moved(Qt.vector2d(x, y)); + } + + function notifyResized() { + resized(Qt.size(width, height)); + } + + onXChanged: notifyMoved(); + onYChanged: notifyMoved(); + + onWidthChanged: notifyResized(); + onHeightChanged: notifyResized(); + + onShownChanged: { + keyboardEnabled = HMD.active; + } + + Item { + width: pane.contentWidth + implicitHeight: pane.scrollHeight + + Controls.WebView { + id: webview + url: "about:blank" + anchors.fill: parent + focus: true + profile: HFWebEngineProfile; + + property string userScriptUrl: "" + + // Create a global EventBridge object for raiseAndLowerKeyboard. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // Detect when may want to raise and lower keyboard. + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: webview.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] + + function onWebEventReceived(event) { + if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); + } + } + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + eventBridge.webEventReceived.connect(onWebEventReceived); + } + } + } +} diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 322535641d..5da10906ff 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtWebEngine 1.1 -import QtWebChannel 1.0 import "windows" as Windows import controlsUit 1.0 as Controls @@ -62,47 +60,6 @@ Windows.ScrollingWindow { url: "about:blank" anchors.fill: parent focus: true - profile: HFWebEngineProfile; - - property string userScriptUrl: "" - - // Create a global EventBridge object for raiseAndLowerKeyboard. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // Detect when may want to raise and lower keyboard. - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webview.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - function onWebEventReceived(event) { - if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); - } - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - eventBridge.webEventReceived.connect(onWebEventReceived); - } } } } diff --git a/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml b/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml new file mode 100644 index 0000000000..e8e01c4865 --- /dev/null +++ b/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml @@ -0,0 +1,24 @@ +// +// WebSpinner.qml +// +// Created by David Rowe on 23 May 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtWebEngine 1.5 + +AnimatedImage { + property WebEngineView webview: parent + source: "../../icons/loader-snake-64-w.gif" + visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString()) + playing: visible + z: 10000 + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } +} diff --git a/interface/resources/qml/controlsUit/WebSpinner.qml b/interface/resources/qml/controlsUit/WebSpinner.qml index e8e01c4865..fb3dc3a8ab 100644 --- a/interface/resources/qml/controlsUit/WebSpinner.qml +++ b/interface/resources/qml/controlsUit/WebSpinner.qml @@ -9,10 +9,14 @@ // import QtQuick 2.5 -import QtWebEngine 1.5 AnimatedImage { - property WebEngineView webview: parent + Item { + id: webView + property bool loading: false + property string url: "" + } + source: "../../icons/loader-snake-64-w.gif" visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString()) playing: visible diff --git a/interface/resources/qml/hifi/+webengine/DesktopWebEngine.qml b/interface/resources/qml/hifi/+webengine/DesktopWebEngine.qml new file mode 100644 index 0000000000..56cc38254f --- /dev/null +++ b/interface/resources/qml/hifi/+webengine/DesktopWebEngine.qml @@ -0,0 +1,44 @@ +import QtQuick 2.7 +import QtWebEngine 1.5 + +Item { + id: root + + property bool webViewProfileSetup: false + property string currentUrl: "" + property string downloadUrl: "" + property string adaptedPath: "" + property string tempDir: "" + function setupWebEngineSettings() { + WebEngine.settings.javascriptCanOpenWindows = true; + WebEngine.settings.javascriptCanAccessClipboard = false; + WebEngine.settings.spatialNavigationEnabled = false; + WebEngine.settings.localContentCanAccessRemoteUrls = true; + } + + + function initWebviewProfileHandlers(profile) { + downloadUrl = currentUrl; + if (webViewProfileSetup) return; + webViewProfileSetup = true; + + profile.downloadRequested.connect(function(download){ + adaptedPath = File.convertUrlToPath(downloadUrl); + tempDir = File.getTempDir(); + download.path = tempDir + "/" + adaptedPath; + download.accept(); + if (download.state === WebEngineDownloadItem.DownloadInterrupted) { + console.log("download failed to complete"); + } + }) + + profile.downloadFinished.connect(function(download){ + if (download.state === WebEngineDownloadItem.DownloadCompleted) { + File.runUnzip(download.path, downloadUrl, autoAdd); + } else { + console.log("The download was corrupted, state: " + download.state); + } + autoAdd = false; + }) + } +} diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 731477e2ae..c44ebdbab1 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -1,5 +1,4 @@ import QtQuick 2.7 -import QtWebEngine 1.5; import Qt.labs.settings 1.0 as QtSettings import QtQuick.Controls 2.3 @@ -88,43 +87,20 @@ OriginalDesktop.Desktop { })({}); Component.onCompleted: { - WebEngine.settings.javascriptCanOpenWindows = true; - WebEngine.settings.javascriptCanAccessClipboard = false; - WebEngine.settings.spatialNavigationEnabled = false; - WebEngine.settings.localContentCanAccessRemoteUrls = true; + webEngineConfig.setupWebEngineSettings(); } // Accept a download through the webview - property bool webViewProfileSetup: false - property string currentUrl: "" - property string downloadUrl: "" - property string adaptedPath: "" - property string tempDir: "" + property alias webViewProfileSetup: webEngineConfig.webViewProfileSetup + property alias currentUrl: webEngineConfig.currentUrl + property alias downloadUrl: webEngineConfig.downloadUrl + property alias adaptedPath: webEngineConfig.adaptedPath + property alias tempDir: webEngineConfig.tempDir + property var initWebviewProfileHandlers: webEngineConfig.initWebviewProfileHandlers property bool autoAdd: false - function initWebviewProfileHandlers(profile) { - downloadUrl = currentUrl; - if (webViewProfileSetup) return; - webViewProfileSetup = true; - - profile.downloadRequested.connect(function(download){ - adaptedPath = File.convertUrlToPath(downloadUrl); - tempDir = File.getTempDir(); - download.path = tempDir + "/" + adaptedPath; - download.accept(); - if (download.state === WebEngineDownloadItem.DownloadInterrupted) { - console.log("download failed to complete"); - } - }) - - profile.downloadFinished.connect(function(download){ - if (download.state === WebEngineDownloadItem.DownloadCompleted) { - File.runUnzip(download.path, downloadUrl, autoAdd); - } else { - console.log("The download was corrupted, state: " + download.state); - } - autoAdd = false; - }) + DesktopWebEngine { + id: webEngineConfig } function setAutoAdd(auto) { diff --git a/interface/resources/qml/hifi/DesktopWebEngine.qml b/interface/resources/qml/hifi/DesktopWebEngine.qml new file mode 100644 index 0000000000..58c6244e7e --- /dev/null +++ b/interface/resources/qml/hifi/DesktopWebEngine.qml @@ -0,0 +1,17 @@ +import QtQuick 2.7 + +Item { + id: root + + property bool webViewProfileSetup: false + property string currentUrl: "" + property string downloadUrl: "" + property string adaptedPath: "" + property string tempDir: "" + function setupWebEngineSettings() { + } + + + function initWebviewProfileHandlers(profile) { + } +} diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml deleted file mode 100644 index c05de26471..0000000000 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ /dev/null @@ -1,443 +0,0 @@ - -// -// WebBrowser.qml -// -// -// Created by Vlad Stelmahovsky on 06/22/2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import QtQuick.Controls 2.2 as QQControls -import QtQuick.Layouts 1.3 -import QtGraphicalEffects 1.0 - -import QtWebEngine 1.5 -import QtWebChannel 1.0 - -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls -import "../windows" -import "../controls" - -import HifiWeb 1.0 - -Rectangle { - id: root; - - HifiConstants { id: hifi; } - - property string title: ""; - signal sendToScript(var message); - property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false - property bool keyboardRaised: false - property bool punctuationMode: false - property var suggestionsList: [] - readonly property string searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q="; - - - WebBrowserSuggestionsEngine { - id: searchEngine - - onSuggestions: { - if (suggestions.length > 0) { - suggestionsList = [] - suggestionsList.push(addressBarInput.text); //do not overwrite edit text - for(var i = 0; i < suggestions.length; i++) { - suggestionsList.push(suggestions[i]); - } - addressBar.model = suggestionsList - if (!addressBar.popup.visible) { - addressBar.popup.open(); - } - } - } - } - - Timer { - id: suggestionRequestTimer - interval: 200 - repeat: false - onTriggered: { - if (addressBar.editText !== "") { - searchEngine.querySuggestions(addressBarInput.text); - } - } - } - - color: hifi.colors.baseGray; - - function goTo(url) { - //must be valid attempt to open an site with dot - var urlNew = url - if (url.indexOf(".") > 0) { - if (url.indexOf("http") < 0) { - urlNew = "http://" + url; - } - } else { - urlNew = searchUrlTemplate + url - } - - addressBar.model = [] - //need to rebind if binfing was broken by selecting from suggestions - addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; }); - webStack.currentItem.webEngineView.url = urlNew - suggestionRequestTimer.stop(); - addressBar.popup.close(); - } - - Column { - spacing: 2 - width: parent.width; - - RowLayout { - id: addressBarRow - width: parent.width; - height: 48 - - HifiControls.WebGlyphButton { - enabled: webStack.currentItem.webEngineView.canGoBack || webStack.depth > 1 - glyph: hifi.glyphs.backward; - anchors.verticalCenter: parent.verticalCenter; - size: 38; - onClicked: { - if (webStack.currentItem.webEngineView.canGoBack) { - webStack.currentItem.webEngineView.goBack(); - } else if (webStack.depth > 1) { - webStack.pop(); - } - } - } - - HifiControls.WebGlyphButton { - enabled: webStack.currentItem.webEngineView.canGoForward - glyph: hifi.glyphs.forward; - anchors.verticalCenter: parent.verticalCenter; - size: 38; - onClicked: { - webStack.currentItem.webEngineView.goForward(); - } - } - - QQControls.ComboBox { - id: addressBar - - //selectByMouse: true - focus: true - - editable: true - //flat: true - indicator: Item {} - background: Item {} - onActivated: { - goTo(textAt(index)); - } - - onHighlightedIndexChanged: { - if (highlightedIndex >= 0) { - addressBar.editText = textAt(highlightedIndex) - } - } - - popup.height: webStack.height - - onFocusChanged: { - if (focus) { - addressBarInput.selectAll(); - } - } - - contentItem: QQControls.TextField { - id: addressBarInput - leftPadding: 26 - rightPadding: hifi.dimensions.controlLineHeight + 5 - text: addressBar.editText - placeholderText: qsTr("Enter URL") - font: addressBar.font - selectByMouse: true - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - onFocusChanged: { - if (focus) { - selectAll(); - } - } - - Keys.onDeletePressed: { - addressBarInput.text = "" - } - - Keys.onPressed: { - if (event.key === Qt.Key_Return) { - goTo(addressBarInput.text); - event.accepted = true; - } - } - - Image { - anchors.verticalCenter: parent.verticalCenter; - x: 5 - z: 2 - id: faviconImage - width: 16; height: 16 - sourceSize: Qt.size(width, height) - source: webStack.currentItem.webEngineView.icon - } - - HifiControls.WebGlyphButton { - glyph: webStack.currentItem.webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; - anchors.verticalCenter: parent.verticalCenter; - width: hifi.dimensions.controlLineHeight - z: 2 - x: addressBarInput.width - implicitWidth - onClicked: { - if (webStack.currentItem.webEngineView.loading) { - webStack.currentItem.webEngineView.stop(); - } else { - webStack.currentItem.reloadTimer.start(); - } - } - } - } - - Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i"); - - Keys.onPressed: { - if (event.key === Qt.Key_Return) { - goTo(addressBarInput.text); - event.accepted = true; - } - } - - onEditTextChanged: { - if (addressBar.editText !== "" && addressBar.editText !== webStack.currentItem.webEngineView.url.toString()) { - suggestionRequestTimer.restart(); - } else { - addressBar.model = [] - addressBar.popup.close(); - } - - } - - Layout.fillWidth: true - editText: webStack.currentItem.webEngineView.url - onAccepted: goTo(addressBarInput.text); - } - - HifiControls.WebGlyphButton { - checkable: true - checked: webStack.currentItem.webEngineView.audioMuted - glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted - anchors.verticalCenter: parent.verticalCenter; - width: hifi.dimensions.controlLineHeight - onClicked: { - webStack.currentItem.webEngineView.audioMuted = !webStack.currentItem.webEngineView.audioMuted - } - } - } - - QQControls.ProgressBar { - id: loadProgressBar - background: Rectangle { - implicitHeight: 2 - color: "#6A6A6A" - } - - contentItem: Item { - implicitHeight: 2 - - Rectangle { - width: loadProgressBar.visualPosition * parent.width - height: parent.height - color: "#00B4EF" - } - } - - width: parent.width; - from: 0 - to: 100 - value: webStack.currentItem.webEngineView.loadProgress - height: 2 - } - - Component { - id: webViewComponent - Rectangle { - property alias webEngineView: webEngineView - property alias reloadTimer: reloadTimer - - property WebEngineNewViewRequest request: null - - property bool isDialog: QQControls.StackView.index > 0 - property real margins: isDialog ? 10 : 0 - - color: "#d1d1d1" - - QQControls.StackView.onActivated: { - addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; }); - } - - onRequestChanged: { - if (isDialog && request !== null && request !== undefined) {//is Dialog ? - request.openIn(webEngineView); - } - } - - HifiControls.BaseWebView { - id: webEngineView - anchors.fill: parent - anchors.margins: parent.margins - - layer.enabled: parent.isDialog - layer.effect: DropShadow { - verticalOffset: 8 - horizontalOffset: 8 - color: "#330066ff" - samples: 10 - spread: 0.5 - } - - focus: true - objectName: "tabletWebEngineView" - - //profile: HFWebEngineProfile; - profile.httpUserAgent: "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0" - - property string userScriptUrl: "" - - onLoadingChanged: { - if (!loading) { - addressBarInput.cursorPosition = 0 //set input field cursot to beginning - suggestionRequestTimer.stop(); - addressBar.popup.close(); - } - } - - onLinkHovered: { - //TODO: change cursor shape? - } - - // creates a global EventBridge object. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.Deferred - worldId: WebEngineScript.MainWorld - } - - // detects when to raise and lower virtual keyboard - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webEngineView.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - settings.autoLoadImages: true - settings.javascriptEnabled: true - settings.errorPageEnabled: true - settings.pluginsEnabled: true - settings.fullScreenSupportEnabled: true - settings.autoLoadIconsForPage: true - settings.touchIconsEnabled: true - - onCertificateError: { - error.defer(); - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - } - - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } - - onNewViewRequested: { - if (request.destination == WebEngineView.NewViewInDialog) { - webStack.push(webViewComponent, {"request": request}); - } else { - request.openIn(webEngineView); - } - } - - onRenderProcessTerminated: { - var status = ""; - switch (terminationStatus) { - case WebEngineView.NormalTerminationStatus: - status = "(normal exit)"; - break; - case WebEngineView.AbnormalTerminationStatus: - status = "(abnormal exit)"; - break; - case WebEngineView.CrashedTerminationStatus: - status = "(crashed)"; - break; - case WebEngineView.KilledTerminationStatus: - status = "(killed)"; - break; - } - - console.error("Render process exited with code " + exitCode + " " + status); - reloadTimer.running = true; - } - - onFullScreenRequested: { - if (request.toggleOn) { - webEngineView.state = "FullScreen"; - } else { - webEngineView.state = ""; - } - request.accept(); - } - - onWindowCloseRequested: { - webStack.pop(); - } - } - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: webEngineView.reload() - } - } - } - - QQControls.StackView { - id: webStack - width: parent.width; - property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 - height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - - Component.onCompleted: webStack.push(webViewComponent, {"webEngineView.url": "https://www.highfidelity.com"}); - } - } - - - HifiControls.Keyboard { - id: keyboard - raised: parent.keyboardEnabled && parent.keyboardRaised - numeric: parent.punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - } -} diff --git a/interface/resources/qml/hifi/avatarapp/Spinner.qml b/interface/resources/qml/hifi/avatarapp/Spinner.qml index 3fc331346d..14f8e922d7 100644 --- a/interface/resources/qml/hifi/avatarapp/Spinner.qml +++ b/interface/resources/qml/hifi/avatarapp/Spinner.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtWebEngine 1.5 AnimatedImage { source: "../../../icons/loader-snake-64-w.gif" diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 24ef528673..1909157a79 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -15,7 +15,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.9 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import QtWebEngine 1.5 import stylesUit 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls diff --git a/interface/resources/qml/hifi/tablet/BlocksWebView.qml b/interface/resources/qml/hifi/tablet/BlocksWebView.qml index 1e9eb3beb4..03fce0a112 100644 --- a/interface/resources/qml/hifi/tablet/BlocksWebView.qml +++ b/interface/resources/qml/hifi/tablet/BlocksWebView.qml @@ -1,6 +1,4 @@ import QtQuick 2.0 -import QtWebEngine 1.2 - import "../../controls" as Controls Controls.TabletWebView { diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 267fb9f0cf..5f06e4fbab 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -2,8 +2,6 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import QtQml 2.2 -import QtWebChannel 1.0 -import QtWebEngine 1.1 import "." diff --git a/interface/resources/qml/hifi/tablet/TabletWebView.qml b/interface/resources/qml/hifi/tablet/TabletWebView.qml index ff6be0480f..9eba7824e0 100644 --- a/interface/resources/qml/hifi/tablet/TabletWebView.qml +++ b/interface/resources/qml/hifi/tablet/TabletWebView.qml @@ -1,6 +1,4 @@ import QtQuick 2.0 -import QtWebEngine 1.2 - import "../../controls" as Controls Controls.TabletWebScreen { diff --git a/interface/resources/qml/hifi/tablet/WindowWebView.qml b/interface/resources/qml/hifi/tablet/WindowWebView.qml index 0f697d634e..632ab712cb 100644 --- a/interface/resources/qml/hifi/tablet/WindowWebView.qml +++ b/interface/resources/qml/hifi/tablet/WindowWebView.qml @@ -1,6 +1,4 @@ import QtQuick 2.0 -import QtWebEngine 1.2 - import "../../controls" as Controls Controls.WebView { From 446176d3a516d81dc100789c04cccc021c2d6aa1 Mon Sep 17 00:00:00 2001 From: raveenajain Date: Thu, 7 Feb 2019 15:08:04 -0800 Subject: [PATCH 42/62] update to parse glb files --- libraries/fbx/src/GLTFSerializer.cpp | 67 +++++++++++++++++++++++++--- libraries/fbx/src/GLTFSerializer.h | 3 ++ 2 files changed, 64 insertions(+), 6 deletions(-) mode change 100644 => 100755 libraries/fbx/src/GLTFSerializer.cpp mode change 100644 => 100755 libraries/fbx/src/GLTFSerializer.h diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp old mode 100644 new mode 100755 index 96c236f703..f2ea16aa31 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -124,6 +124,31 @@ bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString& return _defined; } +QByteArray GLTFSerializer::setGLBChunks(const QByteArray& data) { + int byte = 4; + int jsonStart = data.indexOf("JSON", Qt::CaseSensitive); + int binStart = data.indexOf("BIN", Qt::CaseSensitive); + int jsonLength, binLength; + QByteArray jsonLengthChunk, binLengthChunk; + + jsonLengthChunk = data.mid(jsonStart - byte, byte); + QDataStream tempJsonLen(jsonLengthChunk); + tempJsonLen.setByteOrder(QDataStream::LittleEndian); + tempJsonLen >> jsonLength; + QByteArray jsonChunk = data.mid(jsonStart + byte, jsonLength); + + if (binStart != -1) { + binLengthChunk = data.mid(binStart - byte, byte); + + QDataStream tempBinLen(binLengthChunk); + tempBinLen.setByteOrder(QDataStream::LittleEndian); + tempBinLen >> binLength; + + _glbBinary = data.mid(binStart + byte, binLength); + } + return jsonChunk; +} + int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) { if (type == "POINTS") { @@ -309,6 +334,14 @@ bool GLTFSerializer::addBuffer(const QJsonObject& object) { GLTFBuffer buffer; getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); + + if (_url.toString().endsWith("glb")) { + if (!_glbBinary.isEmpty()) { + buffer.blob = _glbBinary; + } else { + return false; + } + } if (getStringVal(object, "uri", buffer.uri, buffer.defined)) { if (!readBinary(buffer.uri, buffer.blob)) { return false; @@ -530,9 +563,16 @@ bool GLTFSerializer::addTexture(const QJsonObject& object) { bool GLTFSerializer::parseGLTF(const QByteArray& data) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); - - QJsonDocument d = QJsonDocument::fromJson(data); + + QByteArray jsonChunk = data; + + if (_url.toString().endsWith("glb") && data.indexOf("glTF") == 0 && data.contains("JSON")) { + jsonChunk = setGLBChunks(data); + } + + QJsonDocument d = QJsonDocument::fromJson(jsonChunk); QJsonObject jsFile = d.object(); + bool success = setAsset(jsFile); if (success) { QJsonArray accessors; @@ -904,6 +944,10 @@ MediaType GLTFSerializer::getMediaType() const { MediaType mediaType("gltf"); mediaType.extensions.push_back("gltf"); mediaType.webMediaTypes.push_back("model/gltf+json"); + + mediaType.extensions.push_back("glb"); + mediaType.webMediaTypes.push_back("model/gltf-binary"); + return mediaType; } @@ -912,9 +956,9 @@ std::unique_ptr GLTFSerializer::getFactory() const { } HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) { - - _url = url; + _url = url; + // Normalize url for local files QUrl normalizeUrl = DependencyManager::get()->normalizeURL(_url); if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) { @@ -943,7 +987,7 @@ bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) { bool success; std::tie(success, outdata) = requestData(binaryUrl); - + return success; } @@ -1003,7 +1047,7 @@ QNetworkReply* GLTFSerializer::request(QUrl& url, bool isTest) { HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { HFMTexture fbxtex = HFMTexture(); fbxtex.texcoordSet = 0; - + if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; QString fname = QUrl(url).fileName(); @@ -1011,6 +1055,17 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { qCDebug(modelformat) << "fname: " << fname; fbxtex.name = fname; fbxtex.filename = textureUrl.toEncoded(); + + if (_url.toString().endsWith("glb") && !_glbBinary.isEmpty()) { + int bufferView = _file.images[texture.source].bufferView; + + GLTFBufferView& imagesBufferview = _file.bufferviews[bufferView]; + int offset = imagesBufferview.byteOffset; + int length = imagesBufferview.byteLength; + + fbxtex.content = _glbBinary.mid(offset, length); + fbxtex.filename = textureUrl.toEncoded().append(texture.source); + } } return fbxtex; } diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h old mode 100644 new mode 100755 index 5fca77c4fd..fe1dea49e0 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -709,6 +709,7 @@ public: private: GLTFFile _file; QUrl _url; + QByteArray _glbBinary; glm::mat4 getModelTransform(const GLTFNode& node); @@ -731,6 +732,8 @@ private: QVector& values, QMap& defined); bool getObjectArrayVal(const QJsonObject& object, const QString& fieldname, QJsonArray& objects, QMap& defined); + + QByteArray setGLBChunks(const QByteArray& data); int getMaterialAlphaMode(const QString& type); int getAccessorType(const QString& type); From 2617febbcd8a31de616beb35347568d00d77bedd Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 15:31:13 -0800 Subject: [PATCH 43/62] Replace ad-hoc audio meter with meter calibrated in dBFS --- interface/src/scripting/Audio.cpp | 24 ++++------------------ libraries/audio-client/src/AudioClient.cpp | 2 +- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 9ccb698da0..d9185a9a70 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -15,6 +15,7 @@ #include "Application.h" #include "AudioClient.h" +#include "AudioHelpers.h" #include "ui/AvatarInputs.h" using namespace scripting; @@ -26,26 +27,9 @@ QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; float Audio::loudnessToLevel(float loudness) { - const float LOG2 = log(2.0f); - const float METER_LOUDNESS_SCALE = 2.8f / 5.0f; - const float LOG2_LOUDNESS_FLOOR = 11.0f; - - float level = 0.0f; - - loudness += 1.0f; - float log2loudness = logf(loudness) / LOG2; - - if (log2loudness <= LOG2_LOUDNESS_FLOOR) { - level = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE; - } else { - level = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE; - } - - if (level > 1.0f) { - level = 1.0; - } - - return level; + float level = 6.02059991f * fastLog2f(loudness); // level in dBFS + level = (level + 60.0f) * (1/48.0f); // map [-60, -12] dBFS to [0, 1] + return glm::clamp(level, 0.0f, 1.0f); } Audio::Audio() : _devices(_contextIsHMD) { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c9cf1646ec..4489d19806 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -175,7 +175,7 @@ static float computeLoudness(int16_t* samples, int numSamples, int numChannels, const int32_t CLIPPING_THRESHOLD = 32392; // -0.1 dBFS const int32_t CLIPPING_DETECTION = 3; // consecutive samples over threshold - float scale = numSamples ? 1.0f / numSamples : 0.0f; + float scale = numSamples ? 1.0f / (numSamples * 32768.0f) : 0.0f; int32_t loudness = 0; isClipping = false; From 4bbb030ad74e76f00b0035a04e47c210a6f368ec Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 7 Feb 2019 15:34:44 -0800 Subject: [PATCH 44/62] Show only free items when running from the oculus store --- .../qml/hifi/commerce/marketplace/Marketplace.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 351c43286c..0d42cb599e 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -38,8 +38,8 @@ Rectangle { property bool keyboardEnabled: HMD.active property bool keyboardRaised: false property string searchScopeString: "Featured" - property bool isLoggedIn: false; - property bool supports3DHTML: true; + property bool isLoggedIn: false + property bool supports3DHTML: true anchors.fill: (typeof parent === undefined) ? undefined : parent @@ -49,7 +49,7 @@ Rectangle { licenseInfo.visible = false; marketBrowseModel.getFirstPage(); { - if(root.searchString !== undefined && root.searchString !== "") { + if(root.searchString !== undefined && root.searchString !== "") { root.searchScopeString = "Search Results: \"" + root.searchString + "\""; } else if (root.categoryString !== "") { root.searchScopeString = root.categoryString; @@ -498,7 +498,7 @@ Rectangle { "", "", root.sortString, - false, + WalletScriptingInterface.limitedCommerce, marketBrowseModel.currentPageToRetrieve, marketBrowseModel.itemsPerPage ); From d014dc2d1447d08c0af322f9adb5e1b5450b8cac Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 15:35:20 -0800 Subject: [PATCH 45/62] Add audio meter ballistics for less display jitter (10ms attack, 300ms release) --- libraries/audio-client/src/AudioClient.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4489d19806..60a95ff58a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1222,7 +1222,11 @@ void AudioClient::handleMicAudioInput() { // detect loudness and clipping on the raw input bool isClipping = false; - _lastInputLoudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); + float inputLoudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); + + float tc = (inputLoudness > _lastInputLoudness) ? 0.378f : 0.967f; // 10ms attack, 300ms release @ 100Hz + inputLoudness += tc * (_lastInputLoudness - inputLoudness); + _lastInputLoudness = inputLoudness; if (isClipping) { _timeSinceLastClip = 0.0f; From 52aa20b4d58074a18033deaae28c8a317d2ea557 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 15:47:56 -0800 Subject: [PATCH 46/62] Add "gated" indicator to audio level meter --- interface/resources/qml/hifi/audio/MicBar.qml | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index fee37ca1c1..3dbf55818e 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -16,7 +16,13 @@ import TabletScriptingInterface 1.0 Rectangle { readonly property var level: AudioScriptingInterface.inputLevel; - + + property bool gated: false; + Component.onCompleted: { + AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); + AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + } + property bool standalone: false; property var dragTarget: null; @@ -189,7 +195,7 @@ Rectangle { Rectangle { // mask id: mask; - width: parent.width * level; + width: gated ? 0 : parent.width * level; radius: 5; anchors { bottom: parent.bottom; @@ -225,5 +231,19 @@ Rectangle { } } } + + Rectangle { + id: gatedIndicator; + visible: gated + + radius: 4; + width: 2 * radius; + height: 2 * radius; + color: "#0080FF"; + anchors { + right: parent.left; + verticalCenter: parent.verticalCenter; + } + } } } From d2abf1a56d5e9e67cfa9a064d415f8564b8381d4 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 15:52:29 -0800 Subject: [PATCH 47/62] Add "clipping" indicator to audio level meter --- interface/resources/qml/hifi/audio/MicBar.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 3dbf55818e..615235470f 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -234,7 +234,7 @@ Rectangle { Rectangle { id: gatedIndicator; - visible: gated + visible: gated && !AudioScriptingInterface.clipping radius: 4; width: 2 * radius; @@ -245,5 +245,19 @@ Rectangle { verticalCenter: parent.verticalCenter; } } + + Rectangle { + id: clippingIndicator; + visible: AudioScriptingInterface.clipping + + radius: 4; + width: 2 * radius; + height: 2 * radius; + color: colors.red; + anchors { + left: parent.right; + verticalCenter: parent.verticalCenter; + } + } } } From b04a59af1ba7cd4be7e7842d3149dd1f782fc39c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 16:31:25 -0800 Subject: [PATCH 48/62] Calibrate meter to encourage hotter mic levels --- interface/src/scripting/Audio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index d9185a9a70..fb64dbe098 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -28,7 +28,7 @@ Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, float Audio::loudnessToLevel(float loudness) { float level = 6.02059991f * fastLog2f(loudness); // level in dBFS - level = (level + 60.0f) * (1/48.0f); // map [-60, -12] dBFS to [0, 1] + level = (level + 48.0f) * (1/39.0f); // map [-48, -9] dBFS to [0, 1] return glm::clamp(level, 0.0f, 1.0f); } From 5681a996a1b9f74810c4c6261d15fe5a2162aafb Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 16:33:41 -0800 Subject: [PATCH 49/62] Fix meter color gradient, to encourage proper mic levels. High levels are now yellow, actual digital clipping sets red indicator light. --- interface/resources/qml/hifi/audio/MicBar.qml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 615235470f..39f75a9182 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -83,6 +83,7 @@ Rectangle { readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; + readonly property string yellow: "#C0C000"; readonly property string red: colors.muted; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; @@ -218,16 +219,12 @@ Rectangle { color: colors.greenStart; } GradientStop { - position: 0.8; + position: 0.5; color: colors.greenEnd; } - GradientStop { - position: 0.81; - color: colors.red; - } GradientStop { position: 1; - color: colors.red; + color: colors.yellow; } } } From 79bf26ee27332aa8a94af93b9fc2ff39a9be423d Mon Sep 17 00:00:00 2001 From: Matt Hardcastle Date: Thu, 7 Feb 2019 15:57:14 -0800 Subject: [PATCH 50/62] Add prebuild metrics logging for CI system Metric about the times various parts of the build take are great to have. This change takes us a step in that direction by adding metrics to the prebuild step of the build process. These metrics are off by default; use the `--ci-build` option of `prebuild.py` to enable them. --- prebuild.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/prebuild.py b/prebuild.py index fb54b8d6fe..060e1fd3b0 100644 --- a/prebuild.py +++ b/prebuild.py @@ -35,9 +35,50 @@ import re import tempfile import time import functools +import subprocess +import logging + +from uuid import uuid4 +from contextlib import contextmanager print = functools.partial(print, flush=True) +class TrackableLogger(logging.Logger): + guid = str(uuid4()) + + def _log(self, msg, *args, **kwargs): + x = {'guid': self.guid} + if 'extra' in kwargs: + kwargs['extra'].update(x) + else: + kwargs['extra'] = x + super()._log(msg, *args, **kwargs) + +logging.setLoggerClass(TrackableLogger) +logger = logging.getLogger('prebuild') + +def headSha(): + repo_dir = os.path.dirname(os.path.abspath(__file__)) + git = subprocess.Popen( + 'git rev-parse --short HEAD', + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + shell=True, cwd=repo_dir, universal_newlines=True, + ) + stdout, _ = git.communicate() + sha = stdout.split('\n')[0] + if not sha: + raise RuntimeError("couldn't find git sha") + return sha + +@contextmanager +def timer(name): + ''' Print the elapsed time a context's execution takes to execute ''' + start = time.time() + yield + # Please take care when modifiying this print statement. + # Log parsing logic may depend on it. + logger.info('%s took %.3f secs' % (name, time.time() - start)) + def parse_args(): # our custom ports, relative to the script location defaultPortsPath = hifi_utils.scriptRelative('cmake', 'ports') @@ -50,6 +91,7 @@ def parse_args(): parser.add_argument('--vcpkg-root', type=str, help='The location of the vcpkg distribution') parser.add_argument('--build-root', required=True, type=str, help='The location of the cmake build') parser.add_argument('--ports-path', type=str, default=defaultPortsPath) + parser.add_argument('--ci-build', action='store_true') if True: args = parser.parse_args() else: @@ -66,11 +108,19 @@ def main(): del os.environ[var] args = parse_args() + + if args.ci_build: + logging.basicConfig(datefmt='%s', format='%(asctime)s %(guid)s %(message)s', level=logging.INFO) + + logger.info('sha=%s' % headSha()) + logger.info('start') + # Only allow one instance of the program to run at a time pm = hifi_vcpkg.VcpkgRepo(args) with hifi_singleton.Singleton(pm.lockFile) as lock: - if not pm.upToDate(): - pm.bootstrap() + with timer('Bootstraping'): + if not pm.upToDate(): + pm.bootstrap() # Always write the tag, even if we changed nothing. This # allows vcpkg to reclaim disk space by identifying directories with @@ -80,11 +130,13 @@ def main(): # Grab our required dependencies: # * build host tools, like spirv-cross and scribe # * build client dependencies like openssl and nvtt - pm.setupDependencies() + with timer('Setting up dependencies'): + pm.setupDependencies() # wipe out the build directories (after writing the tag, since failure # here shouldn't invalidte the vcpkg install) - pm.cleanBuilds() + with timer('Cleaning builds'): + pm.cleanBuilds() # If we're running in android mode, we also need to grab a bunch of additional binaries # (this logic is all migrated from the old setupDependencies tasks in gradle) @@ -98,7 +150,10 @@ def main(): hifi_android.QtPackager(appPath, qtPath).bundle() # Write the vcpkg config to the build directory last - pm.writeConfig() + with timer('Writing configuration'): + pm.writeConfig() + + logger.info('end') print(sys.argv) main() From e6b2c890d0ec2354bddd282f290547cd005425d3 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 7 Feb 2019 22:21:05 -0800 Subject: [PATCH 51/62] Case21085 - Changing domains with scripting APIs not working AccountManager was earlier changed to support QUrlQuery for query strings in it's 'send.' Unfortunately, QUrlQuery isn't a scriptable property type so using AM was failing from scripts --- interface/src/commerce/QmlMarketplace.cpp | 14 +++++-------- interface/src/commerce/QmlMarketplace.h | 7 ++++++- libraries/networking/src/AccountManager.cpp | 23 ++++++++++++--------- libraries/networking/src/AccountManager.h | 5 ++--- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/interface/src/commerce/QmlMarketplace.cpp b/interface/src/commerce/QmlMarketplace.cpp index 07a9e570bd..23ba418a2d 100644 --- a/interface/src/commerce/QmlMarketplace.cpp +++ b/interface/src/commerce/QmlMarketplace.cpp @@ -72,20 +72,17 @@ void QmlMarketplace::getMarketplaceItems( void QmlMarketplace::getMarketplaceItem(const QString& marketplaceItemId) { QString endpoint = QString("items/") + marketplaceItemId; - QUrlQuery request; - send(endpoint, "getMarketplaceItemSuccess", "getMarketplaceItemFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request); + send(endpoint, "getMarketplaceItemSuccess", "getMarketplaceItemFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional); } void QmlMarketplace::marketplaceItemLike(const QString& marketplaceItemId, const bool like) { QString endpoint = QString("items/") + marketplaceItemId + "/like"; - QUrlQuery request; - send(endpoint, "marketplaceItemLikeSuccess", "marketplaceItemLikeFailure", like ? QNetworkAccessManager::PostOperation : QNetworkAccessManager::DeleteOperation, AccountManagerAuth::Required, request); + send(endpoint, "marketplaceItemLikeSuccess", "marketplaceItemLikeFailure", like ? QNetworkAccessManager::PostOperation : QNetworkAccessManager::DeleteOperation, AccountManagerAuth::Required); } void QmlMarketplace::getMarketplaceCategories() { QString endpoint = "categories"; - QUrlQuery request; - send(endpoint, "getMarketplaceCategoriesSuccess", "getMarketplaceCategoriesFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None, request); + send(endpoint, "getMarketplaceCategoriesSuccess", "getMarketplaceCategoriesFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None); } @@ -94,14 +91,13 @@ void QmlMarketplace::send(const QString& endpoint, const QString& success, const const QString URL = "/api/v1/marketplace/"; JSONCallbackParameters callbackParams(this, success, fail); - accountManager->sendRequest(URL + endpoint, + accountManager->sendRequest(URL + endpoint + "?" + request.toString(), authType, method, callbackParams, QByteArray(), NULL, - QVariantMap(), - request); + QVariantMap()); } diff --git a/interface/src/commerce/QmlMarketplace.h b/interface/src/commerce/QmlMarketplace.h index f954198371..5794d4f53c 100644 --- a/interface/src/commerce/QmlMarketplace.h +++ b/interface/src/commerce/QmlMarketplace.h @@ -60,7 +60,12 @@ signals: void marketplaceItemLikeResult(QJsonObject result); private: - void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, const QUrlQuery & request); + void send(const QString& endpoint, + const QString& success, + const QString& fail, + QNetworkAccessManager::Operation method, + AccountManagerAuth::Type authType, + const QUrlQuery& request = QUrlQuery()); QJsonObject apiResponse(const QString& label, QNetworkReply* reply); QJsonObject failResponse(const QString& label, QNetworkReply* reply); }; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index cf77c1cad5..4647c50496 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -208,7 +208,7 @@ void AccountManager::setSessionID(const QUuid& sessionID) { } } -QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType, const QUrlQuery & query) { +QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType) { QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); @@ -217,17 +217,22 @@ QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth:: uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); QUrl requestURL = _authURL; - + if (requestURL.isEmpty()) { // Assignment client doesn't set _authURL. requestURL = getMetaverseServerURL(); } + int queryStringLocation = path.indexOf("?"); if (path.startsWith("/")) { - requestURL.setPath(path); + requestURL.setPath(path.left(queryStringLocation)); } else { - requestURL.setPath("/" + path); + requestURL.setPath("/" + path.left(queryStringLocation)); + } + + if (queryStringLocation >= 0) { + QUrlQuery query(path.mid(queryStringLocation+1)); + requestURL.setQuery(query); } - requestURL.setQuery(query); if (authType != AccountManagerAuth::None ) { if (hasValidAccessToken()) { @@ -253,8 +258,7 @@ void AccountManager::sendRequest(const QString& path, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart, - const QVariantMap& propertyMap, - QUrlQuery query) { + const QVariantMap& propertyMap) { if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "sendRequest", @@ -264,14 +268,13 @@ void AccountManager::sendRequest(const QString& path, Q_ARG(const JSONCallbackParameters&, callbackParams), Q_ARG(const QByteArray&, dataByteArray), Q_ARG(QHttpMultiPart*, dataMultiPart), - Q_ARG(QVariantMap, propertyMap), - Q_ARG(QUrlQuery, query)); + Q_ARG(QVariantMap, propertyMap)); return; } QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = createRequest(path, authType, query); + QNetworkRequest networkRequest = createRequest(path, authType); if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qCDebug(networking) << "Making a request to" << qPrintable(networkRequest.url().toString()); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 2ccebdd73c..8732042e93 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -61,15 +61,14 @@ class AccountManager : public QObject, public Dependency { public: AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER); - QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType, const QUrlQuery & query = QUrlQuery()); + QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType); Q_INVOKABLE void sendRequest(const QString& path, AccountManagerAuth::Type authType, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), const QByteArray& dataByteArray = QByteArray(), QHttpMultiPart* dataMultiPart = NULL, - const QVariantMap& propertyMap = QVariantMap(), - QUrlQuery query = QUrlQuery()); + const QVariantMap& propertyMap = QVariantMap()); void setIsAgent(bool isAgent) { _isAgent = isAgent; } From 5b7d7b88331e0d5607cb6676b52b5056d7846cca Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 8 Feb 2019 08:53:43 -0800 Subject: [PATCH 52/62] Pack all non-instanced traits Previously this code only would pack the skeletonModelURL trait. Which is technically not a bug, because there it is the only non-instanced trait. But, we plan to add new traits in the future. So, lets fix this now. --- libraries/avatars/src/ClientTraitsHandler.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index bcbe5308c7..3e188afbdf 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -107,11 +107,10 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() { if (initialSend || *simpleIt == Updated) { if (traitType == AvatarTraits::SkeletonModelURL) { - bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList); - // keep track of our skeleton version in case we get an override back _currentSkeletonVersion = _currentTraitVersion; } + bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList); } ++simpleIt; From 76a56c679a3c95209b7343bc239e97586e165230 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 8 Feb 2019 10:03:05 -0800 Subject: [PATCH 53/62] making requested changes --- interface/resources/images/unsupportedImage.png | Bin 0 -> 24855 bytes interface/resources/qml/QmlWebWindow.qml | 1 + .../qml/controlsUit/+webengine/WebSpinner.qml | 2 +- .../resources/qml/controlsUit/WebSpinner.qml | 4 ++-- 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 interface/resources/images/unsupportedImage.png diff --git a/interface/resources/images/unsupportedImage.png b/interface/resources/images/unsupportedImage.png new file mode 100644 index 0000000000000000000000000000000000000000..87d238b67c9c66ec715d994269bda796b929ebab GIT binary patch literal 24855 zcmXt;cQhN07xrt^sG_YdyF;U@sJ$Yzlq#XEwraOEf`}cl67{83Gp)2%jH*?&HwiJS zHnB&7C^2KkPK@{b&ilUqKj%63x#x54{pX2%W@5m5PUzg3GiP`WpXj|fbLI@=%o!Fj zE{-#2&SaTziJm!gJzD=im4pxO?-JMC@j~Vk zuS!-%g7yuZ^Z|@jwG08u_!Xl5vPu;nQ{p83LMO|ezYI6c^2I66AIu=~>oi5xkqvOD^_V2;`~Pm!~0P#@=u zlG6@EeLKm&NA&8U5@7DwZKwSY1?|l^wMMlOkO`_m@?vd6gI327tpID{;T1I2(?`TH z2waj;c}c5_64Km#?eCESdapr}oEWjd*fBUcln_}{FbNRGZ&n|cMaLo;Q<6|S%C+e? z@>z|23r)Hwva;R5uO9gu$1lqJyB?%E)<9ldN}yhGg81aBHviKtNnE!u6*(-F{C_@J z|9x1aJHA-kmuX+A`iXb;t=D-=3gpshW3|JJI@I3hMG=rXWS~4 zVg=MGG655jh&SwgU{{D+NfoWtWV`Fp?Pk@^-05)Bi%ZcTx_3d-vX$1jja#QTyHYXZ z$-jE~B)kENpVOlSTo^X9B zl%;&VQ1VV3J<>!_)Icm~SH4JCpIA1Y$>#r;dL5{KzSDR|iWPh7ncP&v>yn!)@eADV z69u&}Ox=1#JFyi0J1f-|X-z@k#$_FodMQ@&t!Kn(fP5elxRib)X~4r>rE%%8H>BKS zdoh2D5}*md;5Xuq%y^oHGlo9v^bPwvT#DX*XnLk+Y5TCS&eF{`KIF4vok7iHOa*HX zwJ+OL&@16kN)Gv-8T(%B2Se-wuT@9UNlX~brjIY=wu!Lm6@rgqF?%J_i69ecLHE!U z1dcR;kA+lO0kD=<7a7_N;6y$>_-^+h!oBTl{qz^MeccDl_&{>!Oj6i`Wz_YHH{Km* zwF5j`TEEM0?KjZUpZCrmnVB%?@tyz#_j}DO%;skHMK@E{{!4O?hCCEfNJAF%|l==Yj(LqIqQVE5y{<6elJtaEeH0?nA#JRJ;v6w<|1|9A!$IjE&lrbuh zO<7Bsc;F>+vdCUEnlO5_LOC$;K_FfgfOn->!!EwvJ6cDa{y6kN9gijZboETB+K*Lg zbV4cuwx=kk#|upDgQ4bXzjwh~hu3=Lj_GpBjR(tept>|y9M)^3=9!T-kmXsYo;Yp@ z<>yyt1e8pcV%^YdO14O1HSEq-{b%Oa!o2N?>glZnUfjWE{4!%#F_>A%MgAUgzwJmL zy&#=T9ad+d8ou>0!uw>*f*a^9J?l4lf^Pj< zOMP^FIitO#f!23EEyG(C8DNTxMV)I-4OUCza$pTQRMFO!i-a}oslchp^~eV<62X9m zTEDoD?S!X6QN+3K!Q=iWDx8_A0%+J*Wd#&;x=o205^)O3Xg!s8%(sKafvb;*IG8A# zU4tz}W$l6$61~n>rkK#DjvnQc8B%6sehm@|lqiTaLa$JJZSU0>>IYmJ({`SHgwdp} z#Ia}xmR0v_bx`xQ&>4{OQN^9&`mkH_KU%9qy9xRA;upv7cr8bIOVs=C^$QAH&RR&8 zVI*ybhStO7Zg2J_S}HsUh)v%SYOKF5C!Z%#k`;u5LzZvyGY@q?HhnhP_m-XM?b=iGGfWM;@n#`uu zKbp_q?k041ou3>gfno#rZ=lCa){F6q>fN64s@iqdri|;&NpAJ07k@$K?SsB`T9M-y zxmld!yRmlYZQTs}dO?IMVhX>wq`Ofv#!|D+-NInkUgdHajOK|o)`~W|rs2Z_p3%t6 z!LF{L&u)KAMkfAacA@9DRWv!Z4IrN=`_$6j>HPP`%3F~ogIikC}b=}D6c9u2e(nM>ogP* z|58mXcd@|_rt%0fK2hKdeI7>$T&QzL{YKusc%uybmXUh!?7VlR+k45EM7^si2g-UU z-34;k#6&q^f)(m87~YEtI4n#G3)x>NuB)$ilg+SGF*oBaZ%TeR{$d$@{g$Su{rR2s{oH;M$O5@Z#8b<8+O#u z{Lg=uJBRZNcKQ<6=uQ?4b#r=g=^cg$2I(;mJys^W5T$NOIm3X5s*P?UF0DdN~>6tAE>LXy>E2b z#)}DmH*6Fd_e~)Z!OFZj*`Te`H!<6cn37jm=zRB-4!oa-7FWbBmZhi3$g!nne$>Aq zAe7QsBC=by2e0WZn~@z`(M8X-8PhM-lxZkWB;WFU!oE;3KR6YBb#J1Js`Brv4}KAp z)}Fma?>idj)*7Y|@b`4f1&~oX*FwuSa*|qY*g~6bmx0|KiyDcFfxg?e15yJU%JrsW z2l7VyFe`dI1_)Wndz}vXwQK$TW>K_Q>|+MMDK=x za0&Rm05Ir1T~~7?M{{h6zeYoX3{D~oS_o^{{D4sF{`z|rzlq%}nHyb#hj{i2RZ5E~j;PXfTlo^aU9(Hfr zN|A9)Y=`p0zww*wM0k6;E-KxAFj_vfxFi7sC^o2kZQj=V;tMyCdRh++2||#8k%GtH zSTzsnn-{Oswn($?yXV{hidsM2Z?XN&VKc2t%}5(fw8|AcereDZ$y6SVaaRR9yiIEw z{n(XsKvKKYk@()kPk+A++b`^)KUm#wb@>}kesjlFJST*tDC}Lc zC_jD$kc1lq2gVWXf298yA2&iga+po&_!O%Va+O*xd;IOj*b-BAYGZporRsla;P(ir zUD7eFb0E$Z!j{wW$#=+NZ|~GfK`xV|GrhP3sCfVjU|q_ZGH9{0v->XUTHc`R)o9vR zZL#|6&+VGSd5h*Dzz$IUZiYhpjLlCE%TN5urO|z|9?zuMfH|#!hxIP$HurUXyRl(z zkyDS}-{Q-5oBc!1t?1;#X(M~h!_ef_Of~tIkW^oSfA@WSpkR~*2%XbQUFFYUUE9T0 zEI5@}zudc{=nbF6j(73EXm1W;8+Q^@LUv0|cb!mWlYy?71}v@SV^Jaeb4JJsW&wd( zM5GiHJoX8w1MVf)N0L94i&{@AhwPFmVvcz!vIFRVvwM5)0UzMCpTs6bvUgX%8KU6G zF>BlVoO-eLR$~72im7bLbOl{Cdi39)hYNYc6jP8N&rhMB4UyHcCF?46S5C!GXJFfl z+5{I{U5HD`2u*_ezb#rMg$a6yxU=KJf1iK1YI$vV@3-)FJ)?PL?v`u^MKj#zV zwE4La#MzL3?6I+@`FxlqDZU}D1HYRml=V2sV5;?OeE-SCNR7iHHq$j51UeP_*fPX; z{0_rj!z&6_p_ks}$!^WyH(G7mGEfG43tWde4}RP{tdN~7s*3oILpw4vwURC&Z+K6} zXjXZ5#IFrA9#Bo~Sg|{gd=1?&n#;O=;+XZCaj_vz@^sSDed6)2&CDhj{g9;FNHZC7 z#j8s=x#XPW&3wgw1zgme={Ab3RsLqs6`d}I; zsTQDc!4jNT+{2vQXYOhFtW07(cWri%X){|S`FSb6S^L?6JS=B1`V=&nI-uwUcDQ-0 zu)JnZ&jx#{u?i=y|8d>$?jxFPr0m(qNp|HwZW3%Tv0nKWo_;=@Zc=25UMBX32dr;z z(>0)o$Ao$X;$Pg@N{cnirqkrhb!}E%&aD$GRMJ&rYk|5?^pAA$HvE3p_smyz`xTu> zP45A6Q@8SEkU*D3NH=0QLTI3k$&o=crLkAXY`7dKjq)hUP6-MFiX+d5S923Q>{rT; z)?^C}Nycs>3SYY)e$Fll7)~5pGKC(ZTuYiREt*N$H?T6@MtPgvlTF0Io4Cew_Us_< zQq)t=!o{Vd*P4ymA;#(@7|qx^;|)x{OM5-55!Mu@{KZRzCnU;f|LBELTYmcAf62oE zmwpLo!+e0vCts6A9u`2yLvjB>g4_I&FmCWt-reySTJ}Wdel< zD1CXaz?0c2;JW;O#WfibI+Fc|*SS%j*{b*|zMG8H+pl0RjauMM7o6bY^rQuYF=SAK z^TLSkbH>0}1bAmaRI~qw>YTCjTR^$5^X#LP%Z>HG^&ko-uJL)1hkj#St^2}Qx7@a* zEo0CpWA$&vHD3HMRsV{vno4|PUvEI+f|24b9C6N_8oTKa>ybF!nX6cU${oGvT5&Sn zgNTNhe-iexL!vAx2B!;o6}kUP?j10KPu#^5-b>?Dubu7~MA+EA>YcV4p{_xjFlQ71 z+D7}@j4*B2;^H{`yOFW!%*qgk7$ zG&;64-lIosEe_}^i|*z>S!MR_6ZRYAt%d$_NM=|7pZ|(Q9nN2a9;teC?E#_hqyK`p zIU7%drp7$WIvt}t!L`S)EadE6lijHz72%8Ofps8l%~(Otx_uH}(M~ie|K#AJ=W?7R zz&-Fz?*!f?*wN7Ml2O1inj4xiln9n&p)R!_9y(f7_1OBlkgV&i2flRrf9P)4U+GQm zY}ihZGd{Q+8u9sqBF=DN%!zL7;DRhAw&@NHnvztcK1MKfLu4ip&V^@EoZv5>#i`)i zq1FZ4e%1-8^EBuVHK#8;4(n#P#K*$Q!;oV{F$& z{pm$KIs45Z(wjhri}3h&4)@Rug~8B08zFC`3RTh=yYwwbv(u=Pnj_nQHor_dt<~55 zj`gbb^&_D6jO`?YQ@LVx&$-D??e;XMUPxyX)o{FqBE6J+H|w&0y@6y3bF> zGbXi;EQi6}p0*xwU@NNnCq(Xo-w201mFpfxVpsOBu|Rw|aziSeSS8rSg_hti?j}C) zB6S4Lw$pdau!367CwHb)Mp#_iuiv#B%aJtJ?8ifW@9oAAa<7;C0W>VdZi+NitB+{M zaH(Y_dcEXz=Y;Yd1b(r)CZtSL4!0XBME_MZ+`aRz`mB~ChEvw`7jPH$7zxKgeYyHZ zTa*Cc+BRCkx*-x6jMERP9~U1gXVZE&%eM)-TO>BqKYzzqB%>nz;3g0$7WJ4!k+9l# z?63u!YWWWv)akd6OKBODXo5!C(M@jVxzD!m?MeUg_))N+a9ns<3}|JKe*)9`v z*#22CR_HI}T=pYrH4n&LpeN?V3yz|KCwKSExxncvP-o$@Db%Wd*^5 zy40OlqK@UrdFqJ9%DJybf?mtR_>%n5$ilDS3u&n%s-2+AXy^`}jIdh``L&nl#}}=` z=gN6EA->qrWmvG);+p9%<43TGw(CH0YrhpejqGUu8Fp(TpCWw>II| z+X<{RK1GRp-5a_*ntzNDQ~a^he^zW#Re6o}X|8`h9wED1x8f)-8VL(lmfbHrplbtl%1**H25jD{hx?t zPkr#TQpmg1FvHRq>*g`nQ+yB0`3y&*KD|R2#RpW69R}HF8Hw^!03$&F$`r1#drxM@r!B z{San|4bS7WaX`g|f1qS{Vy!&^4I1TBG*>l?FBTmMD)fx}bL9mBb9@c6VQ?(j@uodzReKPwXejcvSAn(4Z;2BIk$@x8SYSXS;?TXs*&O z~k|s?%4zC2#Mi2WuiRv1Ukl;Nmwi{_k@qxbg@(RxzxHL+6?DTO88=o zt)!L_J@e6f?68iL2W@G%qR!q*%>iK?hx_`EIDEE zdojXd+H|lvldb)JcMdb@s-%_ustGaAPaySQP6pvZj^@MU$3LPfMKm^;*tn!gHP9 z73(h4)X_}dy;hAh4EB0A_4nJB!-p8|^iG+gYAvcF*Td@N2O zYq77SZ@NY49ykfOX%;Znum}CUZ}7H95ZCw-ey?-QSk^d_A&ak{2tNG$dm)JR0?OBd z5S4FS9W!SKy;W2~-|(zIRh-OT-li;!C$(d0Ch9Y4Ck6WC<8tyU=O%@q{vt`^@ng#Bm4Cbp~mDsjD_B)^y-nHp<+;9~HBww8HE zBi-kPnp0befHrM1#-}Y;M&SBnc%p|WR}nA}|IW|OCJXBf@Uf$UhrtRfQqYD^$|2XV zRk1cS_$5d=cCw|?f;!3tE$JvcJ#RMsd0RgXXPrfH_fNE_gL&cvYh)hHZ7F)G-cgsy zb))pw1%3jx8eJL-f89tB8x9n!VV%5=*&msHs+V zvw}5u*(|^0)98DlBok2=xeU1!$TjHl_xGA}9%PTj@AC-jkbRjsWy|efB?0k~u`d90 zfLM9^`UCq9wvBbJePpe<>uxi;r3d5g#osk-B7*kPX%!#q8KlZ-$r96*o0!_8%IEBU z2c|ZUizLHUsfqQVW9rw|gaVpl>ii$Pi_<*GS}@blWDxU9LPk+T%}Uqs_LkHz*xRK- zP%?C$d!~2oXAng+z-^W8W7_fjMYW0d!C?4fsijf=(5hayk{MORlxr3dWXk_0mdh*` zRenYe(_mW+@gA%Hxj0v)OK5PS`^19urPy-7eF|T0b*6!{$zt41;*ZlSheJKQC2TDG z2ZwA)-}%9UUMrh9nEO>=3{Ud6A&zI_|DbF@ShO@2^)Bqa@MdL}81T|)_D4H%Y5KFn z>PXfn@8j!a`@JLi#4Wq_mYrrtRVv~pi_7!(ztkoQOTg@AXS*+?F$LzG>}d}ojpZkr za}4EJ*FrkkHpEJ{{6r$L;!eKWhMbA%ZRo%1p66|&!lE9!q_J>oaf+*H9Cc#Q>3csi zJ-kY#3t)%+$;&_3q|~>Y+c};Ice&_NJ|Y<%U7Ulqq~WVLxMtU&hwpr}u)x8DHg$&O z-=TpPvp{QYCK|VN{tCo1(_40;2+fi<(O#-t&AYDrrK5*5ZP1~Ty}$K>4+wGbde;>{ zJo&kxd*zmh{fHzc=(D*nuyS3Y2M@I11WLj{dLgooBhGA;7rSY_*zTg zg9afRNebO)GUpfcq{?R5uzue<883WlPHC+M-Sa?h=wK8CIkETDsphJwt}B^Gt37ael~>{r$uEZQ@4EK3 z{HyV5YLy}Vrmg(wG+cY(qo7Be+R|VN?JvWak4Syq*UXu%_DrzSVrF~s#wY&b^e~Yp z!UtLR{eEnlUnA+hKK&@g3C31N@IBL7xlGe>HjFS86{i|y*|wjhn28HA*=&S$m}hM? zJ6f`I6itk}=%>06*Bx&uVpJT}&tiSB{?o*s)ST=TGRo8VdfM0j-v#(wLVT}XZ%JX_ zz)s|_)UkG!p}nr1P?zN&PG-qMsIRM#qJX;pl^* z{r8u$jp<+H4J(U4@OW-#Lz~Uv-jkNBV$<>dxC?~6uX2dL1+O^DNllz^fp$)Z(zAM> zv{#lhkW1M7d6)+GFNn8f&jip5V=DyrA_E_fBV_n`8$lU%jq1>Pn(cO4L*^K_hs_%Y zS2hdceBfa7eCW2*WEpY5XSwgKx97Z0w#*7&YYHbNd}&Vo$=A#WpPMt;iV6yjUIb25 z1D#hwv-r{4t8d(RaJ6)uctu#sn{hx{|3Ec-XGaD`k_EbzUn(#9#+f%YEc4%5NTqJG z{>nomamy`)4xpwpwbHVg3RVlp4%73a$Na?c>&Ap`W&ezl#O;xo(uB&bBwj72H7aC^ zwwsi9FcH3MszT_qFq3|7UoY7H(0)_BzZ~f)@SLlcJ$|7u*PYMFVa94ElYirw>$ z(NcZ{ie7qKJF~WYSyjWuz%F|X?k0G9?aP;bsl5mdUxyfeHJl(1b;n4!IxysfP*c5_PK7+Si=daFy*4mjJB{#FSz{GwsFDukFQX z#Yz7XINxt^yxXEsT3DOTPqi5p-bbR3In{rJO@`X>8*}=TM))oeG|rE7uJ$@Cetw}V7A|5?FVK}u4w1fKnsKj!0+?jTicR0GV{X~L@b2Q%5X^QivnqKu9S$K~mhZm91iRfv0#)lhk{NsT(S zj5vU?8kfz@oWIB(=}i#YWzBcq=^9AFdRTMGV~D*`81X?NwOg9O=j zzh6nvpVh`#UN200-lmh*It=#C|GQHh;~OAo7QbXf-eSfGw@c9vSJK8uy1bE_XM6NFJ zc(hw(gq=Bh7slF4SQhYwi#P_wJ6{gV=Mq&=C;wr>N^+ruCqUkxzNJkIX39vugScgOx=$Inz(u00QTaJn z$6rexN?B*2UtM?RyTy?N=fpk}Bz=Fa%W*F(C7wR`H6reO$nd}UM0sb*e}y3(zdgL0 zFiu!fHlOXaV~h#&==lyWv%fMYZf|Be(QoG?jy@AmUQX<=q)HdqtSt*4X;@U1aq}%F zbv%O5!Xt-1Nk)Lz(pZV?l99(lCC0%R>(4Q&b5EQgZa0HHY4-)4!}}mN6=kU^^`Qe|qjdv_F%6vh z^6{u5@-|5r7H}zytW!w~L%D8F)Q|Wl`X9X{D1a@I1Hh-ud zU3Ve}7QmjDTjb}tqfxTx!5IxOtk-W`V=n0v^kY)>#^)pDL# z3#f111nrb>v5&FzEuCWnJ8Y&l%s({Ct$*M{y)>O8xF`7JJSe*JCCPmmVAv%k_f7Jf ztZ|?r^$E!TJSqy98A$*7wR-r?Nrp;%`tgANVAEm2ZoZMJAWS!F36inaV)wC{mgP4x zkb42!r4uBKG{MoElmmkddgL|qJliON7@+AYxc9LKl16j_7z`+WF-1OLM zzCxR|98u0JrFW#Kma}j&JzQ7CH4r9wID_0%>0FhzBmYbXImT2^n4o4|N=h0cvXduU zy z+PFYLjlf2BhdGJ3@@hw|cNxKS(<>#CV2o9W5nZ2dB>vx&bm{JSQ1%yJcw2B>+_61h z9OqQCi%laqyiEeR?@|YhvA8t65uy!Vg8}oW@xC~o9d>m$%_zj%C zGkmtDqXxuortSDb97vNwaHXSuX6)V!QK`acOnL;O0uF4WkeJ+{Dm;)#B?@XI!%zD7 z6s1b}L3_%KOio4q%O~Ql?S!$tY>$emJV}^&#y0_Xs3OuR)ek0&^Cyj{RAx&{8{?5` zxQ^Udw~DqQ{V6}XYyDncao!QY?ggK<1@o=ee05b0pXr&ldKJlc$Bx6^y;FS)fP-g) zx&|dyxWqU=vSI06yT-%n?7|abpJEnzOGFDM_MfnWFOBWRSe3S31iFD525Bfs18KSF zJSAPYR((sj8C1S=lR0oL>t+zLT;nzEE*2&wjSifpGvC6r9bJXtT8>a_i`D47o(ndD zaFxTn)uevCVgV~gZgu~1!g}^*uCTjs00hFn#}=*2ViJffU18+@7zXm+-)a3$%i`@E z)rdH#GUS|ow!B?|&P9^axm_Jixi%lI4%(v=NGXPaiHtF)={IiGTSv=x&X~e$g=GQ@Y)txjWONC9{B($ zk5wIw;uW(LF^kdL`0IgB1o=fCik`#m5Sr!+j_i-8=WpB^l2p~Bj5LCg1>^!6ypef8 zBEGp}OCEn&vSvncf_~;lejbA$p7>!J^DzWTP_`Y-3HbzajnQwjZ#-Lx*-0JvuD<_K zFdhj%OKA13n{FTK7)MN1kr6lMd7BA3_K?Id6$dkoYSoG5ZSw$MX7JG7g_q(dUkJXA zMf?+mhxirViGpQP$(m)!e9<2YAnSg=UlVgDo7)V*)t*Zy<;fPgOD}gpg9`EM6}4Ll zhV{yIbL}EHsIOh9=+pJ=FwDW#bsQ0L?X^J|hiq6~^cE-pHo}nNS+|pcG!WxdsN5qH z95j`RIGHt%@*&BrG>|RnX~_30xAM}41zXJI)^0Tx$!$W@+$3R==?XLS$~%SgpH`}X zyV<<|CR7gO5mP?;c9mpmhKNL6m^0vnfTtAvdRf?HpzdVhU2JS>D+ix^{q6sQwDv{ShfGDe|+Ma%CaLmX_47gAq(- zNX+;ks5s~jX`HYNj$4C5%@4sWl#x%~E(M_4(T^fMtz}<1--rl$(jJl7T@5TVV(LKw zJ45=j`I2$RmXZNTSjV-Wjh)1un_jyDfJUMdeZ(l`I5g4M5sIxke*_YocNOzvI#;V+ z6-gN|v`pO8*@sbv-Mq(q6uWJQxnp@arxJ zSxQ?1LysJz4`UMiS4R9ZQc=wUCA+`&mvgOGB-0gUH<_ywDDe_>b+jF`V;HuCMrmL5 z9WDN+5`t5mqc4zDaw4b#o9DepGno;lT`V-VJKBzZ%Mo-bX>bzE&?sPwdPg=yz&KzN zM<|f>7W^~ZjgrtJ{|9<$+E^ajI#L322%1@rE>|9Y4YRZAvZ9>*efy>4@mIf${wwsu z39t_m{t9(288O9wjMdEYkvx8BB&34C0-m?YU*@N$j&ujYjrC2w%@?x zwk!0pkUe2ubdy|$jf%NP$vNu^dAwW7-w~nZw60a}x<5UdF_8}*zl~~oe(^fgsvu-; z3{_M&xjEE{*O-u*LL=WTTqX>cL-Qo-q*ynM*xifyPJcu9%B*lqjB#fVZ7qHg+J)S|vHI*6dmocY%my#=)DlJ$qTeH;y%}&#c{!tgNLzh9X-TK-C-#Oin z0BD-A=W@k}hq_;TQ#@eExUb&5s34ZCEGArR>KN5@IaHQ4uiAH`c;NVj(V`R@Mvs;r znX6$4ArB`vrpzA>{jXwv@a&eSx6!^lAsS?p6>|3vVr5bG;&n#Z9~<8dBd1E}&hi!s zDf+W>oq3goVC?NW|4XilI(Rayp>nABzts0w4I1`Wc-2E3yd3>na=DBzs^xI?Y(Gv0 zEBcC?S|VamRP_Dy7jFS}xm+T`EY|uDXSy+CBBtfeZsMq7x1gYH5JH$#Ol|EmO)g6G*%J~K z^w&A3$k2t|*{zLmcSS}uxnG?5m~3`$6)oMJL$^RXzbg+awiBgE;qZ$y zy|t#=s8Jw(=~SkkOn&S$Z7W+K0ltewsrR|oF<%(j`j9_I8c)%-sPM6sDfE4@(_+ns z8h=%zSNH8(%%jD%owheBweB!9Z!UA$1<6A}hqc-R*j%(McsyC?>@q4p$ApS3GOaiy zTqN6}Mq^H)YRsUrVYNGUd~1IqvJt~rFZhqiy7FApT`5*Gb}?1fc_)#v#p!QG2=4@< zBT%6;s}KCi^_HjQ#p{klHJoon zl2}37Xl7b{@~zUSCTEI)U2`Mkri|jGl|AVveR=-V_cm-r~XRYd$ zj-*>ula&4syuNl@-8yrr{d%%5rGV>x%5r)S)3>QhnZcxPQfV%FZv)>gD8>CLwiVe$ z?$`(?Q~$@6|0LJ9l6*1Re|E0wqkY-h|D*3%?`^sqVW;7;eFYLoAO!~DkM0cx`^h&v zurCFuJ)UX3RqC~zPFS$=9z1rHR-N;I=R_h;br^d_HMu-~o303-txfK1h(uW0q|_(h zdhqy1T+>}U*MCCCQhf`n*$Vn~^~cWV6?@4OiM>CMZ}voary6oFe>wofHmG%tx>XymcP|9^ zjMS@ljo$2n<0-XAA9Z_tHOy5#QZi1v8i$WoD>6L%P%L>NbdU5Y0u?`GaK&lX)HYq2 za>Ub~_66-doBpjk-D^^7zm1f+_O0YsT&3`R$Y}e$U6t31y%rX^Vk^Ye)$%%E$Uv~~ zp`q?i5wnHu5kAH3g`bnU$r(0Es)(_vlQWBu3dBkcf^DMgub|rqZn3wp^6bXp>VeUv zBZ_)w%>grM&M6v-?vthQV1OQke-_gp$Q^h*G!9u`J(;$!GQjp9+xaAof2J*Ep8w4? zZ{57Ot#&hh7ja2B{+h6akiu-h;N5Dg0Cb2;MWt5XarA^>UYu9_@wmO;(Ddo&2ik&_ z!9hhHLy^d!4!>%H$wvQ0!J{%xnB6Hfz=JWo(_W07N$y$37(yQ{#54}4!LvKSeT$zG&fJ>XZ|~08GPlBL95RYe`nwtmCU>LQ*cB**T$c*{Sl{{ z@0UeAzwE%B1WXT^q_YWZ<{3q>ZNdz)ApjoJsGb!BT)9)-S(`1l(Q%u$n##><6L9?0 zvWswAwe(VdU-6e5W<^C}FBvb2a(6Y)x>?g*+bQs8ceWCBRvG7gnRNVu=e1hWp<%Zn zCsAQfx)e~&X_&+*K^7ONSu<;(x!Y-R^x;OeSagU#QzHw5ZK1kzS-d;Y6ow1)dTI{y zjbWdfx^IkHo2*krBO}mQWZi22S5daeg~=FU=!8i`1YZ*8pD^yr+A7cNC)7vLV8-rX znuUfD;7;vh4fry^E&aj|LxZcYdh}iy)Met!OQNnDhcwD2VJ?ToO}-_+^4NSTk0YHRQUUb@3>!CD*5xkrit> zZBi}-CdenQ0}3CIm;b0Yi8&lHMI3yghh`1>ME31B{SYrS4?GIKS#KiO)88F9yJRm- z{;tij<1~4<+Npc&5itFc+gLl|S3_Q4>@*TwwW0imljmV4i>_9{aC@*~u?Zf6Vb6jQ#Lzr|-4IPJvQ61=MGEfOQ|XCysAT+xZ=zf)4a6u{g)VcWN*}1L-$FbdbX#qCcT*GDK_s^dcIMC@Dnu6T_|T z{!?2yTjf*I*s1=uG=+iSQyy4OBlSIKJl;p)50AD-`W~fa1>JI-i)7qbcfAey%p89Q zfKZIRmko_F0&?i>W7^K+O0)gm>OmvHr)%O$l%QiguO_}0|CG1PZm6)EEhT#+SU%_E z=Tl}%0e$H=hFf~Nw^SaFXEzv|P4f{TQFpH|T<>r&&<-H65!?yRYR^T?P=iM<+sAT* zpqR*okr6buaWC=oP_j<3XXE%}Lz%*y+fcq%w#?Oxt4&oMGsk=M53aEWb<~UjHS9xz zU{<$=)}O^ELTONr#jl!w9(WMb6D3ZaPrQ<^XuBq0Z{oV6@bxo9^FEN2;_~)_qv3gf z3**A=Lq_7mLP<)iw?%JNK-#bIzU(R)Um7(LB8+^NQqK6Y)B4qnLnK`?DcfUiz+e`i z?|`4>R$V4+0A&lb8B325hXE**Ey09O$uxVw+$*z^?j-Us>&*5amTiphCgW|~u}Fi7 zgXSv{(F6YE`Zlo(nxEZ5mV+!fYUJ4>Ah%poSz-zwi)N11j)q>&G>n4@TthK#@@bKu zYW5#r)+XC?7D1(7vn^SEDdY8mN!`}#6F@8d2vp|R$mu7S)IT+&1ZMCLe(>-> z(_5rOlWDpWZ8 zSD+`TEx)50iOOpjg^<6?=_S%vgX>(JtdQ{^S{rI>S5+ds+Wc1ew?@<A;4|pt&O-(HYet1p{ciMEKQ@KO0zjzu&9b+L^) zt_mI>IUQSK_dY*-!~rv`ANK(ZQe*6&KhoxKH-N%i&5@bjIca{I(b%5xqG;}qqDMc9 zwY$jr6!}-Sg2%$H?J92k>TV9AjZ*0s+}MPGv?bs2zV)JgssGIZcyR1zVLL=+p?R;f z>^P-{o#7&y4$iYk**88g)3g`^2QSS4P%^6eQ^!NG0xGlKJ`^_v|D{$ILt}JG4UMOYU*; zI+APlYEbW$hUzO@tl!aB;t9)%aMEss%+8vWu7GV)Mc}MpSUKyH|2XxRLOGra1U?v8 z+#(^5_bmIy`=~8DQ{kqhtpmDx4)V$+Hv1P0@x^X1(ASs?DP5^TNPkc+x;pZ=S}}5j zMdS9*#mI-kD=*~yvHpxA0(}BCyKXO(Wm#gO2D3N8&ThtPj@edn zQ2?F7OP{D;BJyl7YHL@f?j^^2U`h1Wu=BXf&Z3PgcJKV-m-O3LS|iecEIB~6V9;!} zp$#yz{BdS{XvAuSw_i$4Gp+fNg1mGYa--;^$03ZNKL_t(WH*uZGg*r}B-&bPWhOir8ZNfXy zb$|lX2qK|FmI+?bw*hEIUeJjpwBN?(_5J`g-*h-@c^p zP6#A9<&eAX#&zshV6mG?EDj{$GwH1>qff^|zIE0#%WHH8tAmjwss|$j4>u)V?y<7Z=*5{dRe;J)yejrLOwONmr*xwwD{o z;qy{^8B^MnhECc_TzgLuny{t+F|+eRjKx_z6TYLlY*%<6SRb%`ba#<8p@DTGo8a21 z>(UQ}*X6a!c~f4P@tU$7jP%t@xsoPo~(bP#TelRP0h;(flu#ZN6x&r_ zdFsmJH~1Q@r)_L$4|)5oZS}3&mU`KSGD2kJryX@|mp1L&kgoKn3Z21AoZ_aNiEO_; zA!M?HAN2zc9taz`uj_Q}_d!ko`PV0N2yHo|H;pgMI4E-h@n*;hGNi zs~e2^s{DrlGubndv+pLp5Z+R+IY0Wh(QRn#>vZgz#NhSJUeEH#jQwU@vFWvWndny>+3BQ*BFoz&>34rw&c_Oys)H^+}-2=qY3ng}d@n?mlr7 z+vF$lDZ*@<9Y@A2Ik?saeGEL3ijuRBdJb&sLX*I{K1;V=>{d(ds1HrtHfor5 z%Y|ed;h}UQte>)$+P9LKG?v> zivRS7SIrpPQoXIV$1=(5gwMJ%*`;n5SPwulx8dk;zrUiXB%>9+ROeD?%`-Sj1O?V~;$81j0)`?sve_y>@ zy8FUR2HS%Mtv)awIOXRvu7P*otWLfAfZUw%TW9IJ0~MOMFy-v4eKd)wGsrX9l@+Q} z?2(vvP?q;U3U;?X8x~(GzP5z=hOafP=zABuB;)E{3$p3Ey4caDY`<%#dP7gTvhEy< zjoNCRwgVCA5)o-_x2H&e&* zx^7WD6G6Uo>ZOf%aRy$}z&p7%iImYl?J7&%Hk4IH|B)S%S6{yVQ`fGz_G`QFrK#h| zk-9YG_}FIB^;y34rwGSfy4T5@@v4)(p3#wg_f(x0-b36s+E}_f`_c^tsDm*FCp;5e z8S6uXQMv;d8_=u+wP6T{XzBuOUmYNNU3=BZL%F&w>FQ-2d)mzQ(*LgASmL8Y@dVM? ztX3`9!7hb3Q}ja{OAnsh&?|k-|5&<@RZQkg;!OUe*KHQvn)trgm+z=Ad*Og)pfNx# zB!+{|0nXrrZe7_L$}cwKW|Q`m&pNUc{Q-Ki(H?vgR(YX%`ez&P`QW=yS=$4`qrIB< zPSQqyI=xtpps!tQ)}^!)mpA&G?RpaE)35DK^;HB&>P%+qCOePS#8+BN40?B3-S^8a zp<3HLiMo5e;b;0fG{6{?$|^&>4BoigZReD=4pc8`;Mu;o?I~N@vAxw{>Dt$h`sx9- z<;hGLeW#vDhYmd3)RswT9X(~Vt*#KhctPTXroBALcyiH4btX6*fmu(ID{b=kJyuJZ zF44W~&H?bV916?-r3vL>?4dV@v;&(UOLu()7C&QInY!@lSAj9}b@W)CB4ktbPKOQzmKJLk~Jdd-g}aJxL7t zsr?DAB9L#IF5=s2&g5k|UOiJ^X|H2zGde5}q>uyWN171%)qy=bnMZ5uU0nHy-wZSd zpK{Rb2Q>H;XbwztDZ<2|sc#!axcyHZWWPdvY*oH7|gX51y9ij9I?iPWe z#3sS}zSyPJGNT{!OD!D8EMtEkJ#Fubd)J-sku&%x4mkbI0Sw=PC*QW9*EHp!tLGpW zs&D(&wU>h*JAMj3ZW{K&gKctPD774OR1 zM%u4^&352XYMSlqn=(n4mTi`e9~$0I@DgeHt^!~2U2YPwq~FY&vT;Odyu@SCAoC$@ z7-8R!fqwne<$BuMl=SfCBsT}x$7^fKk=*s*@PMcM82I(=a{F4ou6o9Yg9?kh5xU{^=R+A^~7I9Hh%Nis2BuRb^Rau~N;K}zFEvO7!}d@aP6^Z9Bv zO5Wwg>I*E$P}cetFN^f~XFq}HQKY3EbxYfE%?>@~w2^jFSKCR;nN_|}xjyI#EZa{# zedUB?N&nQ_HOaMccDH&yF72tn%lYybbRV-nb&_WSCtmwFG^>}a@I7}1NPZXuev%F@ zy>95(I`61spHjmh_*oZeWj1Vkf(&%@%Ak*gultHjCYXBIz-E?r>amK* zHnB&$`YlhmiJooPH(~mUE$a0LI_;x_54M>wV^RdB{~j#@&ggv(a1y9Xk%!2QI0^?k zKRFX z1VSENi|q<)x^5(~p{#d~geLS=58(Yo=@bb^zDOf}Xm-xPXLi)NM{Py3u+#=(Tw!JFT1W@@tQv&y!dNR}dT| z#aK0~$*Xlj{bWKOx8^dbZt*xQqIJK zmTiLDZq3tn+5zvgpKXKJx~0vYIP}@Fjf{mS7kT<*yVh%d(kJ}>BT$mH=SCYRfNkaI z8GUFlZEJWJJ_9fZn{7c8XV7P&LGRO{y|kx3bmg_1b@|$rSM0TI)pnIBC#t*t8)fKQ z^rfCzZBv;3bvmAK+c)d92~7FAKIn66&px1MTj1pc(Ow2kOLrpjuq9_~zLr{lW38U` z)4PqQ0}isER04;d1ES`SQKyY;%l3eNNOp>K<&y@_qTeYmujo{d#|kY@F8ZWR>NO1= zlBq7p*>0_8@)eoz!FI5fGqH5-l{Vw@%C_oul!H#uj`c!}|KtOSHFE6UGcDaCNRNP$ z?H(p^J92b2)n(BMeDV{qD{4PaetTYs!0*C(%j@>0ik9L&fkA3bGix;D$K zT%?d#e$ozd$`rhfs=lEC9Urs_%_2Q*%Trf*^rdwth*;vM{j&PdPzAApJ_Q{LN!~|?#-|Lyb|9ymP4&@g|CNER z%>rAy+LCX*_!&0%GdV2yf5pB1Yg`PayP!0&+0$+cio2<27#vyf) z^W?Ls@9IqO;UZ8cGfC_+K2HQCx!YO4%;@+EA{u)y|sM;U{hzsh_s9J#lS#($NNb#dfv2NZZCq9eE_Hc5NRX zZQ4$;-L`>j`cstSKaSCKGcE%1fT+n#^47Q~0inrWVEX957IN_YNE1p={V{N_|FBO1 zIv{Pc_z(jgx_+SpE(Difh{3lGRHndK_Da`YLX*MvYFX-iWvC71vDev-eo7}fvtPDr zLfa-XDJNZdcwIXYGyM>v=j;#8F`9118O?`>ndG+4U3e2;2+w3+-MVWByNW;tf+Y`n z@}$qsE}O;ORU=dnm^5$-vRTg0FPqu<(w#ChwX=}8gV?$Ql=jPTuC9^OcH+v{H0wF| z?HfFC_=&66XV>@%l`Zz-@u4zB9ggVId3#rU(}t<;-CloFP5Mx1OkNWj=jS&`pf3?#rHeW+r@GwBdM*Uh5c=_u{Y&M!MU_)tOri>n_tpMCK~^Jca@p$^IJL(iGqBv-$# zO9!I2mgqwRrwGUFPTc_n{(APR`8$95UpCF6-9Bd)FKI)|Itp2WPoh&C)Z%^6_JJ1k z>Jx!{czh_(Ojc+qBR_4zSGUN=Y>b>XfP5^qUFmD`v0t1gD`#Kj`|RkG5Lx?_din$J z>iSLd)fZnWJHw2!X@m`(0Q{{#`OW4(`JMl;`P${r9PLdxK8w{c?+oUv5sUQ`t1=u9 zR!slkNB?c}NAurp?q=&NSLJqvFRov7H`~x*iJr4}CjXF4P6De@*(N}7v<&@rN!$+J zr03vTGfqO+NnO|16j;~CNrAx{8%-ceTYbd=4D^GXxO&#p7Bpu#`vT3Pj-MyT zzHO;5Uw!GeMI9dX&X4v|1-i7nE>>P!)=6O7S5N!O0zHWo-idABlztwIm$=+4y4{G% z?nA`7rO7XG@Wjt9MlGJuxF!U*uCH}n`q+?!HOWcvU6Q=?(}(D%vhl>MSuX7jc5yNi zFoRrXy$E_8?6hIPr6FsANK*!AJ3?qAQ5N-qX+s@pLg?T?iuIZXPnvDgM$)xW?8NFq zy&~NHr>wB(%-^QSlfj>zFZX|9vS6z*;sH)!7b5Y(kHjvraUyqY85rYF9h*bN7-P@2 zhGh4plhl-4q9@*G4;_EOrFvFqt6FEx}cJDLBB(O95rgl6b z0MA{vAL5`v+|<~0$*9{4g~AUsw*8xDK^^wtpoey^+P!aNy;OWy6PldpHk1hO4EiAI>l$> zZS~0kX7r4O9W)Na&SO^7s5>5%GuH5wS6`m8=u(8XeP7Td-elhyz4&DZsMsN)odQY| z%G=b?A-r;9q?$d~&aCaph4u|{=}4<14L|i!Tsi0zWlJUM$Xl{)X+UkHZ1R_u%xXYA03Ix0$EyaqiID!+H0`8}cqSrx z9=Oy=JP$Z@KDL**_RGnoJ^RDA*p7AWrVKplo_z3it*y!~vV4^3Y(L@0Yh52>vM>h5 zk2)~~O0%wP9si6WxZ^~6f$;=HKgNPQRx7^lczs^C>l3za%ja6)A?rL|qcG?l%pMHr z&`OZo|DjR0Ae#p&b(N{1GU}B?Z0$=+Up&CrNnPstR^!TciP;Va<+0;HQx+PZ>>oTz zcjAldLgb`ttG3^&H@8pp@tXa!|J1vf$4Rx074`bK6Z@ji5$L%bB-R%6DIT->hO%})p9E`~6Z*AZDQ-|4qaN@)< zqj>DVp?gfMJ4T9d`(N5K=0H9caNCe}-;gEww&HdA90wgN={N?YgKy$(Yit)jOLvPt z385!W9pB19xm)q{QNPvkKzSDKlPfmcJFvxeoB;al>8pDDhMC27)hjZUJ!RmDr(g6< zzqOlo)@+AtNqF{<3z~Yjkic3P*!rjpbxJvi zarRZN*ln+CJ>cqSLzq6KzIyPL6_%uncBN~}dbZj1xn16C7g^-84vo?cu)sa3wIXZa zDKFhKmUOzrEUum91Nj)czIZ89A1~^DXe05pK#y4Io4rZmKDy1q>;5jzmp3JF_Ed@i zHj%00ACO`)B)vXR@{sQiW|fRZ=5A5!DPs(5Pnh<7i#kq-)3HE3D~#Z76dLbY{ke z$qjTHsUh3wl@kKb(Q!HxzUyc=Y*#aQ%^Paq7&-x_I8bS`1h%~?Vp2;7QWA=%9g_fC z@Rg~Pt52si(E6kve&}P-Y5h)pWNmLvww+D1D^%7xzG*{!>z+u`|Jo<)AkXKim)Y!D zcRcDiQa66a!I&67>&hD6K0Re?i0xY5_KtxjH#SUqAa~=Fmu-{;cRMK^HR0_f5T2i; zCoc_;g=gUPuXM0yISUlyLi>@kttewN6aHfyT#U2b7;W#>&{Mm&ai2_uOr> z50cC#e)3IzlYP!iK2&DS`cD9-+$LVi?TY&x-B(~e80rtzYr68lvxYb0o;NSMxVkRULC`mWyP?DC}Ux_|lVP4g#z{7;(sd~FYu zIz(IRKl=YZZ~o#J-)df7zT7JN5CZeXWpg!ub97TzZ?2l_*VoO(Z{}=z+f8Ja^ z+kA50*4d9PUpD_@^UK}7gUS;_#>V3Irnz`N?{M}fl&5UUK%?$HhRO&z9u&rX5a98*@OiDRswOGid5b;k(if=PUNIo`EY zXw)jY0_xOq(v<_&GRYHHua>Rr$_pI~)KfNbWra2G_1o9YC(YAJbY^mr{G*RPvfBs3 z<@j5WTv19485`%z38RdY^QJ+r<0+pq&{{d!eK3z}H)h-RI(c(L2t&Mv$?FUpj|`;8 z@rEZ0Qv{|6oK6I$L_eKlF?ljY;NBuICHlREb}~Fg;B+D|CHmeWac(B(A=^{xtgyG2d>^NL|Ju_g_iZyQ(j+$DW5jb ztxN4g&9~mCE3byR<4*0%u6Am_Qm^LGzI=FJ{pu&ROYeRC>p%PT<~#q^cgjvhhjzwe zVXX2v_1RQjUOg83=s6}+Kk4cLQwG2Kj;-w`OdI0bvMuZO7u$)e2UO?(2Luv4leEhY QZ2$lO07*qoM6N<$g7ykz9RL6T literal 0 HcmV?d00001 diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 5da10906ff..a40168039e 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -58,6 +58,7 @@ Windows.ScrollingWindow { Controls.WebView { id: webview url: "about:blank" + property string userScriptUrl: "" anchors.fill: parent focus: true } diff --git a/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml b/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml index e8e01c4865..c2e2ca4b40 100644 --- a/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml +++ b/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml @@ -13,7 +13,7 @@ import QtWebEngine 1.5 AnimatedImage { property WebEngineView webview: parent - source: "../../icons/loader-snake-64-w.gif" + source: "qrc:////icons//loader-snake-64-w.gif" visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString()) playing: visible z: 10000 diff --git a/interface/resources/qml/controlsUit/WebSpinner.qml b/interface/resources/qml/controlsUit/WebSpinner.qml index fb3dc3a8ab..bcf415e0c0 100644 --- a/interface/resources/qml/controlsUit/WebSpinner.qml +++ b/interface/resources/qml/controlsUit/WebSpinner.qml @@ -10,14 +10,14 @@ import QtQuick 2.5 -AnimatedImage { +Image { Item { id: webView property bool loading: false property string url: "" } - source: "../../icons/loader-snake-64-w.gif" + source: "qrc:////images//unsupportedImage.png" visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString()) playing: visible z: 10000 From 4727123e5f95f941b94901a630545cc9bdad7f26 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 8 Feb 2019 10:25:18 -0800 Subject: [PATCH 54/62] Do not include nitpick in production builds. --- tools/CMakeLists.txt | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 886f15ded4..ea9d4b8496 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -19,20 +19,36 @@ function(check_test name) endfunction() if (BUILD_TOOLS) - set(ALL_TOOLS - udt-test - vhacd-util - frame-optimizer - gpu-frame-player - ice-client - ktx-tool - ac-client - skeleton-dump - atp-client - oven - nitpick - ) - + # Allow different tools for production builds + if (RELEASE_TYPE STREQUAL "PRODUCTION") + set(ALL_TOOLS + udt-test + vhacd-util + frame-optimizer + gpu-frame-player + ice-client + ktx-tool + ac-client + skeleton-dump + atp-client + oven + ) + else() + set(ALL_TOOLS + udt-test + vhacd-util + frame-optimizer + gpu-frame-player + ice-client + ktx-tool + ac-client + skeleton-dump + atp-client + oven + ####nitpick + ) + endif() + foreach(TOOL ${ALL_TOOLS}) check_test(${TOOL}) if (${BUILD_TOOL_RESULT}) From 6598f83d39435e5a945c6bd13d3357f9291d1a7b Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 8 Feb 2019 11:22:39 -0800 Subject: [PATCH 55/62] Removed debug code. --- tools/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ea9d4b8496..4d7a594d92 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -45,7 +45,7 @@ if (BUILD_TOOLS) skeleton-dump atp-client oven - ####nitpick + nitpick ) endif() From 709f784feacdf5d8d8abbce276b6c9f519d94ddc Mon Sep 17 00:00:00 2001 From: raveenajain Date: Fri, 8 Feb 2019 16:51:18 -0800 Subject: [PATCH 56/62] :} --- libraries/fbx/src/GLTFSerializer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 9a23a9428f..d6de3d4b25 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -1095,6 +1095,7 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { fbxtex.content = _glbBinary.mid(offset, length); fbxtex.filename = textureUrl.toEncoded().append(texture.source); + } if (url.contains("data:image/jpeg;base64,") || url.contains("data:image/png;base64,")) { fbxtex.content = requestEmbeddedData(url); From dff98f462f621493fa31c61df033cdb13b3fe79c Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sat, 9 Feb 2019 13:13:13 -0800 Subject: [PATCH 57/62] Removed unnecessary of spaces. --- tools/nitpick/ui/Nitpick.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/nitpick/ui/Nitpick.ui b/tools/nitpick/ui/Nitpick.ui index 319452233f..16aaa9594d 100644 --- a/tools/nitpick/ui/Nitpick.ui +++ b/tools/nitpick/ui/Nitpick.ui @@ -43,7 +43,7 @@ - 3 + 0 @@ -85,7 +85,7 @@ - Create all MD files + Create all MD files @@ -124,7 +124,7 @@ - Create all Recursive Scripts + Create all Recursive Scripts From b2fb7a737be139b4a53f586d6e2550b8187c03bf Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sat, 9 Feb 2019 13:21:36 -0800 Subject: [PATCH 58/62] Ready for testing. --- tools/nitpick/src/Nitpick.cpp | 2 +- tools/nitpick/src/Test.cpp | 154 +++++++++++++--------------------- tools/nitpick/src/Test.h | 9 +- 3 files changed, 65 insertions(+), 100 deletions(-) diff --git a/tools/nitpick/src/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp index 78ed0ca0af..fa53730ce0 100644 --- a/tools/nitpick/src/Nitpick.cpp +++ b/tools/nitpick/src/Nitpick.cpp @@ -40,7 +40,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Nitpick - v2.0.1"); + setWindowTitle("Nitpick - v2.1.0"); } Nitpick::~Nitpick() { diff --git a/tools/nitpick/src/Test.cpp b/tools/nitpick/src/Test.cpp index 2e62296146..59583cda8b 100644 --- a/tools/nitpick/src/Test.cpp +++ b/tools/nitpick/src/Test.cpp @@ -758,47 +758,66 @@ void Test::createAllRecursiveScripts() { return; } + createAllRecursiveScripts(_testsRootDirectory); createRecursiveScript(_testsRootDirectory, false); - - QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - // Only process directories - QDir dir; - if (!isAValidDirectory(directory)) { - continue; - } - - // Only process directories that have sub-directories - bool hasNoSubDirectories{ true }; - QDirIterator it2(directory, QDirIterator::Subdirectories); - while (it2.hasNext()) { - QString directory2 = it2.next(); - - // Only process directories - QDir dir; - if (isAValidDirectory(directory2)) { - hasNoSubDirectories = false; - break; - } - } - - if (!hasNoSubDirectories) { - createRecursiveScript(directory, false); - } - } - QMessageBox::information(0, "Success", "Scripts have been created"); } -void Test::createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode) { - const QString recursiveTestsScriptName("testRecursive.js"); - const QString recursiveTestsFilename(topLevelDirectory + "/" + recursiveTestsScriptName); +void Test::createAllRecursiveScripts(const QString& directory) { + QDirIterator it(directory, QDirIterator::Subdirectories); + + while (it.hasNext()) { + QString nextDirectory = it.next(); + if (isAValidDirectory(nextDirectory)) { + createAllRecursiveScripts(nextDirectory); + createRecursiveScript(nextDirectory, false); + } + } +} + +void Test::createRecursiveScript(const QString& directory, bool interactiveMode) { + // If folder contains a test, then we are at a leaf + const QString testPathname{ directory + "/" + TEST_FILENAME }; + if (QFileInfo(testPathname).exists()) { + return; + } + + // Directories are included in reverse order. The nitpick scripts use a stack mechanism, + // so this ensures that the tests run in alphabetical order (a convenience when debugging) + QStringList directories; + QDirIterator it(directory); + while (it.hasNext()) { + QString subDirectory = it.next(); + + // Only process directories + if (!isAValidDirectory(subDirectory)) { + continue; + } + + const QString testPathname{ subDirectory + "/" + TEST_FILENAME }; + if (QFileInfo(testPathname).exists()) { + // Current folder contains a test script + directories.push_front(testPathname); + } + + const QString testRecursivePathname{ subDirectory + "/" + TEST_RECURSIVE_FILENAME }; + if (QFileInfo(testRecursivePathname).exists()) { + // Current folder contains a recursive script + directories.push_front(testRecursivePathname); + } + } + + // If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant + if (directories.length() == 0) { + return; + } + + // Open the recursive script file + const QString recursiveTestsFilename(directory + "/" + TEST_RECURSIVE_FILENAME); QFile recursiveTestsFile(recursiveTestsFilename); if (!recursiveTestsFile.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Failed to create \"" + recursiveTestsScriptName + "\" in directory \"" + topLevelDirectory + "\""); + "Failed to create \"" + TEST_RECURSIVE_FILENAME + "\" in directory \"" + directory + "\""); exit(-1); } @@ -812,72 +831,16 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact QString user = nitpick->getSelectedUser(); textStream << "PATH_TO_THE_REPO_PATH_UTILS_FILE = \"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + - "/tests/utils/branchUtils.js\";" - << endl; - textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl; - textStream << "var nitpick = createNitpick(Script.resolvePath(\".\"));" << endl << endl; + "/tests/utils/branchUtils.js\";" + << endl; + textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl << endl; - textStream << "var testsRootPath = nitpick.getTestsRootPath();" << endl << endl; - - // Wait 10 seconds before starting - textStream << "if (typeof Test !== 'undefined') {" << endl; - textStream << " Test.wait(10000);" << endl; - textStream << "};" << endl << endl; + textStream << "if (typeof nitpick === 'undefined') var nitpick = createNitpick(Script.resolvePath(\".\"));" << endl; + textStream << "if (typeof testsRootPath === 'undefined') var testsRootPath = nitpick.getTestsRootPath();" << endl << endl; textStream << "nitpick.enableRecursive();" << endl; textStream << "nitpick.enableAuto();" << endl << endl; - // This is used to verify that the recursive test contains at least one test - bool testFound{ false }; - - // Directories are included in reverse order. The nitpick scripts use a stack mechanism, - // so this ensures that the tests run in alphabetical order (a convenience when debugging) - QStringList directories; - - // First test if top-level folder has a test.js file - const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - // Current folder contains a test - directories.push_front(testPathname); - - testFound = true; - } - - QDirIterator it(topLevelDirectory, QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - // Only process directories - QDir dir(directory); - if (!isAValidDirectory(directory)) { - continue; - } - - const QString testPathname{ directory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - // Current folder contains a test - directories.push_front(testPathname); - - testFound = true; - } - } - - if (interactiveMode && !testFound) { - QMessageBox::information(0, "Failure", "No \"" + TEST_FILENAME + "\" files found"); - recursiveTestsFile.close(); - return; - } - - // If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant - // The script will be closed and deleted - if (directories.length() == 0) { - recursiveTestsFile.close(); - QFile::remove(recursiveTestsFilename); - return; - } - // Now include the test scripts for (int i = 0; i < directories.length(); ++i) { includeTest(textStream, directories.at(i)); @@ -928,7 +891,6 @@ void Test::createTestsOutline() { QString directory = it.next(); // Only process directories - QDir dir; if (!isAValidDirectory(directory)) { continue; } diff --git a/tools/nitpick/src/Test.h b/tools/nitpick/src/Test.h index aafd2f5711..842e4bdb48 100644 --- a/tools/nitpick/src/Test.h +++ b/tools/nitpick/src/Test.h @@ -72,9 +72,11 @@ public: void updateTestRailRunResult(); - void createRecursiveScript(); void createAllRecursiveScripts(); - void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode); + void createAllRecursiveScripts(const QString& directory); + + void createRecursiveScript(); + void createRecursiveScript(const QString& directory, bool interactiveMode); int compareImageLists(); int checkTextResults(); @@ -109,7 +111,8 @@ private: bool _isRunningFromCommandLine{ false }; bool _isRunningInAutomaticTestRun{ false }; - const QString TEST_FILENAME { "test.js" }; + const QString TEST_FILENAME{ "test.js" }; + const QString TEST_RECURSIVE_FILENAME{ "testRecursive.js" }; const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; From 20841a0eea1237dc8278f506ec18150e8245547a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Sat, 9 Feb 2019 16:42:34 -0800 Subject: [PATCH 59/62] fix skybox loading I think and pull in sabrina's ktx caching fix --- .../src/model-networking/TextureCache.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 910de258f9..c7235337c2 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -335,12 +335,8 @@ int networkTexturePointerMetaTypeId = qRegisterMetaType(url); - _lowestRequestedMipLevel = 0; - _loaded = true; } NetworkTexture::NetworkTexture(const NetworkTexture& other) : @@ -425,7 +421,7 @@ gpu::TexturePointer NetworkTexture::getFallbackTexture() const { class ImageReader : public QRunnable { public: ImageReader(const QWeakPointer& resource, const QUrl& url, - const QByteArray& data, int maxNumPixels); + const QByteArray& data, size_t extraHash, int maxNumPixels); void run() override final; void read(); @@ -435,6 +431,7 @@ private: QWeakPointer _resource; QUrl _url; QByteArray _content; + size_t _extraHash; int _maxNumPixels; }; @@ -1068,7 +1065,7 @@ void NetworkTexture::loadTextureContent(const QByteArray& content) { return; } - QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels)); + QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _extraHash, _maxNumPixels)); } void NetworkTexture::refresh() { @@ -1093,10 +1090,11 @@ void NetworkTexture::refresh() { Resource::refresh(); } -ImageReader::ImageReader(const QWeakPointer& resource, const QUrl& url, const QByteArray& data, int maxNumPixels) : +ImageReader::ImageReader(const QWeakPointer& resource, const QUrl& url, const QByteArray& data, size_t extraHash, int maxNumPixels) : _resource(resource), _url(url), _content(data), + _extraHash(extraHash), _maxNumPixels(maxNumPixels) { DependencyManager::get()->incrementStat("PendingProcessing"); @@ -1152,11 +1150,12 @@ void ImageReader::read() { } auto networkTexture = resource.staticCast(); - // Hash the source image to for KTX caching + // Hash the source image and extraHash for KTX caching std::string hash; { QCryptographicHash hasher(QCryptographicHash::Md5); hasher.addData(_content); + hasher.addData(std::to_string(_extraHash).c_str()); hash = hasher.result().toHex().toStdString(); } From d3e0aa5d8cd2cb69afb1afadadb17b29a9187a5b Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sun, 10 Feb 2019 20:32:38 -0800 Subject: [PATCH 60/62] Make sure only top-level recursive script runs recursively. --- tools/nitpick/src/Test.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/nitpick/src/Test.cpp b/tools/nitpick/src/Test.cpp index 59583cda8b..20d5ad89a6 100644 --- a/tools/nitpick/src/Test.cpp +++ b/tools/nitpick/src/Test.cpp @@ -835,8 +835,8 @@ void Test::createRecursiveScript(const QString& directory, bool interactiveMode) << endl; textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl << endl; - textStream << "if (typeof nitpick === 'undefined') var nitpick = createNitpick(Script.resolvePath(\".\"));" << endl; - textStream << "if (typeof testsRootPath === 'undefined') var testsRootPath = nitpick.getTestsRootPath();" << endl << endl; + textStream << "if (typeof nitpick === 'undefined') nitpick = createNitpick(Script.resolvePath(\".\"));" << endl; + textStream << "if (typeof testsRootPath === 'undefined') testsRootPath = nitpick.getTestsRootPath();" << endl << endl; textStream << "nitpick.enableRecursive();" << endl; textStream << "nitpick.enableAuto();" << endl << endl; @@ -847,7 +847,10 @@ void Test::createRecursiveScript(const QString& directory, bool interactiveMode) } textStream << endl; - textStream << "nitpick.runRecursive();" << endl; + textStream << "if (typeof runningRecursive === 'undefined') {" << endl; + textStream << " runningRecursive = true;" << endl; + textStream << " nitpick.runRecursive();" << endl; + textStream << "}" << endl << endl; recursiveTestsFile.close(); } From 16b48046c64c60466f116d5d8976b7df34db2321 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 11 Feb 2019 15:27:30 -0800 Subject: [PATCH 61/62] Fix not being able to type text into particle url field --- scripts/system/html/js/entityProperties.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index ee95312fa4..14a0031f1f 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -2328,7 +2328,7 @@ function createTextureProperty(property, elProperty) { elInput.setAttribute("id", elementID); elInput.setAttribute("type", "text"); - let imageLoad = _.debounce(function (url) { + let imageLoad = function(url) { if (url.slice(0, 5).toLowerCase() === "atp:/") { elImage.src = ""; elImage.style.display = "none"; @@ -2348,15 +2348,12 @@ function createTextureProperty(property, elProperty) { elDiv.classList.remove("no-preview"); elDiv.classList.add("no-texture"); } - }, IMAGE_DEBOUNCE_TIMEOUT); - elInput.imageLoad = imageLoad; - elInput.oninput = function (event) { - // Add throttle - let url = event.target.value; - imageLoad(url); - updateProperty(property.name, url, property.isParticleProperty) }; - elInput.onchange = elInput.oninput; + elInput.imageLoad = imageLoad; + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + elInput.addEventListener('change', function(ev) { + imageLoad(ev.target.value); + }); elProperty.appendChild(elInput); elProperty.appendChild(elDiv); From d96b0534ab539aab1be085f2edf8c06d6b0e3480 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Feb 2019 16:12:13 -0800 Subject: [PATCH 62/62] fix resource texture crash --- .../src/model-networking/TextureCache.cpp | 12 ++++++++---- .../src/model-networking/TextureCache.h | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index c7235337c2..d4cf7e6ce9 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -333,10 +333,14 @@ QSharedPointer TextureCache::createResourceCopy(const QSharedPointer>(); -NetworkTexture::NetworkTexture(const QUrl& url) : +NetworkTexture::NetworkTexture(const QUrl& url, bool resourceTexture) : Resource(url), _maxNumPixels(100) { + if (resourceTexture) { + _textureSource = std::make_shared(url); + _loaded = true; + } } NetworkTexture::NetworkTexture(const NetworkTexture& other) : @@ -1244,11 +1248,11 @@ void ImageReader::read() { Q_ARG(int, texture->getHeight())); } -NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) { +NetworkTexturePointer TextureCache::getResourceTexture(const QUrl& resourceTextureUrl) { gpu::TexturePointer texture; if (resourceTextureUrl == SPECTATOR_CAMERA_FRAME_URL) { if (!_spectatorCameraNetworkTexture) { - _spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl)); + _spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl, true)); } if (!_spectatorCameraFramebuffer) { getSpectatorCameraFramebuffer(); // initialize frame buffer @@ -1259,7 +1263,7 @@ NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) // FIXME: Generalize this, DRY up this code if (resourceTextureUrl == HMD_PREVIEW_FRAME_URL) { if (!_hmdPreviewNetworkTexture) { - _hmdPreviewNetworkTexture.reset(new NetworkTexture(resourceTextureUrl)); + _hmdPreviewNetworkTexture.reset(new NetworkTexture(resourceTextureUrl, true)); } if (_hmdPreviewFramebuffer) { texture = _hmdPreviewFramebuffer->getRenderBuffer(0); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index d744d060b6..cdedc64ea5 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -45,7 +45,7 @@ class NetworkTexture : public Resource, public Texture { Q_OBJECT public: - NetworkTexture(const QUrl& url); + NetworkTexture(const QUrl& url, bool resourceTexture = false); NetworkTexture(const NetworkTexture& other); ~NetworkTexture() override; @@ -183,7 +183,7 @@ public: gpu::TexturePointer getTextureByHash(const std::string& hash); gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture); - NetworkTexturePointer getResourceTexture(QUrl resourceTextureUrl); + NetworkTexturePointer getResourceTexture(const QUrl& resourceTextureUrl); const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height); const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(); const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(int width, int height);