From 21fd10d154c43442e3ec4e4c0b17f51271f0f863 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 3 Jun 2020 15:52:10 -0400 Subject: [PATCH] Revert commit 359248829c using -m 1 to temporarily fix issue 383. --- CMakeLists.txt | 1 - .../src/avatars/ScriptableAvatar.cpp | 4 +- interface/src/ModelPropertiesDialog.cpp | 2 +- interface/src/avatar/AvatarDoctor.cpp | 8 +- interface/src/avatar/AvatarDoctor.h | 2 +- interface/src/avatar/MyAvatar.cpp | 4 +- interface/src/raypick/CollisionPick.cpp | 238 +- interface/src/raypick/CollisionPick.h | 5 +- libraries/animation/src/AnimSkeleton.cpp | 17 +- libraries/animation/src/AnimSkeleton.h | 2 +- .../src/avatars-renderer/Avatar.cpp | 2 +- .../src/avatars-renderer/SkeletonModel.cpp | 4 +- libraries/baking/src/FBXBaker.cpp | 6 +- libraries/baking/src/FBXBaker.h | 2 +- libraries/baking/src/MaterialBaker.cpp | 4 +- libraries/baking/src/MaterialBaker.h | 2 +- libraries/baking/src/ModelBaker.cpp | 2 +- libraries/baking/src/OBJBaker.cpp | 43 +- libraries/baking/src/OBJBaker.h | 4 +- .../src/RenderableModelEntityItem.cpp | 372 +-- .../src/RenderableModelEntityItem.h | 2 +- .../RenderableParticleEffectEntityItem.cpp | 14 +- .../src/RenderableParticleEffectEntityItem.h | 4 +- .../src/RenderablePolyVoxEntityItem.cpp | 49 +- libraries/entities/src/ZoneEntityItem.cpp | 4 +- libraries/entities/src/ZoneEntityItem.h | 2 +- libraries/fbx/src/FBXSerializer.cpp | 525 +++-- libraries/fbx/src/FBXSerializer.h | 10 +- libraries/fbx/src/FBXSerializer_Mesh.cpp | 21 +- libraries/fbx/src/FST.cpp | 2 +- libraries/fbx/src/GLTFSerializer.cpp | 1998 +++++++++-------- libraries/fbx/src/GLTFSerializer.h | 79 +- libraries/fbx/src/OBJSerializer.cpp | 164 +- libraries/fbx/src/OBJSerializer.h | 1 + libraries/hfm/src/hfm/HFM.cpp | 169 +- libraries/hfm/src/hfm/HFM.h | 98 +- libraries/hfm/src/hfm/HFMModelMath.cpp | 212 -- libraries/hfm/src/hfm/HFMModelMath.h | 45 - libraries/hfm/src/hfm/HFMSerializer.h | 2 +- .../model-baker/src/model-baker/Baker.cpp | 83 +- .../model-baker/src/model-baker/BakerTypes.h | 8 - .../src/model-baker/BuildDracoMeshTask.cpp | 67 +- .../src/model-baker/BuildDracoMeshTask.h | 2 +- .../src/model-baker/BuildGraphicsMeshTask.cpp | 74 +- .../src/model-baker/BuildGraphicsMeshTask.h | 6 +- .../model-baker/CalculateMeshTangentsTask.cpp | 2 +- .../CalculateTransformedExtentsTask.cpp | 41 - .../CalculateTransformedExtentsTask.h | 29 - .../model-baker/CollectShapeVerticesTask.cpp | 91 - .../model-baker/CollectShapeVerticesTask.h | 30 - .../src/model-networking/ModelCache.cpp | 126 +- .../src/model-networking/ModelCache.h | 58 +- libraries/physics/src/ShapeFactory.cpp | 12 +- .../src/procedural/ProceduralMaterialCache.h | 2 +- .../render-utils/src/CauterizedModel.cpp | 218 +- libraries/render-utils/src/CauterizedModel.h | 2 +- libraries/render-utils/src/GeometryCache.cpp | 2 +- libraries/render-utils/src/GeometryCache.h | 2 +- .../render-utils/src/MeshPartPayload.cpp | 97 +- libraries/render-utils/src/MeshPartPayload.h | 15 +- libraries/render-utils/src/Model.cpp | 337 +-- libraries/render-utils/src/Model.h | 30 +- .../render-utils/src/SoftAttachmentModel.cpp | 37 +- libraries/shared/src/GLMHelpers.h | 10 - libraries/shared/src/ShapeInfo.cpp | 10 +- libraries/shared/src/ShapeInfo.h | 12 +- tools/vhacd-util/src/VHACDUtil.cpp | 17 +- tools/vhacd-util/src/VHACDUtilApp.cpp | 6 +- 68 files changed, 2639 insertions(+), 2912 deletions(-) delete mode 100644 libraries/hfm/src/hfm/HFMModelMath.cpp delete mode 100644 libraries/hfm/src/hfm/HFMModelMath.h delete mode 100644 libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.cpp delete mode 100644 libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.h delete mode 100644 libraries/model-baker/src/model-baker/CollectShapeVerticesTask.cpp delete mode 100644 libraries/model-baker/src/model-baker/CollectShapeVerticesTask.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ecbfd2eed9..16ac6d2a83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,7 +270,6 @@ find_package( Threads ) add_definitions(-DGLM_FORCE_RADIANS) add_definitions(-DGLM_ENABLE_EXPERIMENTAL) add_definitions(-DGLM_FORCE_CTOR_INIT) -add_definitions(-DGLM_LANG_STL11_FORCED) # Workaround for GLM not detecting support for C++11 templates on Android if (WIN32) # Deal with fakakta Visual Studo 2017 bug diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 3e91cd05e8..529ad4b387 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -144,10 +144,10 @@ void ScriptableAvatar::update(float deltatime) { } _animationDetails.currentFrame = currentFrame; - const std::vector& modelJoints = _bind->getHFMModel().joints; + const QVector& modelJoints = _bind->getHFMModel().joints; QStringList animationJointNames = _animation->getJointNames(); - const auto nJoints = (int)modelJoints.size(); + const int nJoints = modelJoints.size(); if (_jointData.size() != nJoints) { _jointData.resize(nJoints); } diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index bf7fd26b08..d67341990d 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -80,7 +80,7 @@ QVariantHash ModelPropertiesDialog::getMapping() const { // update the joint indices QVariantHash jointIndices; - for (size_t i = 0; i < _hfmModel.joints.size(); i++) { + for (int i = 0; i < _hfmModel.joints.size(); i++) { jointIndices.insert(_hfmModel.joints.at(i).name, QString::number(i)); } mapping.insert(JOINT_INDEX_FIELD, jointIndices); diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 1fafe6485d..d84383cf4f 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -79,7 +79,7 @@ void AvatarDoctor::startDiagnosing() { _missingTextureCount = 0; _unsupportedTextureCount = 0; - const auto resource = DependencyManager::get()->getModelResource(_avatarFSTFileUrl); + const auto resource = DependencyManager::get()->getGeometryResource(_avatarFSTFileUrl); resource->refresh(); const auto resourceLoaded = [this, resource](bool success) { @@ -99,12 +99,12 @@ void AvatarDoctor::startDiagnosing() { } // RIG - if (avatarModel.joints.empty()) { + if (avatarModel.joints.isEmpty()) { addError("Avatar has no rig.", "no-rig"); } else { auto jointNames = avatarModel.getJointNames(); - if (avatarModel.joints.size() > NETWORKED_JOINTS_LIMIT) { + if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) { addError(tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), "maximum-bone-limit"); } // Avatar does not have Hips bone mapped @@ -297,7 +297,7 @@ void AvatarDoctor::startDiagnosing() { if (resource->isLoaded()) { resourceLoaded(!resource->isFailed()); } else { - connect(resource.data(), &ModelResource::finished, this, resourceLoaded); + connect(resource.data(), &GeometryResource::finished, this, resourceLoaded); } } else { addError("Model file cannot be opened", "missing-file"); diff --git a/interface/src/avatar/AvatarDoctor.h b/interface/src/avatar/AvatarDoctor.h index 1e3c84e02f..1465a5defc 100644 --- a/interface/src/avatar/AvatarDoctor.h +++ b/interface/src/avatar/AvatarDoctor.h @@ -53,7 +53,7 @@ private: int _materialMappingCount = 0; int _materialMappingLoadedCount = 0; - ModelResource::Pointer _model; + GeometryResource::Pointer _model; bool _isDiagnosing = false; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 749b617dbe..f947028e02 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2474,7 +2474,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { if (_fullAvatarModelName.isEmpty()) { // Store the FST file name into preferences - const auto& mapping = _skeletonModel->getNetworkModel()->getMapping(); + const auto& mapping = _skeletonModel->getGeometry()->getMapping(); if (mapping.value("name").isValid()) { _fullAvatarModelName = mapping.value("name").toString(); } @@ -2482,7 +2482,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); - _fstAnimGraphOverrideUrl = _skeletonModel->getNetworkModel()->getAnimGraphOverrideUrl(); + _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); initFlowFromFST(); } diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 756c8fab7f..2602bdb0a0 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -121,9 +121,8 @@ bool CollisionPick::isLoaded() const { bool CollisionPick::getShapeInfoReady(const CollisionRegion& pick) { if (_mathPick.shouldComputeShapeInfo()) { if (_cachedResource && _cachedResource->isLoaded()) { - // TODO: Model CollisionPick support - //computeShapeInfo(pick, *_mathPick.shapeInfo, _cachedResource); - //_mathPick.loaded = true; + computeShapeInfo(pick, *_mathPick.shapeInfo, _cachedResource); + _mathPick.loaded = true; } else { _mathPick.loaded = false; } @@ -135,7 +134,7 @@ bool CollisionPick::getShapeInfoReady(const CollisionRegion& pick) { return _mathPick.loaded; } -void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { +void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { ShapeType type = shapeInfo.getType(); glm::vec3 dimensions = pick.transform.getScale(); QString modelURL = (resource ? resource->getURL().toString() : ""); @@ -148,12 +147,241 @@ void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, } } +void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { + // This code was copied and modified from RenderableModelEntityItem::computeShapeInfo + // TODO: Move to some shared code area (in entities-renderer? model-networking?) + // after we verify this is working and do a diff comparison with RenderableModelEntityItem::computeShapeInfo + // to consolidate the code. + // We may also want to make computeShapeInfo always abstract away from the gpu model mesh, like it does here. + const uint32_t TRIANGLE_STRIDE = 3; + const uint32_t QUAD_STRIDE = 4; + + ShapeType type = shapeInfo.getType(); + glm::vec3 dimensions = pick.transform.getScale(); + if (type == SHAPE_TYPE_COMPOUND) { + // should never fall in here when collision model not fully loaded + // TODO: assert that all geometries exist and are loaded + //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); + const HFMModel& collisionModel = resource->getHFMModel(); + + ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); + pointCollection.clear(); + uint32_t i = 0; + + // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect + // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. + foreach (const HFMMesh& mesh, collisionModel.meshes) { + // each meshPart is a convex hull + foreach (const HFMMeshPart &meshPart, mesh.parts) { + pointCollection.push_back(QVector()); + ShapeInfo::PointList& pointsInPart = pointCollection[i]; + + // run through all the triangles and (uniquely) add each point to the hull + uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up + //assert(numIndices % TRIANGLE_STRIDE == 0); + numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer + + for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + } + + // run through all the quads and (uniquely) add each point to the hull + numIndices = (uint32_t)meshPart.quadIndices.size(); + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up + //assert(numIndices % QUAD_STRIDE == 0); + numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer + + for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; + glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + if (!pointsInPart.contains(p3)) { + pointsInPart << p3; + } + } + + if (pointsInPart.size() == 0) { + qCDebug(scriptengine) << "Warning -- meshPart has no faces"; + pointCollection.pop_back(); + continue; + } + ++i; + } + } + + // We expect that the collision model will have the same units and will be displaced + // from its origin in the same way the visual model is. The visual model has + // been centered and probably scaled. We take the scaling and offset which were applied + // to the visual model and apply them to the collision model (without regard for the + // collision model's extents). + + glm::vec3 scaleToFit = dimensions / resource->getHFMModel().getUnscaledMeshExtents().size(); + // multiply each point by scale + for (int32_t i = 0; i < pointCollection.size(); i++) { + for (int32_t j = 0; j < pointCollection[i].size(); j++) { + // back compensate for registration so we can apply that offset to the shapeInfo later + pointCollection[i][j] = scaleToFit * pointCollection[i][j]; + } + } + shapeInfo.setParams(type, dimensions, resource->getURL().toString()); + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + const HFMModel& hfmModel = resource->getHFMModel(); + int numHFMMeshes = hfmModel.meshes.size(); + int totalNumVertices = 0; + for (int i = 0; i < numHFMMeshes; i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); + totalNumVertices += mesh.vertices.size(); + } + const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; + if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { + qWarning() << "model" << "has too many vertices" << totalNumVertices << "and will collide as a box."; + shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + auto& meshes = resource->getHFMModel().meshes; + int32_t numMeshes = (int32_t)(meshes.size()); + + const int MAX_ALLOWED_MESH_COUNT = 1000; + if (numMeshes > MAX_ALLOWED_MESH_COUNT) { + // too many will cause the deadlock timer to throw... + shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); + pointCollection.clear(); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + pointCollection.resize(numMeshes); + } else { + pointCollection.resize(1); + } + + ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); + triangleIndices.clear(); + + Extents extents; + int32_t meshCount = 0; + int32_t pointListIndex = 0; + for (auto& mesh : meshes) { + if (!mesh.vertices.size()) { + continue; + } + QVector vertices = mesh.vertices; + + ShapeInfo::PointList& points = pointCollection[pointListIndex]; + + // reserve room + int32_t sizeToReserve = (int32_t)(vertices.count()); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // a list of points for each mesh + pointListIndex++; + } else { + // only one list of points + sizeToReserve += (int32_t)points.size(); + } + points.reserve(sizeToReserve); + + // copy points + const glm::vec3* vertexItr = vertices.cbegin(); + while (vertexItr != vertices.cend()) { + glm::vec3 point = *vertexItr; + points.push_back(point); + extents.addPoint(point); + ++vertexItr; + } + + if (type == SHAPE_TYPE_STATIC_MESH) { + // copy into triangleIndices + size_t triangleIndicesCount = 0; + for (const HFMMeshPart& meshPart : mesh.parts) { + triangleIndicesCount += meshPart.triangleIndices.count(); + } + triangleIndices.reserve((int)triangleIndicesCount); + + for (const HFMMeshPart& meshPart : mesh.parts) { + const int* indexItr = meshPart.triangleIndices.cbegin(); + while (indexItr != meshPart.triangleIndices.cend()) { + triangleIndices.push_back(*indexItr); + ++indexItr; + } + } + } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // for each mesh copy unique part indices, separated by special bogus (flag) index values + for (const HFMMeshPart& meshPart : mesh.parts) { + // collect unique list of indices for this part + std::set uniqueIndices; + auto numIndices = meshPart.triangleIndices.count(); + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up + //assert(numIndices% TRIANGLE_STRIDE == 0); + numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer + + auto indexItr = meshPart.triangleIndices.cbegin(); + while (indexItr != meshPart.triangleIndices.cend()) { + uniqueIndices.insert(*indexItr); + ++indexItr; + } + + // store uniqueIndices in triangleIndices + triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); + for (auto index : uniqueIndices) { + triangleIndices.push_back(index); + } + // flag end of part + triangleIndices.push_back(END_OF_MESH_PART); + } + // flag end of mesh + triangleIndices.push_back(END_OF_MESH); + } + ++meshCount; + } + + // scale and shift + glm::vec3 extentsSize = extents.size(); + glm::vec3 scaleToFit = dimensions / extentsSize; + for (int32_t i = 0; i < 3; ++i) { + if (extentsSize[i] < 1.0e-6f) { + scaleToFit[i] = 1.0f; + } + } + for (auto points : pointCollection) { + for (int32_t i = 0; i < points.size(); ++i) { + points[i] = (points[i] * scaleToFit); + } + } + + shapeInfo.setParams(type, 0.5f * dimensions, resource->getURL().toString()); + } +} + CollisionPick::CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, bool scaleWithParent, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine) : Pick(collisionRegion, filter, maxDistance, enabled), _scaleWithParent(scaleWithParent), _physicsEngine(physicsEngine) { if (collisionRegion.shouldComputeShapeInfo()) { - _cachedResource = DependencyManager::get()->getCollisionModelResource(collisionRegion.modelURL); + _cachedResource = DependencyManager::get()->getCollisionGeometryResource(collisionRegion.modelURL); } _mathPick.loaded = isLoaded(); } diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 617c7b1f00..24317bf19a 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -63,13 +63,14 @@ protected: bool isLoaded() const; // Returns true if _mathPick.shapeInfo is valid. Otherwise, attempts to get the _mathPick ready for use. bool getShapeInfoReady(const CollisionRegion& pick); - void computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + void computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + void computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); void filterIntersections(std::vector& intersections) const; bool _scaleWithParent; PhysicsEnginePointer _physicsEngine; - QSharedPointer _cachedResource; + QSharedPointer _cachedResource; // Options for what information to get from collision results bool _includeNormals; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index e5f05ab45f..b26d00d8d0 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -20,17 +20,24 @@ AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) { _geometryOffset = hfmModel.offset; - buildSkeletonFromJoints(hfmModel.joints, hfmModel.jointRotationOffsets); + // convert to std::vector of joints + std::vector joints; + joints.reserve(hfmModel.joints.size()); + for (auto& joint : hfmModel.joints) { + joints.push_back(joint); + } + buildSkeletonFromJoints(joints, hfmModel.jointRotationOffsets); // we make a copy of the inverseBindMatrices in order to prevent mutating the model bind pose // when we are dealing with a joint offset in the model - for (uint32_t i = 0; i < (uint32_t)hfmModel.skinDeformers.size(); i++) { - const auto& deformer = hfmModel.skinDeformers[i]; + for (int i = 0; i < (int)hfmModel.meshes.size(); i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); std::vector dummyClustersList; - for (uint32_t j = 0; j < (uint32_t)deformer.clusters.size(); j++) { + for (int j = 0; j < mesh.clusters.size(); j++) { + std::vector bindMatrices; // cast into a non-const reference, so we can mutate the FBXCluster - HFMCluster& cluster = const_cast(deformer.clusters.at(j)); + HFMCluster& cluster = const_cast(mesh.clusters.at(j)); HFMCluster localCluster; localCluster.jointIndex = cluster.jointIndex; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index a6470ac609..efc1c1599f 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -68,7 +68,7 @@ public: void dump(const AnimPoseVec& poses) const; std::vector lookUpJointIndices(const std::vector& jointNames) const; - const HFMCluster getClusterBindMatricesOriginalValues(int skinDeformerIndex, int clusterIndex) const { return _clusterBindMatrixOriginalValues[skinDeformerIndex][clusterIndex]; } + const HFMCluster getClusterBindMatricesOriginalValues(const int meshIndex, const int clusterIndex) const { return _clusterBindMatrixOriginalValues[meshIndex][clusterIndex]; } protected: void buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 3792057052..16dc99ab49 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -943,7 +943,7 @@ void Avatar::simulateAttachments(float deltaTime) { bool texturesLoaded = _attachmentModelsTexturesLoaded.at(i); // Watch for texture loading - if (!texturesLoaded && model->getNetworkModel() && model->getNetworkModel()->areTexturesLoaded()) { + if (!texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { _attachmentModelsTexturesLoaded[i] = true; model->updateRenderItems(); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index e9e26ca716..ccc87c28f3 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -171,7 +171,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { // FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem, // but Avatars don't get updates in the same way - if (!_texturesLoaded && getNetworkModel() && getNetworkModel()->areTexturesLoaded()) { + if (!_texturesLoaded && getGeometry() && getGeometry()->areTexturesLoaded()) { _texturesLoaded = true; updateRenderItems(); } @@ -326,7 +326,7 @@ void SkeletonModel::computeBoundingShape() { } const HFMModel& hfmModel = getHFMModel(); - if (hfmModel.joints.empty() || _rig.indexOfJoint("Hips") == -1) { + if (hfmModel.joints.isEmpty() || _rig.indexOfJoint("Hips") == -1) { // rootJointIndex == -1 if the avatar model has no skeleton return; } diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index 7f508dfe15..eb02ac2241 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -90,11 +90,11 @@ void FBXBaker::replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dra } } -void FBXBaker::rewriteAndBakeSceneModels(const std::vector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) { +void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) { std::vector meshIndexToRuntimeOrder; - auto meshCount = (uint32_t)meshes.size(); + auto meshCount = (int)meshes.size(); meshIndexToRuntimeOrder.resize(meshCount); - for (uint32_t i = 0; i < meshCount; i++) { + for (int i = 0; i < meshCount; i++) { meshIndexToRuntimeOrder[meshes[i].meshIndex] = i; } diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index 6ac05e36e9..a528de512d 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -33,7 +33,7 @@ protected: virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) override; private: - void rewriteAndBakeSceneModels(const std::vector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists); + void rewriteAndBakeSceneModels(const QVector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists); void replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector& dracoMaterialList); }; diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp index fbb17f0d01..9a1b1b2d24 100644 --- a/libraries/baking/src/MaterialBaker.cpp +++ b/libraries/baking/src/MaterialBaker.cpp @@ -258,9 +258,9 @@ void MaterialBaker::addTexture(const QString& materialName, image::TextureUsage: } }; -void MaterialBaker::setMaterials(const std::vector& materials, const QString& baseURL) { +void MaterialBaker::setMaterials(const QHash& materials, const QString& baseURL) { _materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); - for (const auto& material : materials) { + for (auto& material : materials) { _materialResource->parsedMaterials.names.push_back(material.name.toStdString()); _materialResource->parsedMaterials.networkMaterials[material.name.toStdString()] = std::make_shared(material, baseURL); diff --git a/libraries/baking/src/MaterialBaker.h b/libraries/baking/src/MaterialBaker.h index 68efcfbd7e..33123cfc73 100644 --- a/libraries/baking/src/MaterialBaker.h +++ b/libraries/baking/src/MaterialBaker.h @@ -32,7 +32,7 @@ public: bool isURL() const { return _isURL; } QString getBakedMaterialData() const { return _bakedMaterialData; } - void setMaterials(const std::vector& materials, const QString& baseURL); + void setMaterials(const QHash& materials, const QString& baseURL); void setMaterials(const NetworkMaterialResourcePointer& materialResource); NetworkMaterialResourcePointer getNetworkMaterialResource() const { return _materialResource; } diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 4e76dbb2d5..70290fe283 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -265,7 +265,7 @@ void ModelBaker::bakeSourceCopy() { return; } - if (!_hfmModel->materials.empty()) { + if (!_hfmModel->materials.isEmpty()) { _materialBaker = QSharedPointer( new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir), &MaterialBaker::deleteLater diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp index d726dee897..a2d0ab1094 100644 --- a/libraries/baking/src/OBJBaker.cpp +++ b/libraries/baking/src/OBJBaker.cpp @@ -37,10 +37,10 @@ const QByteArray MESH = "Mesh"; void OBJBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) { // Write OBJ Data as FBX tree nodes - createFBXNodeTree(_rootNode, hfmModel, dracoMeshes[0], dracoMaterialLists[0]); + createFBXNodeTree(_rootNode, hfmModel, dracoMeshes[0]); } -void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh, const std::vector& dracoMaterialList) { +void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh) { // Make all generated nodes children of rootNode rootNode.children = { FBXNode(), FBXNode(), FBXNode() }; FBXNode& globalSettingsNode = rootNode.children[0]; @@ -100,22 +100,19 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h } // Generating Objects node's child - Material node - - // Each material ID should only appear once thanks to deduplication in BuildDracoMeshTask, but we want to make sure they are created in the right order - std::unordered_map materialIDToIndex; - for (uint32_t materialIndex = 0; materialIndex < hfmModel->materials.size(); ++materialIndex) { - const auto& material = hfmModel->materials[materialIndex]; - materialIDToIndex[material.materialID] = materialIndex; - } - - // Create nodes for each material in the material list - for (const auto& dracoMaterial : dracoMaterialList) { - const QString materialID = QString(dracoMaterial); - const uint32_t materialIndex = materialIDToIndex[materialID]; - const auto& material = hfmModel->materials[materialIndex]; + auto& meshParts = hfmModel->meshes[0].parts; + for (auto& meshPart : meshParts) { FBXNode materialNode; materialNode.name = MATERIAL_NODE_NAME; - setMaterialNodeProperties(materialNode, material.materialID, material, hfmModel); + if (hfmModel->materials.size() == 1) { + // case when no material information is provided, OBJSerializer considers it as a single default material + for (auto& materialID : hfmModel->materials.keys()) { + setMaterialNodeProperties(materialNode, materialID, hfmModel); + } + } else { + setMaterialNodeProperties(materialNode, meshPart.materialID, hfmModel); + } + objectNode.children.append(materialNode); } @@ -156,10 +153,12 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h } // Set properties for material nodes -void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, const QString& materialName, const hfm::Material& material, const hfm::Model::Pointer& hfmModel) { +void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel) { auto materialID = nextNodeID(); _materialIDs.push_back(materialID); - materialNode.properties = { materialID, materialName, MESH }; + materialNode.properties = { materialID, material, MESH }; + + HFMMaterial currentMaterial = hfmModel->materials[material]; // Setting the hierarchy: Material -> Properties70 -> P -> Properties FBXNode properties70Node; @@ -171,7 +170,7 @@ void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, const QString& m pNodeDiffuseColor.name = P_NODE_NAME; pNodeDiffuseColor.properties.append({ "DiffuseColor", "Color", "", "A", - material.diffuseColor[0], material.diffuseColor[1], material.diffuseColor[2] + currentMaterial.diffuseColor[0], currentMaterial.diffuseColor[1], currentMaterial.diffuseColor[2] }); } properties70Node.children.append(pNodeDiffuseColor); @@ -182,7 +181,7 @@ void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, const QString& m pNodeSpecularColor.name = P_NODE_NAME; pNodeSpecularColor.properties.append({ "SpecularColor", "Color", "", "A", - material.specularColor[0], material.specularColor[1], material.specularColor[2] + currentMaterial.specularColor[0], currentMaterial.specularColor[1], currentMaterial.specularColor[2] }); } properties70Node.children.append(pNodeSpecularColor); @@ -193,7 +192,7 @@ void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, const QString& m pNodeShininess.name = P_NODE_NAME; pNodeShininess.properties.append({ "Shininess", "Number", "", "A", - material.shininess + currentMaterial.shininess }); } properties70Node.children.append(pNodeShininess); @@ -204,7 +203,7 @@ void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, const QString& m pNodeOpacity.name = P_NODE_NAME; pNodeOpacity.properties.append({ "Opacity", "Number", "", "A", - material.opacity + currentMaterial.opacity }); } properties70Node.children.append(pNodeOpacity); diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h index 778b4da341..55adec5786 100644 --- a/libraries/baking/src/OBJBaker.h +++ b/libraries/baking/src/OBJBaker.h @@ -27,8 +27,8 @@ protected: virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) override; private: - void createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh, const std::vector& dracoMaterialList); - void setMaterialNodeProperties(FBXNode& materialNode, const QString& materialName, const hfm::Material& material, const hfm::Model::Pointer& hfmModel); + void createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh); + void setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel); NodeID nextNodeID() { return _nodeID++; } NodeID _nodeID { 0 }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d88ed2fef0..fb2cf01580 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -282,7 +282,7 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3 } void RenderableModelEntityItem::fetchCollisionGeometryResource() { - _collisionGeometryResource = DependencyManager::get()->getCollisionModelResource(getCollisionShapeURL()); + _collisionGeometryResource = DependencyManager::get()->getCollisionGeometryResource(getCollisionShapeURL()); } bool RenderableModelEntityItem::unableToLoadCollisionShape() { @@ -357,6 +357,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() const { void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { const uint32_t TRIANGLE_STRIDE = 3; + const uint32_t QUAD_STRIDE = 4; ShapeType type = getShapeType(); @@ -379,35 +380,59 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); pointCollection.clear(); - - size_t numParts = 0; - for (const HFMMesh& mesh : collisionGeometry.meshes) { - numParts += mesh.triangleListMesh.parts.size(); - } - pointCollection.reserve(numParts); + uint32_t i = 0; // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. - for (const HFMMesh& mesh : collisionGeometry.meshes) { - const hfm::TriangleListMesh& triangleListMesh = mesh.triangleListMesh; + foreach (const HFMMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull - for (const glm::ivec2& part : triangleListMesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { + pointCollection.push_back(QVector()); + ShapeInfo::PointList& pointsInPart = pointCollection[i]; + // run through all the triangles and (uniquely) add each point to the hull - - pointCollection.emplace_back(); - ShapeInfo::PointList& pointsInPart = pointCollection.back(); - - uint32_t numIndices = (uint32_t)part.y; + uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - uint32_t indexStart = (uint32_t)part.x; - uint32_t indexEnd = indexStart + numIndices; - for (uint32_t j = indexStart; j < indexEnd; ++j) { - // NOTE: It seems odd to skip vertices when initializing a btConvexHullShape, but let's keep the behavior similar to the old behavior for now - glm::vec3 point = triangleListMesh.vertices[triangleListMesh.indices[j]]; - if (std::find(pointsInPart.cbegin(), pointsInPart.cend(), point) == pointsInPart.cend()) { - pointsInPart.push_back(point); + + for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + } + + // run through all the quads and (uniquely) add each point to the hull + numIndices = (uint32_t)meshPart.quadIndices.size(); + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up + //assert(numIndices % QUAD_STRIDE == 0); + numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer + + for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; + glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + if (!pointsInPart.contains(p3)) { + pointsInPart << p3; } } @@ -416,6 +441,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { pointCollection.pop_back(); continue; } + ++i; } } @@ -430,8 +456,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); - for (size_t i = 0; i < pointCollection.size(); i++) { - for (size_t j = 0; j < pointCollection[i].size(); j++) { + for (int32_t i = 0; i < pointCollection.size(); i++) { + for (int32_t j = 0; j < pointCollection[i].size(); j++) { // back compensate for registration so we can apply that offset to the shapeInfo later pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + model->getOffset()) - registrationOffset; } @@ -445,63 +471,46 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { model->updateGeometry(); // compute meshPart local transforms + QVector localTransforms; const HFMModel& hfmModel = model->getHFMModel(); + int numHFMMeshes = hfmModel.meshes.size(); + int totalNumVertices = 0; glm::vec3 dimensions = getScaledDimensions(); glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT)); - - ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); - triangleIndices.clear(); - - Extents extents; - int32_t shapeCount = 0; - int32_t instanceIndex = 0; - - // NOTE: Each pointCollection corresponds to a mesh. Therefore, we should have one pointCollection per mesh instance - // A mesh instance is a unique combination of mesh/transform. For every mesh instance, there are as many shapes as there are parts for that mesh. - // We assume the shapes are grouped by mesh instance, and the group contains one of each mesh part. - uint32_t numInstances = 0; - std::vector>> shapesPerInstancePerMesh; - shapesPerInstancePerMesh.resize(hfmModel.meshes.size()); - for (uint32_t shapeIndex = 0; shapeIndex < hfmModel.shapes.size();) { - const auto& shape = hfmModel.shapes[shapeIndex]; - uint32_t meshIndex = shape.mesh; - const auto& mesh = hfmModel.meshes[meshIndex]; - uint32_t numMeshParts = (uint32_t)mesh.parts.size(); - assert(numMeshParts != 0); - - auto& shapesPerInstance = shapesPerInstancePerMesh[meshIndex]; - shapesPerInstance.emplace_back(); - - auto& shapes = shapesPerInstance.back(); - shapes.resize(numMeshParts); - std::iota(shapes.begin(), shapes.end(), shapeIndex); - - shapeIndex += numMeshParts; - ++numInstances; + for (int i = 0; i < numHFMMeshes; i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); + if (mesh.clusters.size() > 0) { + const HFMCluster& cluster = mesh.clusters.at(0); + auto jointMatrix = model->getRig().getJointTransform(cluster.jointIndex); + // we backtranslate by the registration offset so we can apply that offset to the shapeInfo later + localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix); + } else { + localTransforms.push_back(invRegistraionOffset); + } + totalNumVertices += mesh.vertices.size(); } - - const uint32_t MAX_ALLOWED_MESH_COUNT = 1000; - if (numInstances > MAX_ALLOWED_MESH_COUNT) { - // too many will cause the deadlock timer to throw... - qWarning() << "model" << getModelURL() << "has too many collision meshes" << numInstances << "and will collide as a box."; + const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; + if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { + qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); return; } - size_t totalNumVertices = 0; - for (const auto& shapesPerInstance : shapesPerInstancePerMesh) { - for (const auto& instanceShapes : shapesPerInstance) { - const uint32_t firstShapeIndex = instanceShapes.front(); - const auto& firstShape = hfmModel.shapes[firstShapeIndex]; - const auto& mesh = hfmModel.meshes[firstShape.mesh]; - const auto& triangleListMesh = mesh.triangleListMesh; - // Added once per instance per mesh - totalNumVertices += triangleListMesh.vertices.size(); + std::vector> meshes; + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + auto& hfmMeshes = _collisionGeometryResource->getHFMModel().meshes; + meshes.reserve(hfmMeshes.size()); + for (auto& hfmMesh : hfmMeshes) { + meshes.push_back(hfmMesh._mesh); } + } else { + meshes = model->getGeometry()->getMeshes(); } - const size_t MAX_VERTICES_PER_STATIC_MESH = 1e6; - if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { - qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; + int32_t numMeshes = (int32_t)(meshes.size()); + + const int MAX_ALLOWED_MESH_COUNT = 1000; + if (numMeshes > MAX_ALLOWED_MESH_COUNT) { + // too many will cause the deadlock timer to throw... shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); return; } @@ -509,118 +518,169 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); pointCollection.clear(); if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - pointCollection.resize(numInstances); + pointCollection.resize(numMeshes); } else { pointCollection.resize(1); } - for (uint32_t meshIndex = 0; meshIndex < hfmModel.meshes.size(); ++meshIndex) { - const auto& mesh = hfmModel.meshes[meshIndex]; - const auto& triangleListMesh = mesh.triangleListMesh; - const auto& vertices = triangleListMesh.vertices; - const auto& indices = triangleListMesh.indices; - const std::vector& parts = triangleListMesh.parts; + ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); + triangleIndices.clear(); - const auto& shapesPerInstance = shapesPerInstancePerMesh[meshIndex]; - for (const std::vector& instanceShapes : shapesPerInstance) { - ShapeInfo::PointList& points = pointCollection[instanceIndex]; + Extents extents; + int32_t meshCount = 0; + int32_t pointListIndex = 0; + for (auto& mesh : meshes) { + if (!mesh) { + continue; + } + const gpu::BufferView& vertices = mesh->getVertexBuffer(); + const gpu::BufferView& indices = mesh->getIndexBuffer(); + const gpu::BufferView& parts = mesh->getPartBuffer(); - // reserve room - int32_t sizeToReserve = (int32_t)(vertices.size()); - if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - // a list of points for each instance - instanceIndex++; - } else { - // only one list of points - sizeToReserve += (int32_t)((gpu::Size)points.size()); - } - points.reserve(sizeToReserve); - - // get mesh instance transform - const uint32_t meshIndexOffset = (uint32_t)points.size(); - const uint32_t instanceShapeIndexForTransform = instanceShapes.front(); - const auto& instanceShapeForTransform = hfmModel.shapes[instanceShapeIndexForTransform]; - glm::mat4 localTransform; - if (instanceShapeForTransform.joint != hfm::UNDEFINED_KEY) { - auto jointMatrix = model->getRig().getJointTransform(instanceShapeForTransform.joint); - // we backtranslate by the registration offset so we can apply that offset to the shapeInfo later - if (instanceShapeForTransform.skinDeformer != hfm::UNDEFINED_KEY) { - const auto& skinDeformer = hfmModel.skinDeformers[instanceShapeForTransform.skinDeformer]; - glm::mat4 inverseBindMatrix; - if (!skinDeformer.clusters.empty()) { - const auto& cluster = skinDeformer.clusters.back(); - inverseBindMatrix = cluster.inverseBindMatrix; - } - localTransform = invRegistraionOffset * jointMatrix * inverseBindMatrix; - } else { - localTransform = invRegistraionOffset * jointMatrix; - } - } else { - localTransform = invRegistraionOffset; - } + ShapeInfo::PointList& points = pointCollection[pointListIndex]; - // copy points - auto vertexItr = vertices.cbegin(); - while (vertexItr != vertices.cend()) { - glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr)); - points.push_back(point); - ++vertexItr; - } - for (const auto& instanceShapeIndex : instanceShapes) { - const auto& instanceShape = hfmModel.shapes[instanceShapeIndex]; - extents.addExtents(instanceShape.transformedExtents); - } + // reserve room + int32_t sizeToReserve = (int32_t)(vertices.getNumElements()); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // a list of points for each mesh + pointListIndex++; + } else { + // only one list of points + sizeToReserve += (int32_t)((gpu::Size)points.size()); + } + points.reserve(sizeToReserve); - if (type == SHAPE_TYPE_STATIC_MESH) { - // copy into triangleIndices - triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.size())); - auto partItr = parts.cbegin(); - while (partItr != parts.cend()) { - auto numIndices = partItr->y; + // copy points + uint32_t meshIndexOffset = (uint32_t)points.size(); + const glm::mat4& localTransform = localTransforms[meshCount]; + gpu::BufferView::Iterator vertexItr = vertices.cbegin(); + while (vertexItr != vertices.cend()) { + glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr)); + points.push_back(point); + extents.addPoint(point); + ++vertexItr; + } + + if (type == SHAPE_TYPE_STATIC_MESH) { + // copy into triangleIndices + triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements())); + gpu::BufferView::Iterator partItr = parts.cbegin(); + while (partItr != parts.cend()) { + auto numIndices = partItr->_numIndices; + if (partItr->_topology == graphics::Mesh::TRIANGLES) { // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - auto indexItr = indices.cbegin() + partItr->x; + + auto indexItr = indices.cbegin() + partItr->_startIndex; auto indexEnd = indexItr + numIndices; while (indexItr != indexEnd) { triangleIndices.push_back(*indexItr + meshIndexOffset); ++indexItr; } - ++partItr; + } else if (partItr->_topology == graphics::Mesh::TRIANGLE_STRIP) { + // TODO: resurrect assert after we start sanitizing HFMMesh higher up + //assert(numIndices > 2); + + uint32_t approxNumIndices = TRIANGLE_STRIDE * numIndices; + if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) { + // we underestimated the final size of triangleIndices so we pre-emptively expand it + triangleIndices.reserve(triangleIndices.size() + approxNumIndices); + } + + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + (numIndices - 2); + + // first triangle uses the first three indices + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + + // the rest use previous and next index + uint32_t triangleCount = 1; + while (indexItr != indexEnd) { + if ((*indexItr) != graphics::Mesh::PRIMITIVE_RESTART_INDEX) { + if (triangleCount % 2 == 0) { + // even triangles use first two indices in order + triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); + triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); + } else { + // odd triangles swap order of first two indices + triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); + triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); + } + triangleIndices.push_back(*indexItr + meshIndexOffset); + ++triangleCount; + } + ++indexItr; + } } - } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - // for each mesh copy unique part indices, separated by special bogus (flag) index values - auto partItr = parts.cbegin(); - while (partItr != parts.cend()) { - // collect unique list of indices for this part - std::set uniqueIndices; - auto numIndices = partItr->y; + ++partItr; + } + } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // for each mesh copy unique part indices, separated by special bogus (flag) index values + gpu::BufferView::Iterator partItr = parts.cbegin(); + while (partItr != parts.cend()) { + // collect unique list of indices for this part + std::set uniqueIndices; + auto numIndices = partItr->_numIndices; + if (partItr->_topology == graphics::Mesh::TRIANGLES) { // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices% TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - auto indexItr = indices.cbegin() + partItr->x; + + auto indexItr = indices.cbegin() + partItr->_startIndex; auto indexEnd = indexItr + numIndices; while (indexItr != indexEnd) { uniqueIndices.insert(*indexItr); ++indexItr; } + } else if (partItr->_topology == graphics::Mesh::TRIANGLE_STRIP) { + // TODO: resurrect assert after we start sanitizing HFMMesh higher up + //assert(numIndices > TRIANGLE_STRIDE - 1); - // store uniqueIndices in triangleIndices - triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); - for (auto index : uniqueIndices) { - triangleIndices.push_back(index); + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + (numIndices - 2); + + // first triangle uses the first three indices + uniqueIndices.insert(*(indexItr++)); + uniqueIndices.insert(*(indexItr++)); + uniqueIndices.insert(*(indexItr++)); + + // the rest use previous and next index + uint32_t triangleCount = 1; + while (indexItr != indexEnd) { + if ((*indexItr) != graphics::Mesh::PRIMITIVE_RESTART_INDEX) { + if (triangleCount % 2 == 0) { + // EVEN triangles use first two indices in order + uniqueIndices.insert(*(indexItr - 2)); + uniqueIndices.insert(*(indexItr - 1)); + } else { + // ODD triangles swap order of first two indices + uniqueIndices.insert(*(indexItr - 1)); + uniqueIndices.insert(*(indexItr - 2)); + } + uniqueIndices.insert(*indexItr); + ++triangleCount; + } + ++indexItr; } - // flag end of part - triangleIndices.push_back(END_OF_MESH_PART); - - ++partItr; } - // flag end of mesh - triangleIndices.push_back(END_OF_MESH); - } - } - ++shapeCount; + // store uniqueIndices in triangleIndices + triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); + for (auto index : uniqueIndices) { + triangleIndices.push_back(index); + } + // flag end of part + triangleIndices.push_back(END_OF_MESH_PART); + + ++partItr; + } + // flag end of mesh + triangleIndices.push_back(END_OF_MESH); + } + ++meshCount; } // scale and shift @@ -632,7 +692,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { } } for (auto points : pointCollection) { - for (size_t i = 0; i < points.size(); ++i) { + for (int32_t i = 0; i < points.size(); ++i) { points[i] = (points[i] * scaleToFit); } } @@ -1391,7 +1451,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } } - if (!_texturesLoaded && model->getNetworkModel() && model->getNetworkModel()->areTexturesLoaded()) { + if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { withWriteLock([&] { _texturesLoaded = true; }); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 0634b6ec59..f42f0aec94 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -121,7 +121,7 @@ private: bool readyToAnimate() const; void fetchCollisionGeometryResource(); - ModelResource::Pointer _collisionGeometryResource; + GeometryResource::Pointer _collisionGeometryResource; std::vector _jointMap; QVariantMap _originalTextures; bool _jointMapCompleted { false }; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 2c344f663a..f34eb85230 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -200,7 +200,7 @@ float importanceSample3DDimension(float startDim) { } ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, - const ShapeType& shapeType, const ModelResource::Pointer& geometryResource, + const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, const TriangleInfo& triangleInfo) { CpuParticle particle; @@ -385,7 +385,7 @@ void ParticleEffectEntityRenderer::stepSimulation() { particle::Properties particleProperties; ShapeType shapeType; - ModelResource::Pointer geometryResource; + GeometryResource::Pointer geometryResource; withReadLock([&] { particleProperties = _particleProperties; shapeType = _shapeType; @@ -488,7 +488,7 @@ void ParticleEffectEntityRenderer::fetchGeometryResource() { if (hullURL.isEmpty()) { _geometryResource.reset(); } else { - _geometryResource = DependencyManager::get()->getCollisionModelResource(hullURL); + _geometryResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); } } @@ -496,7 +496,7 @@ void ParticleEffectEntityRenderer::fetchGeometryResource() { void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) { PROFILE_RANGE(render, __FUNCTION__); - uint32_t numberOfMeshes = (uint32_t)hfmModel.meshes.size(); + int numberOfMeshes = hfmModel.meshes.size(); _hasComputedTriangles = true; _triangleInfo.triangles.clear(); @@ -506,11 +506,11 @@ void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) float minArea = FLT_MAX; AABox bounds; - for (uint32_t i = 0; i < numberOfMeshes; i++) { + for (int i = 0; i < numberOfMeshes; i++) { const HFMMesh& mesh = hfmModel.meshes.at(i); - const uint32_t numberOfParts = (uint32_t)mesh.parts.size(); - for (uint32_t j = 0; j < numberOfParts; j++) { + const int numberOfParts = mesh.parts.size(); + for (int j = 0; j < numberOfParts; j++) { const HFMMeshPart& part = mesh.parts.at(j); const int INDICES_PER_TRIANGLE = 3; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index d585104f5c..cc907f2b1d 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -89,7 +89,7 @@ private: } _triangleInfo; static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, - const ShapeType& shapeType, const ModelResource::Pointer& geometryResource, + const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, const TriangleInfo& triangleInfo); void stepSimulation(); @@ -108,7 +108,7 @@ private: QString _compoundShapeURL; void fetchGeometryResource(); - ModelResource::Pointer _geometryResource; + GeometryResource::Pointer _geometryResource; NetworkTexturePointer _networkTexture; ScenePointer _scene; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 5370c17275..1555604604 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1429,13 +1429,14 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { auto polyVoxEntity = std::static_pointer_cast(entity); - ShapeInfo::PointCollection pointCollection; + QVector> pointCollection; AABox box; glm::mat4 vtoM = std::static_pointer_cast(entity)->voxelToLocalMatrix(); if (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { // pull each triangle in the mesh into a polyhedron which can be collided with + unsigned int i = 0; const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); @@ -1464,16 +1465,19 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { box += p2Model; box += p3Model; - ShapeInfo::PointList pointsInPart; - pointsInPart.push_back(p0Model); - pointsInPart.push_back(p1Model); - pointsInPart.push_back(p2Model); - pointsInPart.push_back(p3Model); - - // add points to a new convex hull - pointCollection.push_back(pointsInPart); + QVector pointsInPart; + pointsInPart << p0Model; + pointsInPart << p1Model; + pointsInPart << p2Model; + pointsInPart << p3Model; + // add next convex hull + QVector newMeshPoints; + pointCollection << newMeshPoints; + // add points to the new convex hull + pointCollection[i++] << pointsInPart; } } else { + unsigned int i = 0; polyVoxEntity->forEachVoxelValue(voxelVolumeSize, [&](const ivec3& v, uint8_t value) { if (value > 0) { const auto& x = v.x; @@ -1492,7 +1496,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { return; } - ShapeInfo::PointList pointsInPart; + QVector pointsInPart; float offL = -0.5f; float offH = 0.5f; @@ -1519,17 +1523,20 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { box += p110; box += p111; - pointsInPart.push_back(p000); - pointsInPart.push_back(p001); - pointsInPart.push_back(p010); - pointsInPart.push_back(p011); - pointsInPart.push_back(p100); - pointsInPart.push_back(p101); - pointsInPart.push_back(p110); - pointsInPart.push_back(p111); + pointsInPart << p000; + pointsInPart << p001; + pointsInPart << p010; + pointsInPart << p011; + pointsInPart << p100; + pointsInPart << p101; + pointsInPart << p110; + pointsInPart << p111; - // add points to a new convex hull - pointCollection.push_back(pointsInPart); + // add next convex hull + QVector newMeshPoints; + pointCollection << newMeshPoints; + // add points to the new convex hull + pointCollection[i++] << pointsInPart; } }); } @@ -1539,7 +1546,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection pointCollection, AABox box) { // this catches the payload from computeShapeInfoWorker - if (pointCollection.empty()) { + if (pointCollection.isEmpty()) { EntityItem::computeShapeInfo(_shapeInfo); withWriteLock([&] { _shapeReady = true; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 1ddc972029..898a6d4b98 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -350,7 +350,7 @@ bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c } bool ZoneEntityItem::contains(const glm::vec3& point) const { - ModelResource::Pointer resource = _shapeResource; + GeometryResource::Pointer resource = _shapeResource; if (_shapeType == SHAPE_TYPE_COMPOUND && resource) { if (resource->isLoaded()) { const HFMModel& hfmModel = resource->getHFMModel(); @@ -467,7 +467,7 @@ void ZoneEntityItem::fetchCollisionGeometryResource() { if (hullURL.isEmpty()) { _shapeResource.reset(); } else { - _shapeResource = DependencyManager::get()->getCollisionModelResource(hullURL); + _shapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); } } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 295727d657..8ec3ea12b4 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -173,7 +173,7 @@ protected: static bool _zonesArePickable; void fetchCollisionGeometryResource(); - ModelResource::Pointer _shapeResource; + GeometryResource::Pointer _shapeResource; }; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index d7c05e29de..100f6ee98e 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -20,7 +20,6 @@ #include #include -#include // TOOL: Uncomment the following line to enable the filtering of all the unkwnon fields of a node so we can break point easily while loading a model with problems... //#define DEBUG_FBXSERIALIZER @@ -146,9 +145,8 @@ public: bool isLimbNode; // is this FBXModel transform is a "LimbNode" i.e. a joint }; - glm::mat4 getGlobalTransform(const QMultiMap& _connectionParentMap, - const QHash& fbxModels, QString nodeID, bool mixamoHack, const QString& url) { + const QHash& fbxModels, QString nodeID, bool mixamoHack, const QString& url) { glm::mat4 globalTransform; QVector visitedNodes; // Used to prevent following a cycle while (!nodeID.isNull()) { @@ -168,11 +166,12 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen } QList parentIDs = _connectionParentMap.values(nodeID); nodeID = QString(); - foreach(const QString& parentID, parentIDs) { + foreach (const QString& parentID, parentIDs) { if (visitedNodes.contains(parentID)) { qCWarning(modelformat) << "Ignoring loop detected in FBX connection map for" << url; continue; } + if (fbxModels.contains(parentID)) { nodeID = parentID; break; @@ -182,21 +181,6 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen return globalTransform; } -std::vector getModelIDsForMeshID(const QString& meshID, const QHash& fbxModels, const QMultiMap& _connectionParentMap) { - std::vector modelsForMesh; - if (fbxModels.contains(meshID)) { - modelsForMesh.push_back(meshID); - } else { - // This mesh may have more than one parent model, with different material and transform, representing a different instance of the mesh - for (const auto& parentID : _connectionParentMap.values(meshID)) { - if (fbxModels.contains(parentID)) { - modelsForMesh.push_back(parentID); - } - } - } - return modelsForMesh; -} - class ExtractedBlendshape { public: QString id; @@ -420,7 +404,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const QVector blendshapes; QHash fbxModels; - QHash fbxClusters; + QHash clusters; QHash animationCurves; QHash typeFlags; @@ -531,8 +515,8 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const if (object.properties.at(2) == "Mesh") { meshes.insert(getID(object.properties), extractMesh(object, meshIndex, deduplicateIndices)); } else { // object.properties.at(2) == "Shape" - ExtractedBlendshape blendshape = { getID(object.properties), extractBlendshape(object) }; - blendshapes.append(blendshape); + ExtractedBlendshape extracted = { getID(object.properties), extractBlendshape(object) }; + blendshapes.append(extracted); } } else if (object.name == "Model") { QString name = getModelName(object.properties); @@ -706,8 +690,8 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const // add the blendshapes included in the model, if any if (mesh) { - foreach (const ExtractedBlendshape& blendshape, blendshapes) { - addBlendshapes(blendshape, blendshapeIndices.values(blendshape.id.toLatin1()), *mesh); + foreach (const ExtractedBlendshape& extracted, blendshapes) { + addBlendshapes(extracted, blendshapeIndices.values(extracted.id.toLatin1()), *mesh); } } @@ -1074,9 +1058,9 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } } - // skip empty fbxClusters + // skip empty clusters if (cluster.indices.size() > 0 && cluster.weights.size() > 0) { - fbxClusters.insert(getID(object.properties), cluster); + clusters.insert(getID(object.properties), cluster); } } else if (object.properties.last() == "BlendShapeChannel") { @@ -1230,11 +1214,11 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } // assign the blendshapes to their corresponding meshes - foreach (const ExtractedBlendshape& blendshape, blendshapes) { - QString blendshapeChannelID = _connectionParentMap.value(blendshape.id); + foreach (const ExtractedBlendshape& extracted, blendshapes) { + QString blendshapeChannelID = _connectionParentMap.value(extracted.id); QString blendshapeID = _connectionParentMap.value(blendshapeChannelID); QString meshID = _connectionParentMap.value(blendshapeID); - addBlendshapes(blendshape, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]); + addBlendshapes(extracted, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]); } // get offset transform from mapping @@ -1249,13 +1233,13 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const QVector modelIDs; QSet remainingFBXModels; for (QHash::const_iterator fbxModel = fbxModels.constBegin(); fbxModel != fbxModels.constEnd(); fbxModel++) { - // models with fbxClusters must be parented to the cluster top + // models with clusters must be parented to the cluster top // Unless the model is a root node. bool isARootNode = !modelIDs.contains(_connectionParentMap.value(fbxModel.key())); if (!isARootNode) { foreach(const QString& deformerID, _connectionChildMap.values(fbxModel.key())) { foreach(const QString& clusterID, _connectionChildMap.values(deformerID)) { - if (!fbxClusters.contains(clusterID)) { + if (!clusters.contains(clusterID)) { continue; } QString topID = getTopModelID(_connectionParentMap, fbxModels, _connectionChildMap.value(clusterID), url); @@ -1299,18 +1283,12 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const // convert the models to joints hfmModel.hasSkeletonJoints = false; - - bool needMixamoHack = hfmModel.applicationName == "mixamo.com"; - std::vector transformForClusters; - transformForClusters.reserve((size_t)modelIDs.size()); - for (const QString& modelID : modelIDs) { + foreach (const QString& modelID, modelIDs) { const FBXModel& fbxModel = fbxModels[modelID]; HFMJoint joint; joint.parentIndex = fbxModel.parentIndex; - uint32_t jointIndex = (uint32_t)hfmModel.joints.size(); - - // Copy default joint parameters from model + int jointIndex = hfmModel.joints.size(); joint.translation = fbxModel.translation; // these are usually in centimeters joint.preTransform = fbxModel.preTransform; @@ -1321,62 +1299,35 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const joint.rotationMin = fbxModel.rotationMin; joint.rotationMax = fbxModel.rotationMax; - if (fbxModel.hasGeometricOffset) { - joint.geometricOffset = createMatFromScaleQuatAndPos(fbxModel.geometricScaling, fbxModel.geometricRotation, fbxModel.geometricTranslation); - } + joint.hasGeometricOffset = fbxModel.hasGeometricOffset; + joint.geometricTranslation = fbxModel.geometricTranslation; + joint.geometricRotation = fbxModel.geometricRotation; + joint.geometricScaling = fbxModel.geometricScaling; joint.isSkeletonJoint = fbxModel.isLimbNode; hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint); - - joint.name = fbxModel.name; - - joint.bindTransformFoundInCluster = false; - - // With the basic joint information, we can start to calculate compound transform information - // modelIDs is ordered from parent to children, so we can safely get parent transforms from earlier joints as we iterate - - // Make adjustments to the static joint properties, and pre-calculate static transforms - if (applyUpAxisZRotation && joint.parentIndex == -1) { joint.rotation *= upAxisZRotation; joint.translation = upAxisZRotation * joint.translation; } - glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - joint.localTransform = glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; - if (joint.parentIndex == -1) { - joint.transform = joint.localTransform; - joint.globalTransform = hfmModel.offset * joint.localTransform; + joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform * + glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation); joint.distanceToParent = 0.0f; + } else { const HFMJoint& parentJoint = hfmModel.joints.at(joint.parentIndex); - joint.transform = parentJoint.transform * joint.localTransform; - joint.globalTransform = parentJoint.globalTransform * joint.localTransform; + joint.transform = parentJoint.transform * glm::translate(joint.translation) * + joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation) * parentJoint.inverseDefaultRotation; - joint.distanceToParent = glm::distance(extractTranslation(parentJoint.transform), extractTranslation(joint.transform)); + joint.distanceToParent = glm::distance(extractTranslation(parentJoint.transform), + extractTranslation(joint.transform)); } joint.inverseBindRotation = joint.inverseDefaultRotation; + joint.name = fbxModel.name; - // If needed, separately calculate the FBX-specific transform used for inverse bind transform calculations - - glm::mat4 transformForCluster; - if (applyUpAxisZRotation) { - const glm::quat jointBindCombinedRotation = fbxModel.preRotation * fbxModel.rotation * fbxModel.postRotation; - const glm::mat4 localTransformForCluster = glm::translate(fbxModel.translation) * fbxModel.preTransform * glm::mat4_cast(jointBindCombinedRotation) * fbxModel.postTransform; - if (fbxModel.parentIndex != -1 && fbxModel.parentIndex < (int)jointIndex && !needMixamoHack) { - const glm::mat4& parenttransformForCluster = transformForClusters[fbxModel.parentIndex]; - transformForCluster = parenttransformForCluster * localTransformForCluster; - } else { - transformForCluster = localTransformForCluster; - } - } else { - transformForCluster = joint.transform; - } - transformForClusters.push_back(transformForCluster); - - // Initialize animation information next - // And also get the joint poses from the first frame of the animation, if present + joint.bindTransformFoundInCluster = false; QString rotationID = localRotations.value(modelID); AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); @@ -1404,11 +1355,14 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const joint.translation = hfmModel.animationFrames[i].translations[jointIndex]; joint.rotation = hfmModel.animationFrames[i].rotations[jointIndex]; } - } - hfmModel.joints.push_back(joint); + } + hfmModel.joints.append(joint); } + // NOTE: shapeVertices are in joint-frame + hfmModel.shapeVertices.resize(std::max(1, hfmModel.joints.size()) ); + hfmModel.bindExtents.reset(); hfmModel.meshExtents.reset(); @@ -1446,202 +1400,233 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } } #endif - - std::unordered_map materialNameToID; - for (auto materialIt = _hfmMaterials.cbegin(); materialIt != _hfmMaterials.cend(); ++materialIt) { - materialNameToID[materialIt.key().toStdString()] = (uint32_t)hfmModel.materials.size(); - hfmModel.materials.push_back(materialIt.value()); - } + hfmModel.materials = _hfmMaterials; // see if any materials have texture children bool materialsHaveTextures = checkMaterialsHaveTextures(_hfmMaterials, _textureFilenames, _connectionChildMap); for (QMap::iterator it = meshes.begin(); it != meshes.end(); it++) { - const QString& meshID = it.key(); - const ExtractedMesh& extracted = it.value(); - const auto& partMaterialTextures = extracted.partMaterialTextures; + ExtractedMesh& extracted = it.value(); - uint32_t meshIndex = (uint32_t)hfmModel.meshes.size(); - meshIDsToMeshIndices.insert(meshID, meshIndex); - hfmModel.meshes.push_back(extracted.mesh); - hfm::Mesh& mesh = hfmModel.meshes.back(); + extracted.mesh.meshExtents.reset(); - std::vector instanceModelIDs = getModelIDsForMeshID(meshID, fbxModels, _connectionParentMap); - // meshShapes will be added to hfmModel at the very end - std::vector meshShapes; - meshShapes.reserve(instanceModelIDs.size() * mesh.parts.size()); - for (const QString& modelID : instanceModelIDs) { - // The transform node has the same indexing order as the joints - int indexOfModelID = modelIDs.indexOf(modelID); - if (indexOfModelID == -1) { - qCDebug(modelformat) << "Model not in model list: " << modelID; - } - const uint32_t transformIndex = (indexOfModelID == -1) ? 0 : (uint32_t)indexOfModelID; + // accumulate local transforms + QString modelID = fbxModels.contains(it.key()) ? it.key() : _connectionParentMap.value(it.key()); + glm::mat4 modelTransform = getGlobalTransform(_connectionParentMap, fbxModels, modelID, hfmModel.applicationName == "mixamo.com", url); - // partShapes will be added to meshShapes at the very end - std::vector partShapes { mesh.parts.size() }; - for (uint32_t i = 0; i < (uint32_t)partShapes.size(); ++i) { - hfm::Shape& shape = partShapes[i]; - shape.mesh = meshIndex; - shape.meshPart = i; - shape.joint = transformIndex; - } + // compute the mesh extents from the transformed vertices + foreach (const glm::vec3& vertex, extracted.mesh.vertices) { + glm::vec3 transformedVertex = glm::vec3(modelTransform * glm::vec4(vertex, 1.0f)); + hfmModel.meshExtents.minimum = glm::min(hfmModel.meshExtents.minimum, transformedVertex); + hfmModel.meshExtents.maximum = glm::max(hfmModel.meshExtents.maximum, transformedVertex); - // For FBX_DRACO_MESH_VERSION < 2, or unbaked models, get materials from the partMaterialTextures - if (!partMaterialTextures.empty()) { - int materialIndex = 0; - int textureIndex = 0; - QList children = _connectionChildMap.values(modelID); - for (int i = children.size() - 1; i >= 0; i--) { - const QString& childID = children.at(i); - if (_hfmMaterials.contains(childID)) { - // the pure material associated with this part - const HFMMaterial& material = _hfmMaterials.value(childID); - for (int j = 0; j < partMaterialTextures.size(); j++) { - if (partMaterialTextures.at(j).first == materialIndex) { - hfm::Shape& shape = partShapes[j]; - shape.material = materialNameToID[material.materialID.toStdString()]; - } - } - materialIndex++; - } else if (_textureFilenames.contains(childID)) { - // NOTE (Sabrina 2019/01/11): getTextures now takes in the materialID as a second parameter, because FBX material nodes can sometimes have uv transform information (ex: "Maya|uv_scale") - // I'm leaving the second parameter blank right now as this code may never be used. - HFMTexture texture = getTexture(childID, ""); - for (int j = 0; j < partMaterialTextures.size(); j++) { - int partTexture = partMaterialTextures.at(j).second; - if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { - // TODO: DO something here that replaces this legacy code - // Maybe create a material just for this part with the correct textures? - // material.albedoTexture = texture; - // partShapes[j].material = materialIndex; - } - } - textureIndex++; - } - } - } - // For baked models with FBX_DRACO_MESH_VERSION >= 2, get materials from extracted.materialIDPerMeshPart - if (!extracted.materialIDPerMeshPart.empty()) { - assert(partShapes.size() == extracted.materialIDPerMeshPart.size()); - for (uint32_t i = 0; i < (uint32_t)extracted.materialIDPerMeshPart.size(); ++i) { - hfm::Shape& shape = partShapes[i]; - const std::string& materialID = extracted.materialIDPerMeshPart[i]; - auto materialIt = materialNameToID.find(materialID); - if (materialIt != materialNameToID.end()) { - shape.material = materialIt->second; - } - } - } - - // find the clusters with which the mesh is associated - QVector clusterIDs; - for (const QString& childID : _connectionChildMap.values(meshID)) { - for (const QString& clusterID : _connectionChildMap.values(childID)) { - if (!fbxClusters.contains(clusterID)) { - continue; - } - clusterIDs.append(clusterID); - } - } - - // whether we're skinned depends on how many clusters are attached - if (clusterIDs.size() > 0) { - hfm::SkinDeformer skinDeformer; - auto& clusters = skinDeformer.clusters; - for (const auto& clusterID : clusterIDs) { - HFMCluster hfmCluster; - const Cluster& fbxCluster = fbxClusters[clusterID]; - - // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion - // of skinning information in FBX - QString jointID = _connectionChildMap.value(clusterID); - int indexOfJointID = modelIDs.indexOf(jointID); - if (indexOfJointID == -1) { - qCDebug(modelformat) << "Joint not in model list: " << jointID; - hfmCluster.jointIndex = 0; - } else { - hfmCluster.jointIndex = (uint32_t)indexOfJointID; - } - - const glm::mat4& transformForCluster = transformForClusters[transformIndex]; - hfmCluster.inverseBindMatrix = glm::inverse(fbxCluster.transformLink) * transformForCluster; - - // slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and - // sometimes floating point fuzz can be introduced after the inverse. - hfmCluster.inverseBindMatrix[0][3] = 0.0f; - hfmCluster.inverseBindMatrix[1][3] = 0.0f; - hfmCluster.inverseBindMatrix[2][3] = 0.0f; - hfmCluster.inverseBindMatrix[3][3] = 1.0f; - - hfmCluster.inverseBindTransform = Transform(hfmCluster.inverseBindMatrix); - - clusters.push_back(hfmCluster); - - // override the bind rotation with the transform link - HFMJoint& joint = hfmModel.joints[hfmCluster.jointIndex]; - joint.inverseBindRotation = glm::inverse(extractRotation(fbxCluster.transformLink)); - joint.bindTransform = fbxCluster.transformLink; - joint.bindTransformFoundInCluster = true; - - // update the bind pose extents - glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * joint.bindTransform); - hfmModel.bindExtents.addPoint(bindTranslation); - } - - // the last cluster is the root cluster - HFMCluster cluster; - cluster.jointIndex = transformIndex; - clusters.push_back(cluster); - - // Skinned mesh instances have an hfm::SkinDeformer - std::vector skinClusters; - for (const auto& clusterID : clusterIDs) { - const Cluster& fbxCluster = fbxClusters[clusterID]; - skinClusters.emplace_back(); - hfm::SkinCluster& skinCluster = skinClusters.back(); - size_t indexWeightPairs = (size_t)std::min(fbxCluster.indices.size(), fbxCluster.weights.size()); - skinCluster.indices.reserve(indexWeightPairs); - skinCluster.weights.reserve(indexWeightPairs); - - for (int j = 0; j < fbxCluster.indices.size(); j++) { - int oldIndex = fbxCluster.indices.at(j); - float weight = fbxCluster.weights.at(j); - for (QMultiHash::const_iterator it = extracted.newIndices.constFind(oldIndex); - it != extracted.newIndices.end() && it.key() == oldIndex; it++) { - int newIndex = it.value(); - - skinCluster.indices.push_back(newIndex); - skinCluster.weights.push_back(weight); - } - } - } - // It seems odd that this mesh-related code should be inside of the for loop for instanced model IDs. - // However, in practice, skinned FBX models appear to not be instanced, as the skinning includes both the weights and joints. - { - hfm::ReweightedDeformers reweightedDeformers = hfm::getReweightedDeformers(mesh.vertices.size(), skinClusters); - if (reweightedDeformers.trimmedToMatch) { - qDebug(modelformat) << "FBXSerializer -- The number of indices and weights for a skinning deformer had different sizes and have been trimmed to match"; - } - mesh.clusterIndices = std::move(reweightedDeformers.indices); - mesh.clusterWeights = std::move(reweightedDeformers.weights); - mesh.clusterWeightsPerVertex = reweightedDeformers.weightsPerVertex; - } - - // Store the model's dynamic transform, and put its ID in the shapes - uint32_t skinDeformerID = (uint32_t)hfmModel.skinDeformers.size(); - hfmModel.skinDeformers.push_back(skinDeformer); - for (hfm::Shape& shape : partShapes) { - shape.skinDeformer = skinDeformerID; - } - } - - // Store the parts for this mesh (or instance of this mesh, as the case may be) - meshShapes.insert(meshShapes.cend(), partShapes.cbegin(), partShapes.cend()); + extracted.mesh.meshExtents.minimum = glm::min(extracted.mesh.meshExtents.minimum, transformedVertex); + extracted.mesh.meshExtents.maximum = glm::max(extracted.mesh.meshExtents.maximum, transformedVertex); + extracted.mesh.modelTransform = modelTransform; } - // Store the shapes for the mesh (or multiple instances of the mesh, as the case may be) - hfmModel.shapes.insert(hfmModel.shapes.cend(), meshShapes.cbegin(), meshShapes.cend()); + // look for textures, material properties + // allocate the Part material library + // NOTE: extracted.partMaterialTextures is empty for FBX_DRACO_MESH_VERSION >= 2. In that case, the mesh part's materialID string is already defined. + int materialIndex = 0; + int textureIndex = 0; + QList children = _connectionChildMap.values(modelID); + for (int i = children.size() - 1; i >= 0; i--) { + + const QString& childID = children.at(i); + if (_hfmMaterials.contains(childID)) { + // the pure material associated with this part + HFMMaterial material = _hfmMaterials.value(childID); + + for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { + if (extracted.partMaterialTextures.at(j).first == materialIndex) { + HFMMeshPart& part = extracted.mesh.parts[j]; + part.materialID = material.materialID; + } + } + + materialIndex++; + } else if (_textureFilenames.contains(childID)) { + // NOTE (Sabrina 2019/01/11): getTextures now takes in the materialID as a second parameter, because FBX material nodes can sometimes have uv transform information (ex: "Maya|uv_scale") + // I'm leaving the second parameter blank right now as this code may never be used. + HFMTexture texture = getTexture(childID, ""); + for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { + int partTexture = extracted.partMaterialTextures.at(j).second; + if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { + // TODO: DO something here that replaces this legacy code + // Maybe create a material just for this part with the correct textures? + // extracted.mesh.parts[j].diffuseTexture = texture; + } + } + textureIndex++; + } + } + + // find the clusters with which the mesh is associated + QVector clusterIDs; + foreach (const QString& childID, _connectionChildMap.values(it.key())) { + foreach (const QString& clusterID, _connectionChildMap.values(childID)) { + if (!clusters.contains(clusterID)) { + continue; + } + HFMCluster hfmCluster; + const Cluster& cluster = clusters[clusterID]; + clusterIDs.append(clusterID); + + // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion + // of skinning information in FBX + QString jointID = _connectionChildMap.value(clusterID); + hfmCluster.jointIndex = modelIDs.indexOf(jointID); + if (hfmCluster.jointIndex == -1) { + qCDebug(modelformat) << "Joint not in model list: " << jointID; + hfmCluster.jointIndex = 0; + } + + hfmCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; + + // slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and + // sometimes floating point fuzz can be introduced after the inverse. + hfmCluster.inverseBindMatrix[0][3] = 0.0f; + hfmCluster.inverseBindMatrix[1][3] = 0.0f; + hfmCluster.inverseBindMatrix[2][3] = 0.0f; + hfmCluster.inverseBindMatrix[3][3] = 1.0f; + + hfmCluster.inverseBindTransform = Transform(hfmCluster.inverseBindMatrix); + + extracted.mesh.clusters.append(hfmCluster); + + // override the bind rotation with the transform link + HFMJoint& joint = hfmModel.joints[hfmCluster.jointIndex]; + joint.inverseBindRotation = glm::inverse(extractRotation(cluster.transformLink)); + joint.bindTransform = cluster.transformLink; + joint.bindTransformFoundInCluster = true; + + // update the bind pose extents + glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * joint.bindTransform); + hfmModel.bindExtents.addPoint(bindTranslation); + } + } + + // the last cluster is the root cluster + { + HFMCluster cluster; + cluster.jointIndex = modelIDs.indexOf(modelID); + if (cluster.jointIndex == -1) { + qCDebug(modelformat) << "Model not in model list: " << modelID; + cluster.jointIndex = 0; + } + extracted.mesh.clusters.append(cluster); + } + + // whether we're skinned depends on how many clusters are attached + if (clusterIDs.size() > 1) { + // this is a multi-mesh joint + const int WEIGHTS_PER_VERTEX = 4; + int numClusterIndices = extracted.mesh.vertices.size() * WEIGHTS_PER_VERTEX; + extracted.mesh.clusterIndices.fill(extracted.mesh.clusters.size() - 1, numClusterIndices); + QVector weightAccumulators; + weightAccumulators.fill(0.0f, numClusterIndices); + + for (int i = 0; i < clusterIDs.size(); i++) { + QString clusterID = clusterIDs.at(i); + const Cluster& cluster = clusters[clusterID]; + const HFMCluster& hfmCluster = extracted.mesh.clusters.at(i); + int jointIndex = hfmCluster.jointIndex; + HFMJoint& joint = hfmModel.joints[jointIndex]; + + glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; + ShapeVertices& points = hfmModel.shapeVertices.at(jointIndex); + + for (int j = 0; j < cluster.indices.size(); j++) { + int oldIndex = cluster.indices.at(j); + float weight = cluster.weights.at(j); + for (QMultiHash::const_iterator it = extracted.newIndices.constFind(oldIndex); + it != extracted.newIndices.end() && it.key() == oldIndex; it++) { + int newIndex = it.value(); + + // remember vertices with at least 1/4 weight + // FIXME: vertices with no weightpainting won't get recorded here + const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; + if (weight >= EXPANSION_WEIGHT_THRESHOLD) { + // transform to joint-frame and save for later + const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(newIndex)); + points.push_back(extractTranslation(vertexTransform)); + } + + // look for an unused slot in the weights vector + int weightIndex = newIndex * WEIGHTS_PER_VERTEX; + int lowestIndex = -1; + float lowestWeight = FLT_MAX; + int k = 0; + for (; k < WEIGHTS_PER_VERTEX; k++) { + if (weightAccumulators[weightIndex + k] == 0.0f) { + extracted.mesh.clusterIndices[weightIndex + k] = i; + weightAccumulators[weightIndex + k] = weight; + break; + } + if (weightAccumulators[weightIndex + k] < lowestWeight) { + lowestIndex = k; + lowestWeight = weightAccumulators[weightIndex + k]; + } + } + if (k == WEIGHTS_PER_VERTEX && weight > lowestWeight) { + // no space for an additional weight; we must replace the lowest + weightAccumulators[weightIndex + lowestIndex] = weight; + extracted.mesh.clusterIndices[weightIndex + lowestIndex] = i; + } + } + } + } + + // now that we've accumulated the most relevant weights for each vertex + // normalize and compress to 16-bits + extracted.mesh.clusterWeights.fill(0, numClusterIndices); + int numVertices = extracted.mesh.vertices.size(); + for (int i = 0; i < numVertices; ++i) { + int j = i * WEIGHTS_PER_VERTEX; + + // normalize weights into uint16_t + float totalWeight = 0.0f; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + totalWeight += weightAccumulators[k]; + } + + const float ALMOST_HALF = 0.499f; + if (totalWeight > 0.0f) { + float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + extracted.mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF); + } + } else { + extracted.mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); + } + } + } else { + // this is a single-joint mesh + const HFMCluster& firstHFMCluster = extracted.mesh.clusters.at(0); + int jointIndex = firstHFMCluster.jointIndex; + HFMJoint& joint = hfmModel.joints[jointIndex]; + + // transform cluster vertices to joint-frame and save for later + glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; + ShapeVertices& points = hfmModel.shapeVertices.at(jointIndex); + foreach (const glm::vec3& vertex, extracted.mesh.vertices) { + const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertex); + points.push_back(extractTranslation(vertexTransform)); + } + + // Apply geometric offset, if present, by transforming the vertices directly + if (joint.hasGeometricOffset) { + glm::mat4 geometricOffset = createMatFromScaleQuatAndPos(joint.geometricScaling, joint.geometricRotation, joint.geometricTranslation); + for (int i = 0; i < extracted.mesh.vertices.size(); i++) { + extracted.mesh.vertices[i] = transformPoint(geometricOffset, extracted.mesh.vertices[i]); + } + } + } + + hfmModel.meshes.append(extracted.mesh); + int meshIndex = hfmModel.meshes.size() - 1; + meshIDsToMeshIndices.insert(it.key(), meshIndex); } // attempt to map any meshes to a named model @@ -1660,6 +1645,14 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } } + if (applyUpAxisZRotation) { + hfmModelPtr->meshExtents.transform(glm::mat4_cast(upAxisZRotation)); + hfmModelPtr->bindExtents.transform(glm::mat4_cast(upAxisZRotation)); + for (auto &mesh : hfmModelPtr->meshes) { + mesh.modelTransform *= glm::mat4_cast(upAxisZRotation); + mesh.meshExtents.transform(glm::mat4_cast(upAxisZRotation)); + } + } return hfmModelPtr; } diff --git a/libraries/fbx/src/FBXSerializer.h b/libraries/fbx/src/FBXSerializer.h index 2044d82710..7d41f98444 100644 --- a/libraries/fbx/src/FBXSerializer.h +++ b/libraries/fbx/src/FBXSerializer.h @@ -100,15 +100,7 @@ public: {} }; -class ExtractedMesh { -public: - hfm::Mesh mesh; - std::vector materialIDPerMeshPart; - QMultiHash newIndices; - QVector > blendshapeIndexMaps; - QVector > partMaterialTextures; - QHash texcoordSetMap; -}; +class ExtractedMesh; class FBXSerializer : public HFMSerializer { public: diff --git a/libraries/fbx/src/FBXSerializer_Mesh.cpp b/libraries/fbx/src/FBXSerializer_Mesh.cpp index e687f5e9f2..802db4b428 100644 --- a/libraries/fbx/src/FBXSerializer_Mesh.cpp +++ b/libraries/fbx/src/FBXSerializer_Mesh.cpp @@ -355,7 +355,7 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me // Check for additional metadata unsigned int dracoMeshNodeVersion = 1; - std::vector dracoMaterialList; + std::vector dracoMaterialList; for (const auto& dracoChild : child.children) { if (dracoChild.name == "FBXDracoMeshVersion") { if (!dracoChild.properties.isEmpty()) { @@ -364,7 +364,7 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me } else if (dracoChild.name == "MaterialList") { dracoMaterialList.reserve(dracoChild.properties.size()); for (const auto& materialID : dracoChild.properties) { - dracoMaterialList.push_back(materialID.toString().toStdString()); + dracoMaterialList.push_back(materialID.toString()); } } } @@ -486,20 +486,21 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me // grab or setup the HFMMeshPart for the part this face belongs to int& partIndexPlusOne = materialTextureParts[materialTexture]; if (partIndexPlusOne == 0) { - data.extracted.mesh.parts.emplace_back(); + data.extracted.mesh.parts.resize(data.extracted.mesh.parts.size() + 1); + HFMMeshPart& part = data.extracted.mesh.parts.back(); - // Figure out if this is the older way of defining the per-part material for baked FBX + // Figure out what material this part is if (dracoMeshNodeVersion >= 2) { - // Define the materialID for this mesh part index - uint16_t safeMaterialID = materialID < dracoMaterialList.size() ? materialID : 0; - data.extracted.materialIDPerMeshPart.push_back(dracoMaterialList[safeMaterialID].c_str()); + // Define the materialID now + if (materialID < dracoMaterialList.size()) { + part.materialID = dracoMaterialList[materialID]; + } } else { // Define the materialID later, based on the order of first appearance of the materials in the _connectionChildMap data.extracted.partMaterialTextures.append(materialTexture); } - // in dracoMeshNodeVersion >= 2, fbx meshes have their per-part materials already defined in data.extracted.materialIDPerMeshPart - partIndexPlusOne = (int)data.extracted.mesh.parts.size(); + partIndexPlusOne = data.extracted.mesh.parts.size(); } // give the mesh part this index @@ -534,7 +535,7 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me if (partIndex == 0) { data.extracted.partMaterialTextures.append(materialTexture); data.extracted.mesh.parts.resize(data.extracted.mesh.parts.size() + 1); - partIndex = (int)data.extracted.mesh.parts.size(); + partIndex = data.extracted.mesh.parts.size(); } HFMMeshPart& part = data.extracted.mesh.parts[partIndex - 1]; diff --git a/libraries/fbx/src/FST.cpp b/libraries/fbx/src/FST.cpp index 5f5b7cf637..b6f109c217 100644 --- a/libraries/fbx/src/FST.cpp +++ b/libraries/fbx/src/FST.cpp @@ -77,7 +77,7 @@ FST* FST::createFSTFromModel(const QString& fstPath, const QString& modelFilePat mapping.insert(JOINT_FIELD, joints); QVariantHash jointIndices; - for (size_t i = 0; i < (size_t)hfmModel.joints.size(); i++) { + for (int i = 0; i < hfmModel.joints.size(); i++) { jointIndices.insert(hfmModel.joints.at(i).name, QString::number(i)); } mapping.insert(JOINT_INDEX_FIELD, jointIndices); diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 9d9d16d733..e117e1f211 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -25,13 +25,9 @@ #include #include -#include -#include #include #include -#include - #include #include #include @@ -41,57 +37,30 @@ #include "FBXSerializer.h" -#define GLTF_GET_INDICIES(accCount) \ - int index1 = (indices[n + 0] * accCount); \ - int index2 = (indices[n + 1] * accCount); \ - int index3 = (indices[n + 2] * accCount); +#define GLTF_GET_INDICIES(accCount) int index1 = (indices[n + 0] * accCount); int index2 = (indices[n + 1] * accCount); int index3 = (indices[n + 2] * accCount); -#define GLTF_APPEND_ARRAY_1(newArray, oldArray) \ - GLTF_GET_INDICIES(1) \ - newArray.append(oldArray[index1]); \ - newArray.append(oldArray[index2]); \ - newArray.append(oldArray[index3]); +#define GLTF_APPEND_ARRAY_1(newArray, oldArray) GLTF_GET_INDICIES(1) \ +newArray.append(oldArray[index1]); \ +newArray.append(oldArray[index2]); \ +newArray.append(oldArray[index3]); -#define GLTF_APPEND_ARRAY_2(newArray, oldArray) \ - GLTF_GET_INDICIES(2) \ - newArray.append(oldArray[index1]); \ - newArray.append(oldArray[index1 + 1]); \ - newArray.append(oldArray[index2]); \ - newArray.append(oldArray[index2 + 1]); \ - newArray.append(oldArray[index3]); \ - newArray.append(oldArray[index3 + 1]); +#define GLTF_APPEND_ARRAY_2(newArray, oldArray) GLTF_GET_INDICIES(2) \ +newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); \ +newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); \ +newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); -#define GLTF_APPEND_ARRAY_3(newArray, oldArray) \ - GLTF_GET_INDICIES(3) \ - newArray.append(oldArray[index1]); \ - newArray.append(oldArray[index1 + 1]); \ - newArray.append(oldArray[index1 + 2]); \ - newArray.append(oldArray[index2]); \ - newArray.append(oldArray[index2 + 1]); \ - newArray.append(oldArray[index2 + 2]); \ - newArray.append(oldArray[index3]); \ - newArray.append(oldArray[index3 + 1]); \ - newArray.append(oldArray[index3 + 2]); +#define GLTF_APPEND_ARRAY_3(newArray, oldArray) GLTF_GET_INDICIES(3) \ +newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); newArray.append(oldArray[index1 + 2]); \ +newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); newArray.append(oldArray[index2 + 2]); \ +newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); newArray.append(oldArray[index3 + 2]); -#define GLTF_APPEND_ARRAY_4(newArray, oldArray) \ - GLTF_GET_INDICIES(4) \ - newArray.append(oldArray[index1]); \ - newArray.append(oldArray[index1 + 1]); \ - newArray.append(oldArray[index1 + 2]); \ - newArray.append(oldArray[index1 + 3]); \ - newArray.append(oldArray[index2]); \ - newArray.append(oldArray[index2 + 1]); \ - newArray.append(oldArray[index2 + 2]); \ - newArray.append(oldArray[index2 + 3]); \ - newArray.append(oldArray[index3]); \ - newArray.append(oldArray[index3 + 1]); \ - newArray.append(oldArray[index3 + 2]); \ - newArray.append(oldArray[index3 + 3]); +#define GLTF_APPEND_ARRAY_4(newArray, oldArray) GLTF_GET_INDICIES(4) \ +newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); newArray.append(oldArray[index1 + 2]); newArray.append(oldArray[index1 + 3]); \ +newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); newArray.append(oldArray[index2 + 2]); newArray.append(oldArray[index2 + 3]); \ +newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); newArray.append(oldArray[index3 + 2]); newArray.append(oldArray[index3 + 3]); -bool GLTFSerializer::getStringVal(const QJsonObject& object, - const QString& fieldname, - QString& value, - QMap& defined) { +bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname, + QString& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isString()); if (_defined) { value = object[fieldname].toString(); @@ -100,10 +69,8 @@ bool GLTFSerializer::getStringVal(const QJsonObject& object, return _defined; } -bool GLTFSerializer::getBoolVal(const QJsonObject& object, - const QString& fieldname, - bool& value, - QMap& defined) { +bool GLTFSerializer::getBoolVal(const QJsonObject& object, const QString& fieldname, + bool& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isBool()); if (_defined) { value = object[fieldname].toBool(); @@ -112,7 +79,8 @@ bool GLTFSerializer::getBoolVal(const QJsonObject& object, return _defined; } -bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldname, int& value, QMap& defined) { +bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldname, + int& value, QMap& defined) { bool _defined = (object.contains(fieldname) && !object[fieldname].isNull()); if (_defined) { value = object[fieldname].toInt(); @@ -121,10 +89,8 @@ bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldna return _defined; } -bool GLTFSerializer::getDoubleVal(const QJsonObject& object, - const QString& fieldname, - double& value, - QMap& defined) { +bool GLTFSerializer::getDoubleVal(const QJsonObject& object, const QString& fieldname, + double& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isDouble()); if (_defined) { value = object[fieldname].toDouble(); @@ -132,10 +98,8 @@ bool GLTFSerializer::getDoubleVal(const QJsonObject& object, defined.insert(fieldname, _defined); return _defined; } -bool GLTFSerializer::getObjectVal(const QJsonObject& object, - const QString& fieldname, - QJsonObject& value, - QMap& defined) { +bool GLTFSerializer::getObjectVal(const QJsonObject& object, const QString& fieldname, + QJsonObject& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isObject()); if (_defined) { value = object[fieldname].toObject(); @@ -144,14 +108,12 @@ bool GLTFSerializer::getObjectVal(const QJsonObject& object, return _defined; } -bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, - const QString& fieldname, - QVector& values, - QMap& defined) { +bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, const QString& fieldname, + QVector& values, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { QJsonArray arr = object[fieldname].toArray(); - for (const QJsonValue& v : arr) { + foreach(const QJsonValue & v, arr) { if (!v.isNull()) { values.push_back(v.toInt()); } @@ -161,14 +123,12 @@ bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, return _defined; } -bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, - const QString& fieldname, - QVector& values, - QMap& defined) { +bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, + QVector& values, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { QJsonArray arr = object[fieldname].toArray(); - for (const QJsonValue& v : arr) { + foreach(const QJsonValue & v, arr) { if (v.isDouble()) { values.push_back(v.toDouble()); } @@ -178,10 +138,8 @@ bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, return _defined; } -bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, - const QString& fieldname, - QJsonArray& objects, - QMap& defined) { +bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, + QJsonArray& objects, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { objects = object[fieldname].toArray(); @@ -191,7 +149,7 @@ bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, } hifi::ByteArray GLTFSerializer::setGLBChunks(const hifi::ByteArray& data) { - int byte = 4; + int byte = 4; int jsonStart = data.indexOf("JSON", Qt::CaseSensitive); int binStart = data.indexOf("BIN", Qt::CaseSensitive); int jsonLength, binLength; @@ -215,7 +173,8 @@ hifi::ByteArray GLTFSerializer::setGLBChunks(const hifi::ByteArray& data) { return jsonChunk; } -int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) { +int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) +{ if (type == "POINTS") { return GLTFMeshPrimitivesRenderingMode::POINTS; } @@ -240,7 +199,8 @@ int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) { return GLTFMeshPrimitivesRenderingMode::TRIANGLES; } -int GLTFSerializer::getAccessorType(const QString& type) { +int GLTFSerializer::getAccessorType(const QString& type) +{ if (type == "SCALAR") { return GLTFAccessorType::SCALAR; } @@ -278,7 +238,8 @@ graphics::MaterialKey::OpacityMapMode GLTFSerializer::getMaterialAlphaMode(const return graphics::MaterialKey::OPACITY_MAP_BLEND; } -int GLTFSerializer::getCameraType(const QString& type) { +int GLTFSerializer::getCameraType(const QString& type) +{ if (type == "orthographic") { return GLTFCameraTypes::ORTHOGRAPHIC; } @@ -288,7 +249,8 @@ int GLTFSerializer::getCameraType(const QString& type) { return GLTFCameraTypes::PERSPECTIVE; } -int GLTFSerializer::getImageMimeType(const QString& mime) { +int GLTFSerializer::getImageMimeType(const QString& mime) +{ if (mime == "image/jpeg") { return GLTFImageMimetype::JPEG; } @@ -298,7 +260,8 @@ int GLTFSerializer::getImageMimeType(const QString& mime) { return GLTFImageMimetype::JPEG; } -int GLTFSerializer::getAnimationSamplerInterpolation(const QString& interpolation) { +int GLTFSerializer::getAnimationSamplerInterpolation(const QString& interpolation) +{ if (interpolation == "LINEAR") { return GLTFAnimationSamplerInterpolation::LINEAR; } @@ -309,7 +272,8 @@ bool GLTFSerializer::setAsset(const QJsonObject& object) { QJsonObject jsAsset; bool isAssetDefined = getObjectVal(object, "asset", jsAsset, _file.defined); if (isAssetDefined) { - if (!getStringVal(jsAsset, "version", _file.asset.version, _file.asset.defined) || _file.asset.version != "2.0") { + if (!getStringVal(jsAsset, "version", _file.asset.version, + _file.asset.defined) || _file.asset.version != "2.0") { return false; } getStringVal(jsAsset, "generator", _file.asset.generator, _file.asset.defined); @@ -318,8 +282,7 @@ bool GLTFSerializer::setAsset(const QJsonObject& object) { return isAssetDefined; } -GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::createAccessorSparseIndices( - const QJsonObject& object) { +GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::createAccessorSparseIndices(const QJsonObject& object) { GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices accessorSparseIndices; getIntVal(object, "bufferView", accessorSparseIndices.bufferView, accessorSparseIndices.defined); @@ -329,8 +292,7 @@ GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::crea return accessorSparseIndices; } -GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues GLTFSerializer::createAccessorSparseValues( - const QJsonObject& object) { +GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues GLTFSerializer::createAccessorSparseValues(const QJsonObject& object) { GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues accessorSparseValues; getIntVal(object, "bufferView", accessorSparseValues.bufferView, accessorSparseValues.defined); @@ -357,7 +319,7 @@ GLTFAccessor::GLTFAccessorSparse GLTFSerializer::createAccessorSparse(const QJso bool GLTFSerializer::addAccessor(const QJsonObject& object) { GLTFAccessor accessor; - + getIntVal(object, "bufferView", accessor.bufferView, accessor.defined); getIntVal(object, "byteOffset", accessor.byteOffset, accessor.defined); getIntVal(object, "componentType", accessor.componentType, accessor.defined); @@ -383,10 +345,10 @@ bool GLTFSerializer::addAccessor(const QJsonObject& object) { bool GLTFSerializer::addAnimation(const QJsonObject& object) { GLTFAnimation animation; - + QJsonArray channels; if (getObjectArrayVal(object, "channels", channels, animation.defined)) { - for (const QJsonValue& v : channels) { + foreach(const QJsonValue & v, channels) { if (v.isObject()) { GLTFChannel channel; getIntVal(v.toObject(), "sampler", channel.sampler, channel.defined); @@ -394,14 +356,14 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { if (getObjectVal(v.toObject(), "target", jsChannel, channel.defined)) { getIntVal(jsChannel, "node", channel.target.node, channel.target.defined); getIntVal(jsChannel, "path", channel.target.path, channel.target.defined); - } + } } } } QJsonArray samplers; if (getObjectArrayVal(object, "samplers", samplers, animation.defined)) { - for (const QJsonValue& v : samplers) { + foreach(const QJsonValue & v, samplers) { if (v.isObject()) { GLTFAnimationSampler sampler; getIntVal(v.toObject(), "input", sampler.input, sampler.defined); @@ -413,7 +375,7 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { } } } - + _file.animations.push_back(animation); return true; @@ -421,20 +383,20 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { bool GLTFSerializer::addBufferView(const QJsonObject& object) { GLTFBufferView bufferview; - + getIntVal(object, "buffer", bufferview.buffer, bufferview.defined); getIntVal(object, "byteLength", bufferview.byteLength, bufferview.defined); getIntVal(object, "byteOffset", bufferview.byteOffset, bufferview.defined); getIntVal(object, "target", bufferview.target, bufferview.defined); - + _file.bufferviews.push_back(bufferview); - + return true; } bool GLTFSerializer::addBuffer(const QJsonObject& object) { GLTFBuffer buffer; - + getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); if (_url.toString().endsWith("glb")) { @@ -450,13 +412,13 @@ bool GLTFSerializer::addBuffer(const QJsonObject& object) { } } _file.buffers.push_back(buffer); - + return true; } bool GLTFSerializer::addCamera(const QJsonObject& object) { GLTFCamera camera; - + QJsonObject jsPerspective; QJsonObject jsOrthographic; QString type; @@ -476,15 +438,15 @@ bool GLTFSerializer::addCamera(const QJsonObject& object) { } else if (getStringVal(object, "type", type, camera.defined)) { camera.type = getCameraType(type); } - + _file.cameras.push_back(camera); - + return true; } bool GLTFSerializer::addImage(const QJsonObject& object) { GLTFImage image; - + QString mime; getStringVal(object, "uri", image.uri, image.defined); if (image.uri.contains("data:image/png;base64,")) { @@ -494,18 +456,16 @@ bool GLTFSerializer::addImage(const QJsonObject& object) { } if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); - } + } getIntVal(object, "bufferView", image.bufferView, image.defined); - + _file.images.push_back(image); return true; } -bool GLTFSerializer::getIndexFromObject(const QJsonObject& object, - const QString& field, - int& outidx, - QMap& defined) { +bool GLTFSerializer::getIndexFromObject(const QJsonObject& object, const QString& field, + int& outidx, QMap& defined) { QJsonObject subobject; if (getObjectVal(object, field, subobject, defined)) { QMap tmpdefined = QMap(); @@ -530,18 +490,23 @@ bool GLTFSerializer::addMaterial(const QJsonObject& object) { getDoubleVal(object, "alphaCutoff", material.alphaCutoff, material.defined); QJsonObject jsMetallicRoughness; if (getObjectVal(object, "pbrMetallicRoughness", jsMetallicRoughness, material.defined)) { - getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", material.pbrMetallicRoughness.baseColorFactor, + getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", + material.pbrMetallicRoughness.baseColorFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "baseColorTexture", material.pbrMetallicRoughness.baseColorTexture, + getIndexFromObject(jsMetallicRoughness, "baseColorTexture", + material.pbrMetallicRoughness.baseColorTexture, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "metallicFactor", material.pbrMetallicRoughness.metallicFactor, + getDoubleVal(jsMetallicRoughness, "metallicFactor", + material.pbrMetallicRoughness.metallicFactor, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "roughnessFactor", material.pbrMetallicRoughness.roughnessFactor, + getDoubleVal(jsMetallicRoughness, "roughnessFactor", + material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", - material.pbrMetallicRoughness.metallicRoughnessTexture, material.pbrMetallicRoughness.defined); + getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", + material.pbrMetallicRoughness.metallicRoughnessTexture, + material.pbrMetallicRoughness.defined); } - _file.materials.push_back(material); + _file.materials.push_back(material); return true; } @@ -553,18 +518,18 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { QJsonArray jsPrimitives; object.keys(); if (getObjectArrayVal(object, "primitives", jsPrimitives, mesh.defined)) { - for (const QJsonValue& prim : jsPrimitives) { + foreach(const QJsonValue & prim, jsPrimitives) { if (prim.isObject()) { GLTFMeshPrimitive primitive; QJsonObject jsPrimitive = prim.toObject(); getIntVal(jsPrimitive, "mode", primitive.mode, primitive.defined); getIntVal(jsPrimitive, "indices", primitive.indices, primitive.defined); getIntVal(jsPrimitive, "material", primitive.material, primitive.defined); - + QJsonObject jsAttributes; if (getObjectVal(jsPrimitive, "attributes", jsAttributes, primitive.defined)) { QStringList attrKeys = jsAttributes.keys(); - for (const QString& attrKey : attrKeys) { + foreach(const QString & attrKey, attrKeys) { int attrVal; getIntVal(jsAttributes, attrKey, attrVal, primitive.attributes.defined); primitive.attributes.values.insert(attrKey, attrVal); @@ -572,13 +537,14 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { } QJsonArray jsTargets; - if (getObjectArrayVal(jsPrimitive, "targets", jsTargets, primitive.defined)) { - for (const QJsonValue& tar : jsTargets) { + if (getObjectArrayVal(jsPrimitive, "targets", jsTargets, primitive.defined)) + { + foreach(const QJsonValue & tar, jsTargets) { if (tar.isObject()) { QJsonObject jsTarget = tar.toObject(); QStringList tarKeys = jsTarget.keys(); GLTFMeshPrimitiveAttr target; - for (const QString& tarKey : tarKeys) { + foreach(const QString & tarKey, tarKeys) { int tarVal; getIntVal(jsTarget, tarKey, tarVal, target.defined); target.values.insert(tarKey, tarVal); @@ -586,7 +552,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { primitive.targets.push_back(target); } } - } + } mesh.primitives.push_back(primitive); } } @@ -597,7 +563,9 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { if (getObjectVal(object, "extras", jsExtras, mesh.defined)) { QJsonArray jsTargetNames; if (getObjectArrayVal(jsExtras, "targetNames", jsTargetNames, extras.defined)) { - foreach (const QJsonValue& tarName, jsTargetNames) { extras.targetNames.push_back(tarName.toString()); } + foreach (const QJsonValue& tarName, jsTargetNames) { + extras.targetNames.push_back(tarName.toString()); + } } mesh.extras = extras; } @@ -609,7 +577,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { bool GLTFSerializer::addNode(const QJsonObject& object) { GLTFNode node; - + getStringVal(object, "name", node.name, node.defined); getIntVal(object, "camera", node.camera, node.defined); getIntVal(object, "mesh", node.mesh, node.defined); @@ -638,6 +606,7 @@ bool GLTFSerializer::addSampler(const QJsonObject& object) { _file.samplers.push_back(sampler); return true; + } bool GLTFSerializer::addScene(const QJsonObject& object) { @@ -663,10 +632,10 @@ bool GLTFSerializer::addSkin(const QJsonObject& object) { } bool GLTFSerializer::addTexture(const QJsonObject& object) { - GLTFTexture texture; + GLTFTexture texture; getIntVal(object, "sampler", texture.sampler, texture.defined); getIntVal(object, "source", texture.source, texture.defined); - + _file.textures.push_back(texture); return true; @@ -679,8 +648,8 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& 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(); @@ -688,7 +657,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { if (success) { QJsonArray accessors; if (getObjectArrayVal(jsFile, "accessors", accessors, _file.defined)) { - for (const QJsonValue& accVal : accessors) { + foreach(const QJsonValue & accVal, accessors) { if (accVal.isObject()) { success = success && addAccessor(accVal.toObject()); } @@ -697,7 +666,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray animations; if (getObjectArrayVal(jsFile, "animations", animations, _file.defined)) { - for (const QJsonValue& animVal : accessors) { + foreach(const QJsonValue & animVal, accessors) { if (animVal.isObject()) { success = success && addAnimation(animVal.toObject()); } @@ -706,7 +675,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray bufferViews; if (getObjectArrayVal(jsFile, "bufferViews", bufferViews, _file.defined)) { - for (const QJsonValue& bufviewVal : bufferViews) { + foreach(const QJsonValue & bufviewVal, bufferViews) { if (bufviewVal.isObject()) { success = success && addBufferView(bufviewVal.toObject()); } @@ -715,7 +684,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray buffers; if (getObjectArrayVal(jsFile, "buffers", buffers, _file.defined)) { - for (const QJsonValue& bufVal : buffers) { + foreach(const QJsonValue & bufVal, buffers) { if (bufVal.isObject()) { success = success && addBuffer(bufVal.toObject()); } @@ -724,7 +693,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray cameras; if (getObjectArrayVal(jsFile, "cameras", cameras, _file.defined)) { - for (const QJsonValue& camVal : cameras) { + foreach(const QJsonValue & camVal, cameras) { if (camVal.isObject()) { success = success && addCamera(camVal.toObject()); } @@ -733,7 +702,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray images; if (getObjectArrayVal(jsFile, "images", images, _file.defined)) { - for (const QJsonValue& imgVal : images) { + foreach(const QJsonValue & imgVal, images) { if (imgVal.isObject()) { success = success && addImage(imgVal.toObject()); } @@ -742,7 +711,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray materials; if (getObjectArrayVal(jsFile, "materials", materials, _file.defined)) { - for (const QJsonValue& matVal : materials) { + foreach(const QJsonValue & matVal, materials) { if (matVal.isObject()) { success = success && addMaterial(matVal.toObject()); } @@ -751,7 +720,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray meshes; if (getObjectArrayVal(jsFile, "meshes", meshes, _file.defined)) { - for (const QJsonValue& meshVal : meshes) { + foreach(const QJsonValue & meshVal, meshes) { if (meshVal.isObject()) { success = success && addMesh(meshVal.toObject()); } @@ -760,7 +729,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray nodes; if (getObjectArrayVal(jsFile, "nodes", nodes, _file.defined)) { - for (const QJsonValue& nodeVal : nodes) { + foreach(const QJsonValue & nodeVal, nodes) { if (nodeVal.isObject()) { success = success && addNode(nodeVal.toObject()); } @@ -769,7 +738,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray samplers; if (getObjectArrayVal(jsFile, "samplers", samplers, _file.defined)) { - for (const QJsonValue& samVal : samplers) { + foreach(const QJsonValue & samVal, samplers) { if (samVal.isObject()) { success = success && addSampler(samVal.toObject()); } @@ -778,7 +747,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray scenes; if (getObjectArrayVal(jsFile, "scenes", scenes, _file.defined)) { - for (const QJsonValue& sceneVal : scenes) { + foreach(const QJsonValue & sceneVal, scenes) { if (sceneVal.isObject()) { success = success && addScene(sceneVal.toObject()); } @@ -787,7 +756,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray skins; if (getObjectArrayVal(jsFile, "skins", skins, _file.defined)) { - for (const QJsonValue& skinVal : skins) { + foreach(const QJsonValue & skinVal, skins) { if (skinVal.isObject()) { success = success && addSkin(skinVal.toObject()); } @@ -796,22 +765,51 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray textures; if (getObjectArrayVal(jsFile, "textures", textures, _file.defined)) { - for (const QJsonValue& texVal : textures) { + foreach(const QJsonValue & texVal, textures) { if (texVal.isObject()) { success = success && addTexture(texVal.toObject()); } } } - } + } return success; } -const glm::mat4& GLTFSerializer::getModelTransform(const GLTFNode& node) { - return node.transform; +glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { + glm::mat4 tmat = glm::mat4(1.0); + + if (node.defined["matrix"] && node.matrix.size() == 16) { + tmat = glm::mat4(node.matrix[0], node.matrix[1], node.matrix[2], node.matrix[3], + node.matrix[4], node.matrix[5], node.matrix[6], node.matrix[7], + node.matrix[8], node.matrix[9], node.matrix[10], node.matrix[11], + node.matrix[12], node.matrix[13], node.matrix[14], node.matrix[15]); + } else { + + if (node.defined["scale"] && node.scale.size() == 3) { + glm::vec3 scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]); + glm::mat4 s = glm::mat4(1.0); + s = glm::scale(s, scale); + tmat = s * tmat; + } + + if (node.defined["rotation"] && node.rotation.size() == 4) { + //quat(x,y,z,w) to quat(w,x,y,z) + glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); + tmat = glm::mat4_cast(rotquat) * tmat; + } + + if (node.defined["translation"] && node.translation.size() == 3) { + glm::vec3 trans = glm::vec3(node.translation[0], node.translation[1], node.translation[2]); + glm::mat4 t = glm::mat4(1.0); + t = glm::translate(t, trans); + tmat = t * tmat; + } + } + return tmat; } void GLTFSerializer::getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues) { - for (auto& skin : _file.skins) { + for (auto &skin : _file.skins) { GLTFAccessor& indicesAccessor = _file.accessors[skin.inverseBindMatrices]; QVector matrices; addArrayFromAccessor(indicesAccessor, matrices); @@ -828,200 +826,111 @@ void GLTFSerializer::generateTargetData(int index, float weight, QVector; -ParentIndexMap findParentIndices(const QVector& nodes) { - ParentIndexMap parentIndices; - int numNodes = nodes.size(); - for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { - auto& gltfNode = nodes[nodeIndex]; - for (const auto& childIndex : gltfNode.children) { - parentIndices[childIndex] = nodeIndex; - } - } - return parentIndices; -} - -bool requiresNodeReordering(const ParentIndexMap& map) { - for (const auto& entry : map) { - if (entry.first < entry.second) { - return true; - } - } - return false; -} - -int findEdgeCount(const ParentIndexMap& parentIndices, int nodeIndex) { - auto parentsEnd = parentIndices.end(); - ParentIndexMap::const_iterator itr; - int result = 0; - while (parentsEnd != (itr = parentIndices.find(nodeIndex))) { - nodeIndex = itr->second; - ++result; - } - return result; -} - -using IndexBag = std::unordered_set; -using EdgeCountMap = std::map; -EdgeCountMap findEdgeCounts(int numNodes, const ParentIndexMap& map) { - EdgeCountMap edgeCounts; - // For each item, determine how many tranversals to a root node - for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { - // How many steps between this node and a root node? - int edgeCount = findEdgeCount(map, nodeIndex); - // Populate the result map - edgeCounts[edgeCount].insert(nodeIndex); - } - return edgeCounts; -} - -using ReorderMap = std::unordered_map; -ReorderMap buildReorderMap(const EdgeCountMap& map) { - ReorderMap result; - int newIndex = 0; - for (const auto& entry : map) { - const IndexBag& oldIndices = entry.second; - for (const auto& oldIndex : oldIndices) { - result.insert({ oldIndex, newIndex }); - ++newIndex; - } - } - return result; -} - -void reorderNodeIndices(QVector& indices, const ReorderMap& oldToNewIndexMap) { - for (auto& index : indices) { - index = oldToNewIndexMap.at(index); - } -} - -} // namespace gltf - -void GLTFFile::populateMaterialNames() { - // Build material names - QSet usedNames; - for (const auto& material : materials) { - if (!material.name.isEmpty()) { - usedNames.insert(material.name); - } - } - - int ukcount = 0; - const QString unknown{ "Default_%1" }; - for (auto& material : materials) { - QString generatedName = unknown.arg(ukcount++); - while (usedNames.contains(generatedName)) { - generatedName = unknown.arg(ukcount++); - } - material.name = generatedName; - material.defined.insert("name", true); - usedNames.insert(generatedName); - } -} - -void GLTFFile::reorderNodes(const std::unordered_map& oldToNewIndexMap) { - int numNodes = nodes.size(); - assert(numNodes == oldToNewIndexMap.size()); - QVector newNodes; - newNodes.resize(numNodes); - for (int oldIndex = 0; oldIndex < numNodes; ++oldIndex) { - const auto& oldNode = nodes[oldIndex]; - int newIndex = oldToNewIndexMap.at(oldIndex); - auto& newNode = newNodes[newIndex]; - // Write the new node - newNode = oldNode; - // Fixup the child indices - gltf::reorderNodeIndices(newNode.children, oldToNewIndexMap); - } - newNodes.swap(nodes); - - for (auto& subScene : scenes) { - gltf::reorderNodeIndices(subScene.nodes, oldToNewIndexMap); - } -} - -// Ensure that the GLTF nodes are ordered so -void GLTFFile::sortNodes() { - // Find all the parents - auto parentIndices = gltf::findParentIndices(nodes); - // If the nodes are already in a good order, we're done - if (!gltf::requiresNodeReordering(parentIndices)) { - return; - } - - auto edgeCounts = gltf::findEdgeCounts(nodes.size(), parentIndices); - auto oldToNewIndexMap = gltf::buildReorderMap(edgeCounts); - reorderNodes(oldToNewIndexMap); - assert(!gltf::requiresNodeReordering(gltf::findParentIndices(nodes))); -} - -void GLTFNode::normalizeTransform() { - if (defined["matrix"] && matrix.size() == 16) { - transform = glm::make_mat4(matrix.constData()); - } else { - transform = glm::mat4(1.0); - if (defined["scale"] && scale.size() == 3) { - glm::vec3 scaleVec = glm::make_vec3(scale.data()); - transform = glm::scale(transform, scaleVec); - } - - if (defined["rotation"] && rotation.size() == 4) { - glm::quat rotQ = glm::make_quat(rotation.data()); - transform = glm::mat4_cast(rotQ) * transform; - } - - if (defined["translation"] && translation.size() == 3) { - glm::vec3 transV = glm::make_vec3(translation.data()); - transform = glm::translate(glm::mat4(1.0), transV) * transform; - } - } -} - -void GLTFFile::normalizeNodeTransforms() { - for (auto& node : nodes) { - node.normalizeTransform(); - } -} - bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& url) { int numNodes = _file.nodes.size(); - - // Build joints - hfmModel.jointIndices["x"] = numNodes; - auto parentIndices = gltf::findParentIndices(_file.nodes); - const auto parentsEnd = parentIndices.end(); - for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { - HFMJoint joint; - auto& node = _file.nodes[nodeIndex]; - auto parentItr = parentIndices.find(nodeIndex); - if (parentsEnd == parentItr) { - joint.parentIndex = -1; - } else { - joint.parentIndex = parentItr->second; - } - joint.transform = getModelTransform(node); + //Build dependencies + QVector parents; + QVector sortedNodes; + parents.fill(-1, numNodes); + sortedNodes.reserve(numNodes); + int nodecount = 0; + foreach(auto &node, _file.nodes) { + foreach(int child, node.children) { + parents[child] = nodecount; + } + sortedNodes.push_back(nodecount); + ++nodecount; + } + + + // Build transforms + nodecount = 0; + foreach(auto &node, _file.nodes) { + // collect node transform + _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); + int parentIndex = parents[nodecount]; + while (parentIndex != -1) { + const auto& parentNode = _file.nodes[parentIndex]; + // collect transforms for a node's parents, grandparents, etc. + _file.nodes[nodecount].transforms.push_back(getModelTransform(parentNode)); + parentIndex = parents[parentIndex]; + } + ++nodecount; + } + + + // since parent indices must exist in the sorted list before any of their children, sortedNodes might not be initialized in the correct order + // therefore we need to re-initialize the order in which nodes will be parsed + QVector hasBeenSorted; + hasBeenSorted.fill(false, numNodes); + int i = 0; // initial index + while (i < numNodes) { + int currentNode = sortedNodes[i]; + int parentIndex = parents[currentNode]; + if (parentIndex == -1 || hasBeenSorted[parentIndex]) { + hasBeenSorted[currentNode] = true; + ++i; + } else { + int j = i + 1; // index of node to be sorted + while (j < numNodes) { + int nextNode = sortedNodes[j]; + parentIndex = parents[nextNode]; + if (parentIndex == -1 || hasBeenSorted[parentIndex]) { + // swap with currentNode + hasBeenSorted[nextNode] = true; + sortedNodes[i] = nextNode; + sortedNodes[j] = currentNode; + ++i; + currentNode = sortedNodes[i]; + } + ++j; + } + } + } + + + // Build map from original to new indices + QVector originalToNewNodeIndexMap; + originalToNewNodeIndexMap.fill(-1, numNodes); + for (int i = 0; i < numNodes; ++i) { + originalToNewNodeIndexMap[sortedNodes[i]] = i; + } + + + // Build joints + HFMJoint joint; + joint.distanceToParent = 0; + hfmModel.jointIndices["x"] = numNodes; + QVector globalTransforms; + globalTransforms.resize(numNodes); + + for (int nodeIndex : sortedNodes) { + auto& node = _file.nodes[nodeIndex]; + + joint.parentIndex = parents[nodeIndex]; + if (joint.parentIndex != -1) { + joint.parentIndex = originalToNewNodeIndexMap[joint.parentIndex]; + } + joint.transform = node.transforms.first(); joint.translation = extractTranslation(joint.transform); joint.rotation = glmExtractRotation(joint.transform); glm::vec3 scale = extractScale(joint.transform); joint.postTransform = glm::scale(glm::mat4(), scale); - joint.globalTransform = joint.transform; - // Nodes are sorted, so we can apply the full transform just by getting the global transform of the already defined parent - if (joint.parentIndex != -1 && joint.parentIndex != nodeIndex) { - const auto& parentJoint = hfmModel.joints[(size_t)joint.parentIndex]; - joint.transform = parentJoint.transform * joint.transform; - joint.globalTransform = joint.globalTransform * parentJoint.globalTransform; - } else { - joint.globalTransform = hfmModel.offset * joint.globalTransform; + joint.parentIndex = parents[nodeIndex]; + globalTransforms[nodeIndex] = joint.transform; + if (joint.parentIndex != -1) { + globalTransforms[nodeIndex] = globalTransforms[joint.parentIndex] * globalTransforms[nodeIndex]; + joint.parentIndex = originalToNewNodeIndexMap[joint.parentIndex]; } joint.name = node.name; joint.isSkeletonJoint = false; hfmModel.joints.push_back(joint); } + hfmModel.shapeVertices.resize(hfmModel.joints.size()); + // get offset transform from mapping float unitScaleFactor = 1.0f; @@ -1036,9 +945,6 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& std::vector globalBindTransforms; jointInverseBindTransforms.resize(numNodes); globalBindTransforms.resize(numNodes); - // Lookup between the GLTF mesh and the skin - std::vector gltfMeshToSkin; - gltfMeshToSkin.resize(_file.meshes.size(), -1); hfmModel.hasSkeletonJoints = !_file.skins.isEmpty(); if (hfmModel.hasSkeletonJoints) { @@ -1046,8 +952,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& getSkinInverseBindMatrices(inverseBindValues); for (int jointIndex = 0; jointIndex < numNodes; ++jointIndex) { - int nodeIndex = jointIndex; - auto& joint = hfmModel.joints[jointIndex]; + int nodeIndex = sortedNodes[jointIndex]; + auto joint = hfmModel.joints[jointIndex]; for (int s = 0; s < _file.skins.size(); ++s) { const auto& skin = _file.skins[s]; @@ -1073,490 +979,642 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * glm::inverse(jointInverseBindTransforms[jointIndex])); hfmModel.bindExtents.addPoint(bindTranslation); } + hfmModel.joints[jointIndex] = joint; + } + } + + + // Build materials + QVector materialIDs; + QString unknown = "Default"; + int ukcount = 0; + foreach(auto material, _file.materials) { + if (!material.defined["name"]) { + QString name = unknown + QString::number(++ukcount); + material.name = name; + material.defined.insert("name", true); } - std::vector skinToRootJoint; - skinToRootJoint.resize(_file.skins.size(), 0); - for (int jointIndex = 0; jointIndex < numNodes; ++jointIndex) { - const auto& node = _file.nodes[jointIndex]; - if (node.skin != -1) { - skinToRootJoint[node.skin] = jointIndex; - if (node.mesh != -1) { - gltfMeshToSkin[node.mesh] = node.skin; + QString mid = material.name; + materialIDs.push_back(mid); + } + + for (int i = 0; i < materialIDs.size(); ++i) { + QString& matid = materialIDs[i]; + hfmModel.materials[matid] = HFMMaterial(); + HFMMaterial& hfmMaterial = hfmModel.materials[matid]; + hfmMaterial._material = std::make_shared(); + hfmMaterial.name = hfmMaterial.materialID = matid; + setHFMMaterial(hfmMaterial, _file.materials[i]); + } + + + // Build meshes + nodecount = 0; + hfmModel.meshExtents.reset(); + for (int nodeIndex : sortedNodes) { + auto& node = _file.nodes[nodeIndex]; + + if (node.defined["mesh"]) { + + hfmModel.meshes.append(HFMMesh()); + HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1]; + if (!hfmModel.hasSkeletonJoints) { + HFMCluster cluster; + cluster.jointIndex = nodecount; + cluster.inverseBindMatrix = glm::mat4(); + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); + } else { // skinned model + for (int j = 0; j < numNodes; ++j) { + HFMCluster cluster; + cluster.jointIndex = j; + cluster.inverseBindMatrix = jointInverseBindTransforms[j]; + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); } } - } - - for (int skinIndex = 0; skinIndex < _file.skins.size(); ++skinIndex) { - const auto& skin = _file.skins[skinIndex]; - hfmModel.skinDeformers.emplace_back(); - auto& skinDeformer = hfmModel.skinDeformers.back(); - - // Add the nodes being referred to for skinning - for (int skinJointIndex : skin.joints) { - hfm::Cluster cluster; - cluster.jointIndex = skinJointIndex; - cluster.inverseBindMatrix = jointInverseBindTransforms[skinJointIndex]; - cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); - skinDeformer.clusters.push_back(cluster); - } - - // Always append a cluster referring to the root joint at the end - int rootJointIndex = skinToRootJoint[skinIndex]; - hfm::Cluster root; - root.jointIndex = rootJointIndex; + HFMCluster root; + root.jointIndex = 0; root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex]; root.inverseBindTransform = Transform(root.inverseBindMatrix); - skinDeformer.clusters.push_back(root); - } - } + mesh.clusters.append(root); - for (const auto& material : _file.materials) { - const QString& matid = material.name; - hfmModel.materials.emplace_back(); - HFMMaterial& hfmMaterial = hfmModel.materials.back(); - hfmMaterial._material = std::make_shared(); - hfmMaterial.materialID = matid; - setHFMMaterial(hfmMaterial, material); - } - - - int gltfMeshCount = _file.meshes.size(); - hfmModel.meshExtents.reset(); - std::vector> templateShapePerPrimPerGLTFMesh; - for (int gltfMeshIndex = 0; gltfMeshIndex < gltfMeshCount; ++gltfMeshIndex) { - const auto& gltfMesh = _file.meshes[gltfMeshIndex]; - hfmModel.meshes.emplace_back(); - // NOTE: The number of hfm meshes may be greater than the number of gltf meshes, if a gltf mesh has primitives with different vertex attributes. In that case, this mesh reference may be reassigned. - hfm::Mesh* meshPtr = &hfmModel.meshes.back(); - const size_t firstMeshIndexForGLTFMesh = hfmModel.meshes.size() - 1; - meshPtr->meshIndex = gltfMeshIndex; - templateShapePerPrimPerGLTFMesh.emplace_back(); - std::vector& templateShapePerPrim = templateShapePerPrimPerGLTFMesh.back(); - - QSet primitiveAttributes; - if (!gltfMesh.primitives.empty()) { - for (const auto& attribute : gltfMesh.primitives[0].attributes.values.keys()) { - primitiveAttributes.insert(attribute); - } - } - std::vector> primitiveAttributeVariants; - - int primCount = (int)gltfMesh.primitives.size(); - size_t hfmMeshIndex = firstMeshIndexForGLTFMesh; - for(int primIndex = 0; primIndex < primCount; ++primIndex) { - auto& primitive = gltfMesh.primitives[primIndex]; - - QList keys = primitive.attributes.values.keys(); - QSet newPrimitiveAttributes; - for (const auto& key : keys) { - newPrimitiveAttributes.insert(key); - } - if (newPrimitiveAttributes != primitiveAttributes) { - assert(primIndex != 0); - - // We need to use a different mesh because the vertex attributes are different - auto attributeVariantIt = std::find(primitiveAttributeVariants.cbegin(), primitiveAttributeVariants.cend(), newPrimitiveAttributes); - if (attributeVariantIt == primitiveAttributeVariants.cend()) { - // Need to allocate a new mesh - hfmModel.meshes.emplace_back(); - meshPtr = &hfmModel.meshes.back(); - hfmMeshIndex = hfmModel.meshes.size() - 1; - meshPtr->meshIndex = gltfMeshIndex; - primitiveAttributeVariants.push_back(newPrimitiveAttributes); - } else { - // An hfm mesh already exists for this gltf mesh with the same vertex attributes. Use it again. - auto variantIndex = (size_t)(attributeVariantIt - primitiveAttributeVariants.cbegin()); - hfmMeshIndex = firstMeshIndexForGLTFMesh + variantIndex; - meshPtr = &hfmModel.meshes[hfmMeshIndex]; + QList meshAttributes; + foreach(auto &primitive, _file.meshes[node.mesh].primitives) { + QList keys = primitive.attributes.values.keys(); + foreach (auto &key, keys) { + if (!meshAttributes.contains(key)) { + meshAttributes.push_back(key); + } } - primitiveAttributes = newPrimitiveAttributes; - } - // Now, allocate the part for the correct mesh... - hfm::Mesh& mesh = *meshPtr; - mesh.parts.emplace_back(); - hfm::MeshPart& part = mesh.parts.back(); - // ...and keep track of the relationship between the gltf mesh/primitive and the hfm mesh/part - templateShapePerPrim.emplace_back(); - hfm::Shape& templateShape = templateShapePerPrim.back(); - templateShape.mesh = (uint32_t)hfmMeshIndex; - templateShape.meshPart = (uint32_t)(mesh.parts.size() - 1); - templateShape.material = primitive.material; - - int indicesAccessorIdx = primitive.indices; - - GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx]; - - // Buffers - constexpr int VERTEX_STRIDE = 3; - constexpr int NORMAL_STRIDE = 3; - constexpr int TEX_COORD_STRIDE = 2; - - QVector indices; - QVector vertices; - QVector normals; - QVector tangents; - QVector texcoords; - QVector texcoords2; - QVector colors; - QVector joints; - QVector weights; - - static int tangentStride = 4; - static int colorStride = 3; - static int jointStride = 4; - static int weightStride = 4; - - bool success = addArrayFromAccessor(indicesAccessor, indices); - - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url; - continue; } - // Increment the triangle indices by the current mesh vertex count so each mesh part can all reference the same buffers within the mesh - int prevMeshVerticesCount = mesh.vertices.count(); - QVector clusterJoints; - QVector clusterWeights; + foreach(auto &primitive, _file.meshes[node.mesh].primitives) { + HFMMeshPart part = HFMMeshPart(); - for(auto &key : keys) { - int accessorIdx = primitive.attributes.values[key]; - GLTFAccessor& accessor = _file.accessors[accessorIdx]; - const auto vertexAttribute = GLTFVertexAttribute::fromString(key); - switch (vertexAttribute) { - case GLTFVertexAttribute::POSITION: - success = addArrayFromAttribute(vertexAttribute, accessor, vertices); - break; + int indicesAccessorIdx = primitive.indices; - case GLTFVertexAttribute::NORMAL: - success = addArrayFromAttribute(vertexAttribute, accessor, normals); - break; + GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx]; - case GLTFVertexAttribute::TANGENT: - success = addArrayFromAttribute(vertexAttribute, accessor, tangents); - tangentStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); - break; + // Buffers + QVector indices; + QVector vertices; + int verticesStride = 3; + QVector normals; + int normalStride = 3; + QVector tangents; + int tangentStride = 4; + QVector texcoords; + int texCoordStride = 2; + QVector texcoords2; + int texCoord2Stride = 2; + QVector colors; + int colorStride = 3; + QVector joints; + int jointStride = 4; + QVector weights; + int weightStride = 4; - case GLTFVertexAttribute::TEXCOORD_0: - success = addArrayFromAttribute(vertexAttribute, accessor, texcoords); - break; + bool success = addArrayFromAccessor(indicesAccessor, indices); - case GLTFVertexAttribute::TEXCOORD_1: - success = addArrayFromAttribute(vertexAttribute, accessor, texcoords2); - break; - - case GLTFVertexAttribute::COLOR_0: - success = addArrayFromAttribute(vertexAttribute, accessor, colors); - colorStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); - break; - - case GLTFVertexAttribute::JOINTS_0: - success = addArrayFromAttribute(vertexAttribute, accessor, joints); - jointStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); - break; - - case GLTFVertexAttribute::WEIGHTS_0: - success = addArrayFromAttribute(vertexAttribute, accessor, weights); - weightStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); - break; - - default: - success = false; - break; - } if (!success) { + qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url; continue; } - } - // Validation stage - if (indices.count() == 0) { - qWarning(modelformat) << "Missing indices for model " << _url; - continue; - } - if (vertices.count() == 0) { - qWarning(modelformat) << "Missing vertices for model " << _url; - continue; - } + // Increment the triangle indices by the current mesh vertex count so each mesh part can all reference the same buffers within the mesh + int prevMeshVerticesCount = mesh.vertices.count(); - int partVerticesCount = vertices.size() / 3; + QList keys = primitive.attributes.values.keys(); + QVector clusterJoints; + QVector clusterWeights; - QVector validatedIndices; - for (int n = 0; n < indices.count(); ++n) { - if (indices[n] < partVerticesCount) { - validatedIndices.push_back(indices[n] + prevMeshVerticesCount); - } else { - validatedIndices = QVector(); - break; + foreach(auto &key, keys) { + int accessorIdx = primitive.attributes.values[key]; + + GLTFAccessor& accessor = _file.accessors[accessorIdx]; + + if (key == "POSITION") { + if (accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF POSITION data for model " << _url; + continue; + } + + success = addArrayFromAccessor(accessor, vertices); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; + continue; + } + } else if (key == "NORMAL") { + if (accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF NORMAL data for model " << _url; + continue; + } + + success = addArrayFromAccessor(accessor, normals); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; + continue; + } + } else if (key == "TANGENT") { + if (accessor.type == GLTFAccessorType::VEC4) { + tangentStride = 4; + } else if (accessor.type == GLTFAccessorType::VEC3) { + tangentStride = 3; + } else { + qWarning(modelformat) << "Invalid accessor type on glTF TANGENT data for model " << _url; + continue; + } + + success = addArrayFromAccessor(accessor, tangents); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF TANGENT data for model " << _url; + tangentStride = 0; + continue; + } + } else if (key == "TEXCOORD_0") { + success = addArrayFromAccessor(accessor, texcoords); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; + continue; + } + + if (accessor.type != GLTFAccessorType::VEC2) { + qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_0 data for model " << _url; + continue; + } + } else if (key == "TEXCOORD_1") { + success = addArrayFromAccessor(accessor, texcoords2); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; + continue; + } + + if (accessor.type != GLTFAccessorType::VEC2) { + qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_1 data for model " << _url; + continue; + } + } else if (key == "COLOR_0") { + if (accessor.type == GLTFAccessorType::VEC4) { + colorStride = 4; + } else if (accessor.type == GLTFAccessorType::VEC3) { + colorStride = 3; + } else { + qWarning(modelformat) << "Invalid accessor type on glTF COLOR_0 data for model " << _url; + continue; + } + + success = addArrayFromAccessor(accessor, colors); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; + continue; + } + } else if (key == "JOINTS_0") { + if (accessor.type == GLTFAccessorType::VEC4) { + jointStride = 4; + } else if (accessor.type == GLTFAccessorType::VEC3) { + jointStride = 3; + } else if (accessor.type == GLTFAccessorType::VEC2) { + jointStride = 2; + } else if (accessor.type == GLTFAccessorType::SCALAR) { + jointStride = 1; + } else { + qWarning(modelformat) << "Invalid accessor type on glTF JOINTS_0 data for model " << _url; + continue; + } + + success = addArrayFromAccessor(accessor, joints); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; + continue; + } + } else if (key == "WEIGHTS_0") { + if (accessor.type == GLTFAccessorType::VEC4) { + weightStride = 4; + } else if (accessor.type == GLTFAccessorType::VEC3) { + weightStride = 3; + } else if (accessor.type == GLTFAccessorType::VEC2) { + weightStride = 2; + } else if (accessor.type == GLTFAccessorType::SCALAR) { + weightStride = 1; + } else { + qWarning(modelformat) << "Invalid accessor type on glTF WEIGHTS_0 data for model " << _url; + continue; + } + + success = addArrayFromAccessor(accessor, weights); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; + continue; + } + } } - } - if (validatedIndices.size() == 0) { - qWarning(modelformat) << "Indices out of range for model " << _url; - continue; - } - - part.triangleIndices.append(validatedIndices); - - mesh.vertices.reserve(mesh.vertices.size() + partVerticesCount); - for (int n = 0; n < vertices.size(); n = n + VERTEX_STRIDE) { - mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2])); - } - - mesh.normals.reserve(mesh.normals.size() + partVerticesCount); - for (int n = 0; n < normals.size(); n = n + NORMAL_STRIDE) { - mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); - } - - if (tangents.size() == partVerticesCount * tangentStride) { - mesh.tangents.reserve(mesh.tangents.size() + partVerticesCount); - for (int n = 0; n < tangents.size(); n += tangentStride) { - float tanW = tangentStride == 4 ? tangents[n + 3] : 1; - mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2])); + // Validation stage + if (indices.count() == 0) { + qWarning(modelformat) << "Missing indices for model " << _url; + continue; } - } - - if (texcoords.size() == partVerticesCount * TEX_COORD_STRIDE) { - mesh.texCoords.reserve(mesh.texCoords.size() + partVerticesCount); - for (int n = 0; n < texcoords.size(); n = n + 2) { - mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); + if (vertices.count() == 0) { + qWarning(modelformat) << "Missing vertices for model " << _url; + continue; } - } else if (primitiveAttributes.contains("TEXCOORD_0")) { - mesh.texCoords.resize(mesh.texCoords.size() + partVerticesCount); - } - if (texcoords2.size() == partVerticesCount * TEX_COORD_STRIDE) { - mesh.texCoords1.reserve(mesh.texCoords1.size() + partVerticesCount); - for (int n = 0; n < texcoords2.size(); n = n + 2) { - mesh.texCoords1.push_back(glm::vec2(texcoords2[n], texcoords2[n + 1])); - } - } else if (primitiveAttributes.contains("TEXCOORD_1")) { - mesh.texCoords1.resize(mesh.texCoords1.size() + partVerticesCount); - } + int partVerticesCount = vertices.size() / 3; - if (colors.size() == partVerticesCount * colorStride) { - mesh.colors.reserve(mesh.colors.size() + partVerticesCount); - for (int n = 0; n < colors.size(); n += colorStride) { - mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); - } - } else if (primitiveAttributes.contains("COLOR_0")) { - mesh.colors.reserve(mesh.colors.size() + partVerticesCount); - for (int i = 0; i < partVerticesCount; ++i) { - mesh.colors.push_back(glm::vec3(1.0f)); - } - } + // generate the normals if they don't exist + if (normals.size() == 0) { + QVector newIndices; + QVector newVertices; + QVector newNormals; + QVector newTexcoords; + QVector newTexcoords2; + QVector newColors; + QVector newJoints; + QVector newWeights; - const int WEIGHTS_PER_VERTEX = 4; + for (int n = 0; n < indices.size(); n = n + 3) { + int v1_index = (indices[n + 0] * 3); + int v2_index = (indices[n + 1] * 3); + int v3_index = (indices[n + 2] * 3); - if (weights.size() == partVerticesCount * weightStride) { - for (int n = 0; n < weights.size(); n += weightStride) { - clusterWeights.push_back(weights[n]); - if (weightStride > 1) { - clusterWeights.push_back(weights[n + 1]); - if (weightStride > 2) { - clusterWeights.push_back(weights[n + 2]); - if (weightStride > 3) { - clusterWeights.push_back(weights[n + 3]); + glm::vec3 v1 = glm::vec3(vertices[v1_index], vertices[v1_index + 1], vertices[v1_index + 2]); + glm::vec3 v2 = glm::vec3(vertices[v2_index], vertices[v2_index + 1], vertices[v2_index + 2]); + glm::vec3 v3 = glm::vec3(vertices[v3_index], vertices[v3_index + 1], vertices[v3_index + 2]); + + newVertices.append(v1.x); + newVertices.append(v1.y); + newVertices.append(v1.z); + newVertices.append(v2.x); + newVertices.append(v2.y); + newVertices.append(v2.z); + newVertices.append(v3.x); + newVertices.append(v3.y); + newVertices.append(v3.z); + + glm::vec3 norm = glm::normalize(glm::cross(v2 - v1, v3 - v1)); + + newNormals.append(norm.x); + newNormals.append(norm.y); + newNormals.append(norm.z); + newNormals.append(norm.x); + newNormals.append(norm.y); + newNormals.append(norm.z); + newNormals.append(norm.x); + newNormals.append(norm.y); + newNormals.append(norm.z); + + if (texcoords.size() == partVerticesCount * texCoordStride) { + GLTF_APPEND_ARRAY_2(newTexcoords, texcoords) + } + + if (texcoords2.size() == partVerticesCount * texCoord2Stride) { + GLTF_APPEND_ARRAY_2(newTexcoords2, texcoords2) + } + + if (colors.size() == partVerticesCount * colorStride) { + if (colorStride == 4) { + GLTF_APPEND_ARRAY_4(newColors, colors) } else { + GLTF_APPEND_ARRAY_3(newColors, colors) + } + } + + if (joints.size() == partVerticesCount * jointStride) { + if (jointStride == 4) { + GLTF_APPEND_ARRAY_4(newJoints, joints) + } else if (jointStride == 3) { + GLTF_APPEND_ARRAY_3(newJoints, joints) + } else if (jointStride == 2) { + GLTF_APPEND_ARRAY_2(newJoints, joints) + } else { + GLTF_APPEND_ARRAY_1(newJoints, joints) + } + } + + if (weights.size() == partVerticesCount * weightStride) { + if (weightStride == 4) { + GLTF_APPEND_ARRAY_4(newWeights, weights) + } else if (weightStride == 3) { + GLTF_APPEND_ARRAY_3(newWeights, weights) + } else if (weightStride == 2) { + GLTF_APPEND_ARRAY_2(newWeights, weights) + } else { + GLTF_APPEND_ARRAY_1(newWeights, weights) + } + } + newIndices.append(n); + newIndices.append(n + 1); + newIndices.append(n + 2); + } + + vertices = newVertices; + normals = newNormals; + tangents = QVector(); + texcoords = newTexcoords; + texcoords2 = newTexcoords2; + colors = newColors; + joints = newJoints; + weights = newWeights; + indices = newIndices; + + partVerticesCount = vertices.size() / 3; + } + + QVector validatedIndices; + for (int n = 0; n < indices.count(); ++n) { + if (indices[n] < partVerticesCount) { + validatedIndices.push_back(indices[n] + prevMeshVerticesCount); + } else { + validatedIndices = QVector(); + break; + } + } + + if (validatedIndices.size() == 0) { + qWarning(modelformat) << "Indices out of range for model " << _url; + continue; + } + + part.triangleIndices.append(validatedIndices); + + for (int n = 0; n < vertices.size(); n = n + verticesStride) { + mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2])); + } + + for (int n = 0; n < normals.size(); n = n + normalStride) { + mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); + } + + // TODO: add correct tangent generation + if (tangents.size() == partVerticesCount * tangentStride) { + for (int n = 0; n < tangents.size(); n += tangentStride) { + float tanW = tangentStride == 4 ? tangents[n + 3] : 1; + mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2])); + } + } else { + if (meshAttributes.contains("TANGENT")) { + for (int i = 0; i < partVerticesCount; ++i) { + mesh.tangents.push_back(glm::vec3(0.0f, 0.0f, 0.0f)); + } + } + } + + if (texcoords.size() == partVerticesCount * texCoordStride) { + for (int n = 0; n < texcoords.size(); n = n + 2) { + mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); + } + } else { + if (meshAttributes.contains("TEXCOORD_0")) { + for (int i = 0; i < partVerticesCount; ++i) { + mesh.texCoords.push_back(glm::vec2(0.0f, 0.0f)); + } + } + } + + if (texcoords2.size() == partVerticesCount * texCoord2Stride) { + for (int n = 0; n < texcoords2.size(); n = n + 2) { + mesh.texCoords1.push_back(glm::vec2(texcoords2[n], texcoords2[n + 1])); + } + } else { + if (meshAttributes.contains("TEXCOORD_1")) { + for (int i = 0; i < partVerticesCount; ++i) { + mesh.texCoords1.push_back(glm::vec2(0.0f, 0.0f)); + } + } + } + + if (colors.size() == partVerticesCount * colorStride) { + for (int n = 0; n < colors.size(); n += colorStride) { + mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); + } + } else { + if (meshAttributes.contains("COLOR_0")) { + for (int i = 0; i < partVerticesCount; ++i) { + mesh.colors.push_back(glm::vec3(1.0f, 1.0f, 1.0f)); + } + } + } + + if (joints.size() == partVerticesCount * jointStride) { + for (int n = 0; n < joints.size(); n += jointStride) { + clusterJoints.push_back(joints[n]); + if (jointStride > 1) { + clusterJoints.push_back(joints[n + 1]); + if (jointStride > 2) { + clusterJoints.push_back(joints[n + 2]); + if (jointStride > 3) { + clusterJoints.push_back(joints[n + 3]); + } else { + clusterJoints.push_back(0); + } + } else { + clusterJoints.push_back(0); + clusterJoints.push_back(0); + } + } else { + clusterJoints.push_back(0); + clusterJoints.push_back(0); + clusterJoints.push_back(0); + } + } + } else { + if (meshAttributes.contains("JOINTS_0")) { + for (int i = 0; i < partVerticesCount; ++i) { + for (int j = 0; j < 4; ++j) { + clusterJoints.push_back(0); + } + } + } + } + + if (weights.size() == partVerticesCount * weightStride) { + for (int n = 0; n < weights.size(); n += weightStride) { + clusterWeights.push_back(weights[n]); + if (weightStride > 1) { + clusterWeights.push_back(weights[n + 1]); + if (weightStride > 2) { + clusterWeights.push_back(weights[n + 2]); + if (weightStride > 3) { + clusterWeights.push_back(weights[n + 3]); + } else { + clusterWeights.push_back(0.0f); + } + } else { + clusterWeights.push_back(0.0f); clusterWeights.push_back(0.0f); } } else { clusterWeights.push_back(0.0f); clusterWeights.push_back(0.0f); + clusterWeights.push_back(0.0f); + } + } + } else { + if (meshAttributes.contains("WEIGHTS_0")) { + for (int i = 0; i < partVerticesCount; ++i) { + clusterWeights.push_back(1.0f); + for (int j = 1; j < 4; ++j) { + clusterWeights.push_back(0.0f); + } } - } else { - clusterWeights.push_back(0.0f); - clusterWeights.push_back(0.0f); - clusterWeights.push_back(0.0f); } } - } else if (primitiveAttributes.contains("WEIGHTS_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - clusterWeights.push_back(1.0f); - for (int j = 0; j < WEIGHTS_PER_VERTEX; ++j) { - clusterWeights.push_back(0.0f); + + // Build weights (adapted from FBXSerializer.cpp) + if (hfmModel.hasSkeletonJoints) { + int prevMeshClusterIndexCount = mesh.clusterIndices.count(); + int prevMeshClusterWeightCount = mesh.clusterWeights.count(); + const int WEIGHTS_PER_VERTEX = 4; + const float ALMOST_HALF = 0.499f; + int numVertices = mesh.vertices.size() - prevMeshVerticesCount; + + // Append new cluster indices and weights for this mesh part + for (int i = 0; i < numVertices * WEIGHTS_PER_VERTEX; ++i) { + mesh.clusterIndices.push_back(mesh.clusters.size() - 1); + mesh.clusterWeights.push_back(0); } - } - } - // Compress floating point weights to uint16_t for graphics runtime - // TODO: If the GLTF skinning weights are already in integer format, we should just copy the data - if (!clusterWeights.empty()) { - size_t numWeights = 4 * (mesh.vertices.size() - (uint32_t)prevMeshVerticesCount); - size_t newWeightsStart = mesh.clusterWeights.size(); - size_t newWeightsEnd = newWeightsStart + numWeights; - mesh.clusterWeights.reserve(newWeightsEnd); - for (int weightIndex = 0; weightIndex < clusterWeights.size(); ++weightIndex) { - // Per the GLTF specification - uint16_t weight = std::round(clusterWeights[weightIndex] * 65535.0f); - mesh.clusterWeights.push_back(weight); - } - mesh.clusterWeightsPerVertex = WEIGHTS_PER_VERTEX; - } + for (int c = 0; c < clusterJoints.size(); ++c) { + mesh.clusterIndices[prevMeshClusterIndexCount + c] = + originalToNewNodeIndexMap[_file.skins[node.skin].joints[clusterJoints[c]]]; + } - if (joints.size() == partVerticesCount * jointStride) { - for (int n = 0; n < joints.size(); n += jointStride) { - mesh.clusterIndices.push_back(joints[n]); - if (jointStride > 1) { - mesh.clusterIndices.push_back(joints[n + 1]); - if (jointStride > 2) { - mesh.clusterIndices.push_back(joints[n + 2]); - if (jointStride > 3) { - mesh.clusterIndices.push_back(joints[n + 3]); - } else { - mesh.clusterIndices.push_back(0); + // normalize and compress to 16-bits + for (int i = 0; i < numVertices; ++i) { + int j = i * WEIGHTS_PER_VERTEX; + + float totalWeight = 0.0f; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + totalWeight += clusterWeights[k]; + } + if (totalWeight > 0.0f) { + float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + mesh.clusterWeights[prevMeshClusterWeightCount + k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF); } } else { - mesh.clusterIndices.push_back(0); - mesh.clusterIndices.push_back(0); + mesh.clusterWeights[prevMeshClusterWeightCount + j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); } - } else { - mesh.clusterIndices.push_back(0); - mesh.clusterIndices.push_back(0); - mesh.clusterIndices.push_back(0); - } - } - } else if (primitiveAttributes.contains("JOINTS_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - for (int j = 0; j < 4; ++j) { - mesh.clusterIndices.push_back(0); - } - } - } + for (int clusterIndex = 0; clusterIndex < mesh.clusters.size() - 1; ++clusterIndex) { + ShapeVertices& points = hfmModel.shapeVertices.at(clusterIndex); + glm::vec3 globalMeshScale = extractScale(globalTransforms[nodeIndex]); + const glm::mat4 meshToJoint = glm::scale(glm::mat4(), globalMeshScale) * jointInverseBindTransforms[clusterIndex]; - if (!mesh.clusterIndices.empty()) { - int skinIndex = gltfMeshToSkin[gltfMeshIndex]; - if (skinIndex != -1) { - const auto& deformer = hfmModel.skinDeformers[(size_t)skinIndex]; - std::vector oldToNew; - oldToNew.resize(_file.nodes.size()); - for (uint16_t clusterIndex = 0; clusterIndex < deformer.clusters.size() - 1; ++clusterIndex) { - const auto& cluster = deformer.clusters[clusterIndex]; - oldToNew[(size_t)cluster.jointIndex] = clusterIndex; - } - } - } - - // populate the texture coordinates if they don't exist - if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { - for (int i = 0; i < part.triangleIndices.size(); ++i) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } - } - - // Build morph targets (blend shapes) - if (!primitive.targets.isEmpty()) { - - // Build list of blendshapes from FST - typedef QPair WeightedIndex; - hifi::VariantHash blendshapeMappings = mapping.value("bs").toHash(); - QMultiHash blendshapeIndices; - - for (int i = 0;; ++i) { - hifi::ByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; - if (blendshapeName.isEmpty()) { - break; - } - QList mappings = blendshapeMappings.values(blendshapeName); - foreach (const QVariant& mapping, mappings) { - QVariantList blendshapeMapping = mapping.toList(); - blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(), WeightedIndex(i, blendshapeMapping.at(1).toFloat())); - } - } - - // glTF morph targets may or may not have names. if they are labeled, add them based on - // the corresponding names from the FST. otherwise, just add them in the order they are given - mesh.blendshapes.resize(blendshapeMappings.size()); - auto values = blendshapeIndices.values(); - auto keys = blendshapeIndices.keys(); - auto names = gltfMesh.extras.targetNames; - QVector weights = gltfMesh.weights; - - for (int weightedIndex = 0; weightedIndex < values.size(); ++weightedIndex) { - float weight = 0.1f; - int indexFromMapping = weightedIndex; - int targetIndex = weightedIndex; - hfmModel.blendshapeChannelNames.push_back("target_" + QString::number(weightedIndex)); - - if (!names.isEmpty()) { - targetIndex = names.indexOf(keys[weightedIndex]); - indexFromMapping = values[weightedIndex].first; - weight = weight * values[weightedIndex].second; - hfmModel.blendshapeChannelNames[weightedIndex] = keys[weightedIndex]; - } - HFMBlendshape& blendshape = mesh.blendshapes[indexFromMapping]; - blendshape.indices = part.triangleIndices; - auto target = primitive.targets[targetIndex]; - - QVector normals; - QVector vertices; - - if (weights.size() == primitive.targets.size()) { - int targetWeight = weights[targetIndex]; - if (targetWeight != 0) { - weight = weight * targetWeight; - } - } - - if (target.values.contains((QString) "NORMAL")) { - generateTargetData(target.values.value((QString) "NORMAL"), weight, normals); - } - if (target.values.contains((QString) "POSITION")) { - generateTargetData(target.values.value((QString) "POSITION"), weight, vertices); - } - bool isNewBlendshape = blendshape.vertices.size() < vertices.size(); - int count = 0; - for (int i : blendshape.indices) { - if (isNewBlendshape) { - blendshape.vertices.push_back(vertices[i]); - blendshape.normals.push_back(normals[i]); - } else { - blendshape.vertices[count] = blendshape.vertices[count] + vertices[i]; - blendshape.normals[count] = blendshape.normals[count] + normals[i]; - ++count; + const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; + if (mesh.clusterWeights[j] >= EXPANSION_WEIGHT_THRESHOLD) { + // TODO: fix transformed vertices being pushed back + auto& vertex = mesh.vertices[i]; + const glm::mat4 vertexTransform = meshToJoint * (glm::translate(glm::mat4(), vertex)); + glm::vec3 transformedVertex = hfmModel.joints[clusterIndex].translation * (extractTranslation(vertexTransform)); + points.push_back(transformedVertex); + } } } } - } + + if (primitive.defined["material"]) { + part.materialID = materialIDs[primitive.material]; + } + mesh.parts.push_back(part); + + // populate the texture coordinates if they don't exist + if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { + for (int i = 0; i < part.triangleIndices.size(); ++i) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } + } + + // Build morph targets (blend shapes) + if (!primitive.targets.isEmpty()) { + + // Build list of blendshapes from FST + typedef QPair WeightedIndex; + hifi::VariantHash blendshapeMappings = mapping.value("bs").toHash(); + QMultiHash blendshapeIndices; + + for (int i = 0;; ++i) { + hifi::ByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; + if (blendshapeName.isEmpty()) { + break; + } + QList mappings = blendshapeMappings.values(blendshapeName); + foreach (const QVariant& mapping, mappings) { + QVariantList blendshapeMapping = mapping.toList(); + blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(), WeightedIndex(i, blendshapeMapping.at(1).toFloat())); + } + } + + // glTF morph targets may or may not have names. if they are labeled, add them based on + // the corresponding names from the FST. otherwise, just add them in the order they are given + mesh.blendshapes.resize(blendshapeMappings.size()); + auto values = blendshapeIndices.values(); + auto keys = blendshapeIndices.keys(); + auto names = _file.meshes[node.mesh].extras.targetNames; + QVector weights = _file.meshes[node.mesh].weights; + + for (int weightedIndex = 0; weightedIndex < values.size(); ++weightedIndex) { + float weight = 0.1f; + int indexFromMapping = weightedIndex; + int targetIndex = weightedIndex; + hfmModel.blendshapeChannelNames.push_back("target_" + QString::number(weightedIndex)); + + if (!names.isEmpty()) { + targetIndex = names.indexOf(keys[weightedIndex]); + indexFromMapping = values[weightedIndex].first; + weight = weight * values[weightedIndex].second; + hfmModel.blendshapeChannelNames[weightedIndex] = keys[weightedIndex]; + } + HFMBlendshape& blendshape = mesh.blendshapes[indexFromMapping]; + blendshape.indices = part.triangleIndices; + auto target = primitive.targets[targetIndex]; + + QVector normals; + QVector vertices; + + if (weights.size() == primitive.targets.size()) { + int targetWeight = weights[targetIndex]; + if (targetWeight != 0) { + weight = weight * targetWeight; + } + } + + if (target.values.contains((QString) "NORMAL")) { + generateTargetData(target.values.value((QString) "NORMAL"), weight, normals); + } + if (target.values.contains((QString) "POSITION")) { + generateTargetData(target.values.value((QString) "POSITION"), weight, vertices); + } + bool isNewBlendshape = blendshape.vertices.size() < vertices.size(); + int count = 0; + for (int i : blendshape.indices) { + if (isNewBlendshape) { + blendshape.vertices.push_back(vertices[i]); + blendshape.normals.push_back(normals[i]); + } else { + blendshape.vertices[count] = blendshape.vertices[count] + vertices[i]; + blendshape.normals[count] = blendshape.normals[count] + normals[i]; + ++count; + } + } + } + } + + foreach(const glm::vec3& vertex, mesh.vertices) { + glm::vec3 transformedVertex = glm::vec3(globalTransforms[nodeIndex] * glm::vec4(vertex, 1.0f)); + mesh.meshExtents.addPoint(transformedVertex); + hfmModel.meshExtents.addPoint(transformedVertex); + } + } + + // Add epsilon to mesh extents to compensate for planar meshes + mesh.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); + mesh.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); + hfmModel.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); + hfmModel.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); + + mesh.meshIndex = hfmModel.meshes.size(); } - } - - - // Create the instance shapes for each transform node - for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { - const auto& node = _file.nodes[nodeIndex]; - if (-1 == node.mesh) { - continue; - } - - const auto& gltfMesh = _file.meshes[node.mesh]; - const auto& templateShapePerPrim = templateShapePerPrimPerGLTFMesh[node.mesh]; - int primCount = (int)gltfMesh.primitives.size(); - for (int primIndex = 0; primIndex < primCount; ++primIndex) { - const auto& templateShape = templateShapePerPrim[primIndex]; - hfmModel.shapes.push_back(templateShape); - auto& hfmShape = hfmModel.shapes.back(); - // Everything else is already defined (mesh, meshPart, material), so just define the new transform and deformer if present - hfmShape.joint = nodeIndex; - hfmShape.skinDeformer = node.skin != -1 ? node.skin : hfm::UNDEFINED_KEY; - } - } - - // TODO: Fix skinning and remove this workaround which disables skinning - // TODO: Restore after testing - { - std::vector meshToRootJoint; - meshToRootJoint.resize(hfmModel.meshes.size(), -1); - std::vector meshToClusterSize; - meshToClusterSize.resize(hfmModel.meshes.size()); - for (auto& shape : hfmModel.shapes) { - shape.skinDeformer = hfm::UNDEFINED_KEY; - } - - for (auto& mesh : hfmModel.meshes) { - mesh.clusterWeights.clear(); - mesh.clusterIndices.clear(); - mesh.clusterWeightsPerVertex = 0; - } - + ++nodecount; } return true; @@ -1578,8 +1636,9 @@ std::unique_ptr GLTFSerializer::getFactory() const { } HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) { - _url = url; + _url = url; + // Normalize url for local files hifi::URL normalizeUrl = DependencyManager::get()->normalizeURL(_url); if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) { @@ -1589,9 +1648,6 @@ HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi:: if (parseGLTF(data)) { //_file.dump(); - _file.sortNodes(); - _file.populateMaterialNames(); - _file.normalizeNodeTransforms(); auto hfmModelPtr = std::make_shared(); HFMModel& hfmModel = *hfmModelPtr; buildGeometry(hfmModel, mapping, _url); @@ -1615,7 +1671,7 @@ bool GLTFSerializer::readBinary(const QString& url, hifi::ByteArray& outdata) { hifi::URL binaryUrl = _url.resolved(url); std::tie(success, outdata) = requestData(binaryUrl); } - + return success; } @@ -1628,8 +1684,8 @@ bool GLTFSerializer::doesResourceExist(const QString& url) { } std::tuple GLTFSerializer::requestData(hifi::URL& url) { - auto request = - DependencyManager::get()->createResourceRequest(nullptr, url, true, -1, "GLTFSerializer::requestData"); + auto request = DependencyManager::get()->createResourceRequest( + nullptr, url, true, -1, "GLTFSerializer::requestData"); if (!request) { return std::make_tuple(false, hifi::ByteArray()); @@ -1648,16 +1704,19 @@ std::tuple GLTFSerializer::requestData(hifi::URL& url) { } hifi::ByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { - QString binaryUrl = url.split(",")[1]; + QString binaryUrl = url.split(",")[1]; return binaryUrl.isEmpty() ? hifi::ByteArray() : QByteArray::fromBase64(binaryUrl.toUtf8()); } + QNetworkReply* GLTFSerializer::request(hifi::URL& url, bool isTest) { if (!qApp) { return nullptr; } bool aboutToQuit{ false }; - auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { aboutToQuit = true; }); + auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { + aboutToQuit = true; + }); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest netRequest(url); netRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -1666,18 +1725,18 @@ QNetworkReply* GLTFSerializer::request(hifi::URL& url, bool isTest) { netReply->deleteLater(); return nullptr; } - QEventLoop loop; // Create an event loop that will quit when we get the finished signal + QEventLoop loop; // Create an event loop that will quit when we get the finished signal QObject::connect(netReply, SIGNAL(finished()), &loop, SLOT(quit())); - loop.exec(); // Nothing is going to happen on this whole run thread until we get this + loop.exec(); // Nothing is going to happen on this whole run thread until we get this QObject::disconnect(connection); - return netReply; // trying to sync later on. + return netReply; // trying to sync later on. } HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { HFMTexture fbxtex = HFMTexture(); fbxtex.texcoordSet = 0; - + if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; @@ -1685,10 +1744,10 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { hifi::URL textureUrl = _url.resolved(url); 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; @@ -1698,7 +1757,7 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { } if (url.contains("data:image/jpeg;base64,") || url.contains("data:image/png;base64,")) { - fbxtex.content = requestEmbeddedData(url); + fbxtex.content = requestEmbeddedData(url); } } return fbxtex; @@ -1728,12 +1787,12 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const GLTFMaterial& mat hfmMat.emissiveTexture = getHFMTexture(_file.textures[material.emissiveTexture]); hfmMat.useEmissiveMap = true; } - + if (material.defined["normalTexture"]) { hfmMat.normalTexture = getHFMTexture(_file.textures[material.normalTexture]); hfmMat.useNormalMap = true; } - + if (material.defined["occlusionTexture"]) { hfmMat.occlusionTexture = getHFMTexture(_file.textures[material.occlusionTexture]); hfmMat.useOcclusionMap = true; @@ -1761,7 +1820,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const GLTFMaterial& mat if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { hfmMat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor); } - if (material.pbrMetallicRoughness.defined["baseColorFactor"] && + if (material.pbrMetallicRoughness.defined["baseColorFactor"] && material.pbrMetallicRoughness.baseColorFactor.size() == 4) { glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], material.pbrMetallicRoughness.baseColorFactor[1], @@ -1771,10 +1830,13 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const GLTFMaterial& mat hfmMat._material->setOpacity(material.pbrMetallicRoughness.baseColorFactor[3]); } } + } -template -bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType) { +template +bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int count, + QVector& outarray, int accessorType) { + QDataStream blobstream(bin); blobstream.setByteOrder(QDataStream::LittleEndian); blobstream.setVersion(QDataStream::Qt_5_9); @@ -1783,31 +1845,31 @@ bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int c int bufferCount = 0; switch (accessorType) { - case GLTFAccessorType::SCALAR: - bufferCount = 1; - break; - case GLTFAccessorType::VEC2: - bufferCount = 2; - break; - case GLTFAccessorType::VEC3: - bufferCount = 3; - break; - case GLTFAccessorType::VEC4: - bufferCount = 4; - break; - case GLTFAccessorType::MAT2: - bufferCount = 4; - break; - case GLTFAccessorType::MAT3: - bufferCount = 9; - break; - case GLTFAccessorType::MAT4: - bufferCount = 16; - break; - default: - qWarning(modelformat) << "Unknown accessorType: " << accessorType; - blobstream.setDevice(nullptr); - return false; + case GLTFAccessorType::SCALAR: + bufferCount = 1; + break; + case GLTFAccessorType::VEC2: + bufferCount = 2; + break; + case GLTFAccessorType::VEC3: + bufferCount = 3; + break; + case GLTFAccessorType::VEC4: + bufferCount = 4; + break; + case GLTFAccessorType::MAT2: + bufferCount = 4; + break; + case GLTFAccessorType::MAT3: + bufferCount = 9; + break; + case GLTFAccessorType::MAT4: + bufferCount = 16; + break; + default: + qWarning(modelformat) << "Unknown accessorType: " << accessorType; + blobstream.setDevice(nullptr); + return false; } for (int i = 0; i < count; ++i) { for (int j = 0; j < bufferCount; ++j) { @@ -1825,142 +1887,31 @@ bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int c blobstream.setDevice(nullptr); return true; } -template -bool GLTFSerializer::addArrayOfType(const hifi::ByteArray& bin, - int byteOffset, - int count, - QVector& outarray, - int accessorType, - int componentType) { +template +bool GLTFSerializer::addArrayOfType(const hifi::ByteArray& bin, int byteOffset, int count, + QVector& outarray, int accessorType, int componentType) { + switch (componentType) { - case GLTFAccessorComponentType::BYTE: {} - case GLTFAccessorComponentType::UNSIGNED_BYTE: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::SHORT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::UNSIGNED_INT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::UNSIGNED_SHORT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::FLOAT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } + case GLTFAccessorComponentType::BYTE: {} + case GLTFAccessorComponentType::UNSIGNED_BYTE: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::SHORT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::UNSIGNED_INT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::UNSIGNED_SHORT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::FLOAT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } } return false; } - -template -bool GLTFSerializer::addArrayFromAttribute(GLTFVertexAttribute::Value vertexAttribute, GLTFAccessor& accessor, QVector& outarray) { - switch (vertexAttribute) { - case GLTFVertexAttribute::POSITION: - if (accessor.type != GLTFAccessorType::VEC3) { - qWarning(modelformat) << "Invalid accessor type on glTF POSITION data for model " << _url; - return false; - } - - if (!addArrayFromAccessor(accessor, outarray)) { - qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; - return false; - } - break; - - case GLTFVertexAttribute::NORMAL: - if (accessor.type != GLTFAccessorType::VEC3) { - qWarning(modelformat) << "Invalid accessor type on glTF NORMAL data for model " << _url; - return false; - } - - if (!addArrayFromAccessor(accessor, outarray)) { - qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; - return false; - } - break; - - case GLTFVertexAttribute::TANGENT: - if (accessor.type != GLTFAccessorType::VEC4 && accessor.type != GLTFAccessorType::VEC3) { - qWarning(modelformat) << "Invalid accessor type on glTF TANGENT data for model " << _url; - return false; - } - - if (!addArrayFromAccessor(accessor, outarray)) { - qWarning(modelformat) << "There was a problem reading glTF TANGENT data for model " << _url; - return false; - } - break; - - case GLTFVertexAttribute::TEXCOORD_0: - if (accessor.type != GLTFAccessorType::VEC2) { - qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_0 data for model " << _url; - return false; - } - - if (!addArrayFromAccessor(accessor, outarray)) { - qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; - return false; - } - break; - - case GLTFVertexAttribute::TEXCOORD_1: - if (accessor.type != GLTFAccessorType::VEC2) { - qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_1 data for model " << _url; - return false; - } - if (!addArrayFromAccessor(accessor, outarray)) { - qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; - return false; - } - break; - - case GLTFVertexAttribute::COLOR_0: - if (accessor.type != GLTFAccessorType::VEC4 && accessor.type != GLTFAccessorType::VEC3) { - qWarning(modelformat) << "Invalid accessor type on glTF COLOR_0 data for model " << _url; - return false; - } - - if (!addArrayFromAccessor(accessor, outarray)) { - qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; - return false; - } - break; - - case GLTFVertexAttribute::JOINTS_0: - if (accessor.type < GLTFAccessorType::SCALAR || accessor.type > GLTFAccessorType::VEC4) { - qWarning(modelformat) << "Invalid accessor type on glTF JOINTS_0 data for model " << _url; - return false; - } - - if (!addArrayFromAccessor(accessor, outarray)) { - qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; - return false; - } - break; - - case GLTFVertexAttribute::WEIGHTS_0: - if (accessor.type < GLTFAccessorType::SCALAR || accessor.type > GLTFAccessorType::VEC4) { - qWarning(modelformat) << "Invalid accessor type on glTF WEIGHTS_0 data for model " << _url; - return false; - } - - if (!addArrayFromAccessor(accessor, outarray)) { - qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; - return false; - } - break; - - default: - qWarning(modelformat) << "Unexpected attribute type" << _url; - return false; - } - - - return true; -} - template bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& outarray) { bool success = true; @@ -2006,7 +1957,7 @@ bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& ou if (success) { for (int i = 0; i < accessor.sparse.count; ++i) { - if ((i * 3) + 2 < out_sparse_values_array.size()) { + if ((i * 3) + 2 < out_sparse_values_array.size()) { if ((out_sparse_indices_array[i] * 3) + 2 < outarray.length()) { for (int j = 0; j < 3; ++j) { outarray[(out_sparse_indices_array[i] * 3) + j] = out_sparse_values_array[(i * 3) + j]; @@ -2028,16 +1979,14 @@ bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& ou return success; } -void GLTFSerializer::retriangulate(const QVector& inIndices, - const QVector& in_vertices, - const QVector& in_normals, - QVector& outIndices, - QVector& out_vertices, - QVector& out_normals) { +void GLTFSerializer::retriangulate(const QVector& inIndices, const QVector& in_vertices, + const QVector& in_normals, QVector& outIndices, + QVector& out_vertices, QVector& out_normals) { for (int i = 0; i < inIndices.size(); i = i + 3) { + int idx1 = inIndices[i]; - int idx2 = inIndices[i + 1]; - int idx3 = inIndices[i + 2]; + int idx2 = inIndices[i+1]; + int idx3 = inIndices[i+2]; out_vertices.push_back(in_vertices[idx1]); out_vertices.push_back(in_vertices[idx2]); @@ -2048,8 +1997,8 @@ void GLTFSerializer::retriangulate(const QVector& inIndices, out_normals.push_back(in_normals[idx3]); outIndices.push_back(i); - outIndices.push_back(i + 1); - outIndices.push_back(i + 2); + outIndices.push_back(i+1); + outIndices.push_back(i+2); } } @@ -2058,7 +2007,7 @@ void GLTFSerializer::glTFDebugDump() { for (GLTFNode node : _file.nodes) { if (node.defined["mesh"]) { qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " node_transform" << node.transform; + qCDebug(modelformat) << " node_transforms" << node.transforms; qCDebug(modelformat) << "\n"; } } @@ -2086,7 +2035,144 @@ void GLTFSerializer::glTFDebugDump() { } void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { - hfmModel.debugDump(); + qCDebug(modelformat) << "---------------- hfmModel ----------------"; + qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; + qCDebug(modelformat) << " offset =" << hfmModel.offset; + + qCDebug(modelformat) << " neckPivot = " << hfmModel.neckPivot; + + qCDebug(modelformat) << " bindExtents.size() = " << hfmModel.bindExtents.size(); + qCDebug(modelformat) << " meshExtents.size() = " << hfmModel.meshExtents.size(); + + qCDebug(modelformat) << " jointIndices.size() =" << hfmModel.jointIndices.size(); + qCDebug(modelformat) << " joints.count() =" << hfmModel.joints.count(); + qCDebug(modelformat) << "---------------- Meshes ----------------"; + qCDebug(modelformat) << " meshes.count() =" << hfmModel.meshes.count(); + qCDebug(modelformat) << " blendshapeChannelNames = " << hfmModel.blendshapeChannelNames; + foreach(HFMMesh mesh, hfmModel.meshes) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " meshpointer =" << mesh._mesh.get(); + qCDebug(modelformat) << " meshindex =" << mesh.meshIndex; + qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.size(); + qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); + qCDebug(modelformat) << " normals.count() =" << mesh.normals.size(); + qCDebug(modelformat) << " tangents.count() =" << mesh.tangents.size(); + qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); + qCDebug(modelformat) << " texCoords.count() =" << mesh.texCoords.count(); + qCDebug(modelformat) << " texCoords1.count() =" << mesh.texCoords1.count(); + qCDebug(modelformat) << " clusterIndices.count() =" << mesh.clusterIndices.count(); + qCDebug(modelformat) << " clusterWeights.count() =" << mesh.clusterWeights.count(); + qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; + qCDebug(modelformat) << " parts.count() =" << mesh.parts.count(); + qCDebug(modelformat) << "---------------- Meshes (blendshapes)--------"; + foreach(HFMBlendshape bshape, mesh.blendshapes) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " bshape.indices.count() =" << bshape.indices.count(); + qCDebug(modelformat) << " bshape.vertices.count() =" << bshape.vertices.count(); + qCDebug(modelformat) << " bshape.normals.count() =" << bshape.normals.count(); + qCDebug(modelformat) << "\n"; + } + qCDebug(modelformat) << "---------------- Meshes (meshparts)--------"; + foreach(HFMMeshPart meshPart, mesh.parts) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); + qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); + qCDebug(modelformat) << " materialID =" << meshPart.materialID; + qCDebug(modelformat) << "\n"; + + } + qCDebug(modelformat) << "---------------- Meshes (clusters)--------"; + qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); + foreach(HFMCluster cluster, mesh.clusters) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; + qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; + qCDebug(modelformat) << "\n"; + } + qCDebug(modelformat) << "\n"; + } + qCDebug(modelformat) << "---------------- AnimationFrames ----------------"; + foreach(HFMAnimationFrame anim, hfmModel.animationFrames) { + qCDebug(modelformat) << " anim.translations = " << anim.translations; + qCDebug(modelformat) << " anim.rotations = " << anim.rotations; + } + QList mitomona_keys = hfmModel.meshIndicesToModelNames.keys(); + foreach(int key, mitomona_keys) { + qCDebug(modelformat) << " meshIndicesToModelNames key =" << key << " val =" << hfmModel.meshIndicesToModelNames[key]; + } + + qCDebug(modelformat) << "---------------- Materials ----------------"; + + foreach(HFMMaterial mat, hfmModel.materials) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " mat.materialID =" << mat.materialID; + qCDebug(modelformat) << " diffuseColor =" << mat.diffuseColor; + qCDebug(modelformat) << " diffuseFactor =" << mat.diffuseFactor; + qCDebug(modelformat) << " specularColor =" << mat.specularColor; + qCDebug(modelformat) << " specularFactor =" << mat.specularFactor; + qCDebug(modelformat) << " emissiveColor =" << mat.emissiveColor; + qCDebug(modelformat) << " emissiveFactor =" << mat.emissiveFactor; + qCDebug(modelformat) << " shininess =" << mat.shininess; + qCDebug(modelformat) << " opacity =" << mat.opacity; + qCDebug(modelformat) << " metallic =" << mat.metallic; + qCDebug(modelformat) << " roughness =" << mat.roughness; + qCDebug(modelformat) << " emissiveIntensity =" << mat.emissiveIntensity; + qCDebug(modelformat) << " ambientFactor =" << mat.ambientFactor; + + qCDebug(modelformat) << " materialID =" << mat.materialID; + qCDebug(modelformat) << " name =" << mat.name; + qCDebug(modelformat) << " shadingModel =" << mat.shadingModel; + qCDebug(modelformat) << " _material =" << mat._material.get(); + + qCDebug(modelformat) << " normalTexture =" << mat.normalTexture.filename; + qCDebug(modelformat) << " albedoTexture =" << mat.albedoTexture.filename; + qCDebug(modelformat) << " opacityTexture =" << mat.opacityTexture.filename; + + qCDebug(modelformat) << " lightmapParams =" << mat.lightmapParams; + + qCDebug(modelformat) << " isPBSMaterial =" << mat.isPBSMaterial; + qCDebug(modelformat) << " useNormalMap =" << mat.useNormalMap; + qCDebug(modelformat) << " useAlbedoMap =" << mat.useAlbedoMap; + qCDebug(modelformat) << " useOpacityMap =" << mat.useOpacityMap; + qCDebug(modelformat) << " useRoughnessMap =" << mat.useRoughnessMap; + qCDebug(modelformat) << " useSpecularMap =" << mat.useSpecularMap; + qCDebug(modelformat) << " useMetallicMap =" << mat.useMetallicMap; + qCDebug(modelformat) << " useEmissiveMap =" << mat.useEmissiveMap; + qCDebug(modelformat) << " useOcclusionMap =" << mat.useOcclusionMap; + qCDebug(modelformat) << "\n"; + } + + qCDebug(modelformat) << "---------------- Joints ----------------"; + + foreach (HFMJoint joint, hfmModel.joints) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " shapeInfo.avgPoint =" << joint.shapeInfo.avgPoint; + qCDebug(modelformat) << " shapeInfo.debugLines =" << joint.shapeInfo.debugLines; + qCDebug(modelformat) << " shapeInfo.dots =" << joint.shapeInfo.dots; + qCDebug(modelformat) << " shapeInfo.points =" << joint.shapeInfo.points; + + qCDebug(modelformat) << " parentIndex" << joint.parentIndex; + qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; + qCDebug(modelformat) << " translation" << joint.translation; + qCDebug(modelformat) << " preTransform" << joint.preTransform; + qCDebug(modelformat) << " preRotation" << joint.preRotation; + qCDebug(modelformat) << " rotation" << joint.rotation; + qCDebug(modelformat) << " postRotation" << joint.postRotation; + qCDebug(modelformat) << " postTransform" << joint.postTransform; + qCDebug(modelformat) << " transform" << joint.transform; + qCDebug(modelformat) << " rotationMin" << joint.rotationMin; + qCDebug(modelformat) << " rotationMax" << joint.rotationMax; + qCDebug(modelformat) << " inverseDefaultRotation" << joint.inverseDefaultRotation; + qCDebug(modelformat) << " inverseBindRotation" << joint.inverseBindRotation; + qCDebug(modelformat) << " bindTransform" << joint.bindTransform; + qCDebug(modelformat) << " name" << joint.name; + qCDebug(modelformat) << " isSkeletonJoint" << joint.isSkeletonJoint; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.hasGeometricOffset; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricTranslation; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricRotation; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricScaling; + qCDebug(modelformat) << "\n"; + } qCDebug(modelformat) << "---------------- GLTF Model ----------------"; glTFDebugDump(); diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h index d59df615e5..b1020f7154 100755 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -38,15 +38,15 @@ struct GLTFAsset { struct GLTFNode { QString name; - int camera{ -1 }; - int mesh{ -1 }; + int camera; + int mesh; QVector children; QVector translation; QVector rotation; QVector scale; QVector matrix; - glm::mat4 transform; - int skin { -1 }; + QVector transforms; + int skin; QVector skeletons; QString jointName; QMap defined; @@ -85,8 +85,6 @@ struct GLTFNode { qCDebug(modelformat) << "skeletons: " << skeletons; } } - - void normalizeTransform(); }; // Meshes @@ -460,56 +458,15 @@ struct GLTFMaterial { // Accesors namespace GLTFAccessorType { - enum Value { - SCALAR = 1, - VEC2 = 2, - VEC3 = 3, - VEC4 = 4, - MAT2 = 5, - MAT3 = 9, - MAT4 = 16 + enum Values { + SCALAR = 0, + VEC2, + VEC3, + VEC4, + MAT2, + MAT3, + MAT4 }; - - inline int count(Value value) { - if (value == MAT2) { - return 4; - } - return (int)value; - } -} - -namespace GLTFVertexAttribute { - enum Value { - UNKNOWN = -1, - POSITION = 0, - NORMAL, - TANGENT, - TEXCOORD_0, - TEXCOORD_1, - COLOR_0, - JOINTS_0, - WEIGHTS_0, - }; - inline Value fromString(const QString& key) { - if (key == "POSITION") { - return POSITION; - } else if (key == "NORMAL") { - return NORMAL; - } else if (key == "TANGENT") { - return TANGENT; - } else if (key == "TEXCOORD_0") { - return TEXCOORD_0; - } else if (key == "TEXCOORD_1") { - return TEXCOORD_1; - } else if (key == "COLOR_0") { - return COLOR_0; - } else if (key == "JOINTS_0") { - return JOINTS_0; - } else if (key == "WEIGHTS_0") { - return WEIGHTS_0; - } - return UNKNOWN; - } } namespace GLTFAccessorComponentType { enum Values { @@ -801,13 +758,6 @@ struct GLTFFile { foreach(auto tex, textures) tex.dump(); } } - - - void populateMaterialNames(); - void sortNodes(); - void normalizeNodeTransforms(); -private: - void reorderNodes(const std::unordered_map& reorderMap); }; class GLTFSerializer : public QObject, public HFMSerializer { @@ -822,7 +772,7 @@ private: hifi::URL _url; hifi::ByteArray _glbBinary; - const glm::mat4& getModelTransform(const GLTFNode& node); + glm::mat4 getModelTransform(const GLTFNode& node); void getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues); void generateTargetData(int index, float weight, QVector& returnVector); @@ -891,9 +841,6 @@ private: template bool addArrayFromAccessor(GLTFAccessor& accessor, QVector& outarray); - template - bool addArrayFromAttribute(GLTFVertexAttribute::Value vertexAttribute, GLTFAccessor& accessor, QVector& outarray); - void retriangulate(const QVector& in_indices, const QVector& in_vertices, const QVector& in_normals, QVector& out_indices, QVector& out_vertices, QVector& out_normals); diff --git a/libraries/fbx/src/OBJSerializer.cpp b/libraries/fbx/src/OBJSerializer.cpp index 4da7351b42..416f343a47 100644 --- a/libraries/fbx/src/OBJSerializer.cpp +++ b/libraries/fbx/src/OBJSerializer.cpp @@ -174,6 +174,11 @@ glm::vec2 OBJTokenizer::getVec2() { return v; } + +void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID) { + meshPart.materialID = materialID; +} + // OBJFace // NOTE (trent, 7/20/17): The vertexColors vector being passed-in isn't necessary here, but I'm just // pairing it with the vertices vector for consistency. @@ -487,7 +492,8 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHa float& scaleGuess, bool combineParts) { FaceGroup faces; HFMMesh& mesh = hfmModel.meshes[0]; - mesh.parts.push_back(HFMMeshPart()); + mesh.parts.append(HFMMeshPart()); + HFMMeshPart& meshPart = mesh.parts.last(); bool sawG = false; bool result = true; int originalFaceCountForDebugging = 0; @@ -495,6 +501,8 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHa bool anyVertexColor { false }; int vertexCount { 0 }; + setMeshPartDefaults(meshPart, QString("dontknow") + QString::number(mesh.parts.count())); + while (true) { int tokenType = tokenizer.nextToken(); if (tokenType == OBJTokenizer::COMMENT_TOKEN) { @@ -667,19 +675,17 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V _url = url; bool combineParts = mapping.value("combineParts").toBool(); - hfmModel.meshes.push_back(HFMMesh()); + hfmModel.meshExtents.reset(); + hfmModel.meshes.append(HFMMesh()); - std::vector materialNamePerShape; try { // call parseOBJGroup as long as it's returning true. Each successful call will // add a new meshPart to the model's single mesh. while (parseOBJGroup(tokenizer, mapping, hfmModel, scaleGuess, combineParts)) {} - uint32_t meshIndex = 0; - HFMMesh& mesh = hfmModel.meshes[meshIndex]; - mesh.meshIndex = meshIndex; + HFMMesh& mesh = hfmModel.meshes[0]; + mesh.meshIndex = 0; - uint32_t jointIndex = 0; hfmModel.joints.resize(1); hfmModel.joints[0].parentIndex = -1; hfmModel.joints[0].distanceToParent = 0; @@ -691,11 +697,19 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V hfmModel.jointIndices["x"] = 1; + HFMCluster cluster; + cluster.jointIndex = 0; + cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + mesh.clusters.append(cluster); + QMap materialMeshIdMap; - std::vector hfmMeshParts; - for (uint32_t meshPartIndex = 0; meshPartIndex < (uint32_t)mesh.parts.size(); ++meshPartIndex) { - HFMMeshPart& meshPart = mesh.parts[meshPartIndex]; - FaceGroup faceGroup = faceGroups[meshPartIndex]; + QVector hfmMeshParts; + for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) { + HFMMeshPart& meshPart = mesh.parts[i]; + FaceGroup faceGroup = faceGroups[meshPartCount]; bool specifiesUV = false; foreach(OBJFace face, faceGroup) { // Go through all of the OBJ faces and determine the number of different materials necessary (each different material will be a unique mesh). @@ -704,13 +718,12 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V // Create a new HFMMesh for this material mapping. materialMeshIdMap.insert(face.materialName, materialMeshIdMap.count()); - uint32_t partIndex = (int)hfmMeshParts.size(); - hfmMeshParts.push_back(HFMMeshPart()); - HFMMeshPart& meshPartNew = hfmMeshParts.back(); + hfmMeshParts.append(HFMMeshPart()); + HFMMeshPart& meshPartNew = hfmMeshParts.last(); meshPartNew.quadIndices = QVector(meshPart.quadIndices); // Copy over quad indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. meshPartNew.quadTrianglesIndices = QVector(meshPart.quadTrianglesIndices); // Copy over quad triangulated indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. meshPartNew.triangleIndices = QVector(meshPart.triangleIndices); // Copy over triangle indices. - + // Do some of the material logic (which previously lived below) now. // All the faces in the same group will have the same name and material. QString groupMaterialName = face.materialName; @@ -732,26 +745,19 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME; } materials[groupMaterialName] = material; + meshPartNew.materialID = groupMaterialName; } - materialNamePerShape.push_back(groupMaterialName); - - - hfm::Shape shape; - shape.mesh = meshIndex; - shape.joint = jointIndex; - shape.meshPart = partIndex; - hfmModel.shapes.push_back(shape); } } } // clean up old mesh parts. - auto unmodifiedMeshPartCount = (uint32_t)mesh.parts.size(); + int unmodifiedMeshPartCount = mesh.parts.count(); mesh.parts.clear(); - mesh.parts = hfmMeshParts; + mesh.parts = QVector(hfmMeshParts); - for (uint32_t meshPartIndex = 0; meshPartIndex < unmodifiedMeshPartCount; meshPartIndex++) { - FaceGroup faceGroup = faceGroups[meshPartIndex]; + for (int i = 0, meshPartCount = 0; i < unmodifiedMeshPartCount; i++, meshPartCount++) { + FaceGroup faceGroup = faceGroups[meshPartCount]; // Now that each mesh has been created with its own unique material mappings, fill them with data (vertex data is duplicated, face data is not). foreach(OBJFace face, faceGroup) { @@ -817,13 +823,18 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V } } } + + mesh.meshExtents.reset(); + foreach(const glm::vec3& vertex, mesh.vertices) { + mesh.meshExtents.addPoint(vertex); + hfmModel.meshExtents.addPoint(vertex); + } + + // hfmDebugDump(hfmModel); } catch(const std::exception& e) { qCDebug(modelformat) << "OBJSerializer fail: " << e.what(); } - // At this point, the hfmModel joint, mesh, parts and shpaes have been defined - // only no material assigned - QString queryPart = _url.query(); bool suppressMaterialsHack = queryPart.contains("hifiusemat"); // If this appears in query string, don't fetch mtl even if used. OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME]; @@ -875,23 +886,17 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V } } - // As we are populating the material list in the hfmModel, let s also create the reverse map (from materialName to index) - QMap materialNameToIndex; foreach (QString materialID, materials.keys()) { OBJMaterial& objMaterial = materials[materialID]; if (!objMaterial.used) { continue; } - // capture the name to index map - materialNameToIndex[materialID] = (uint32_t) hfmModel.materials.size(); - - hfmModel.materials.emplace_back(objMaterial.diffuseColor, - objMaterial.specularColor, - objMaterial.emissiveColor, - objMaterial.shininess, - objMaterial.opacity); - HFMMaterial& hfmMaterial = hfmModel.materials.back(); + HFMMaterial& hfmMaterial = hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor, + objMaterial.specularColor, + objMaterial.emissiveColor, + objMaterial.shininess, + objMaterial.opacity); hfmMaterial.name = materialID; hfmMaterial.materialID = materialID; @@ -991,16 +996,77 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V modelMaterial->setOpacity(hfmMaterial.opacity); } - // GO over the shapes once more to assign the material index correctly - for (uint32_t i = 0; i < (uint32_t)hfmModel.shapes.size(); ++i) { - const auto& materialName = materialNamePerShape[i]; - if (!materialName.isEmpty()) { - auto foundMaterialIndex = materialNameToIndex.find(materialName); - if (foundMaterialIndex != materialNameToIndex.end()) { - hfmModel.shapes[i].material = foundMaterialIndex.value(); + return hfmModelPtr; +} + +void hfmDebugDump(const HFMModel& hfmModel) { + qCDebug(modelformat) << "---------------- hfmModel ----------------"; + qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; + qCDebug(modelformat) << " offset =" << hfmModel.offset; + qCDebug(modelformat) << " meshes.count() =" << hfmModel.meshes.count(); + foreach (HFMMesh mesh, hfmModel.meshes) { + qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.count(); + qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); + qCDebug(modelformat) << " normals.count() =" << mesh.normals.count(); + /*if (mesh.normals.count() == mesh.vertices.count()) { + for (int i = 0; i < mesh.normals.count(); i++) { + qCDebug(modelformat) << " " << mesh.vertices[ i ] << mesh.normals[ i ]; } + }*/ + qCDebug(modelformat) << " tangents.count() =" << mesh.tangents.count(); + qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); + qCDebug(modelformat) << " texCoords.count() =" << mesh.texCoords.count(); + qCDebug(modelformat) << " texCoords1.count() =" << mesh.texCoords1.count(); + qCDebug(modelformat) << " clusterIndices.count() =" << mesh.clusterIndices.count(); + qCDebug(modelformat) << " clusterWeights.count() =" << mesh.clusterWeights.count(); + qCDebug(modelformat) << " meshExtents =" << mesh.meshExtents; + qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; + qCDebug(modelformat) << " parts.count() =" << mesh.parts.count(); + foreach (HFMMeshPart meshPart, mesh.parts) { + qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); + qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); + /* + qCDebug(modelformat) << " diffuseColor =" << meshPart.diffuseColor << "mat =" << meshPart._material->getDiffuse(); + qCDebug(modelformat) << " specularColor =" << meshPart.specularColor << "mat =" << meshPart._material->getMetallic(); + qCDebug(modelformat) << " emissiveColor =" << meshPart.emissiveColor << "mat =" << meshPart._material->getEmissive(); + qCDebug(modelformat) << " emissiveParams =" << meshPart.emissiveParams; + qCDebug(modelformat) << " gloss =" << meshPart.shininess << "mat =" << meshPart._material->getRoughness(); + qCDebug(modelformat) << " opacity =" << meshPart.opacity << "mat =" << meshPart._material->getOpacity(); + */ + qCDebug(modelformat) << " materialID =" << meshPart.materialID; + /* qCDebug(modelformat) << " diffuse texture =" << meshPart.diffuseTexture.filename; + qCDebug(modelformat) << " specular texture =" << meshPart.specularTexture.filename; + */ + } + qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); + foreach (HFMCluster cluster, mesh.clusters) { + qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; + qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; } } - return hfmModelPtr; + qCDebug(modelformat) << " jointIndices =" << hfmModel.jointIndices; + qCDebug(modelformat) << " joints.count() =" << hfmModel.joints.count(); + + foreach (HFMJoint joint, hfmModel.joints) { + + qCDebug(modelformat) << " parentIndex" << joint.parentIndex; + qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; + qCDebug(modelformat) << " translation" << joint.translation; + qCDebug(modelformat) << " preTransform" << joint.preTransform; + qCDebug(modelformat) << " preRotation" << joint.preRotation; + qCDebug(modelformat) << " rotation" << joint.rotation; + qCDebug(modelformat) << " postRotation" << joint.postRotation; + qCDebug(modelformat) << " postTransform" << joint.postTransform; + qCDebug(modelformat) << " transform" << joint.transform; + qCDebug(modelformat) << " rotationMin" << joint.rotationMin; + qCDebug(modelformat) << " rotationMax" << joint.rotationMax; + qCDebug(modelformat) << " inverseDefaultRotation" << joint.inverseDefaultRotation; + qCDebug(modelformat) << " inverseBindRotation" << joint.inverseBindRotation; + qCDebug(modelformat) << " bindTransform" << joint.bindTransform; + qCDebug(modelformat) << " name" << joint.name; + qCDebug(modelformat) << " isSkeletonJoint" << joint.isSkeletonJoint; + } + + qCDebug(modelformat) << "\n"; } diff --git a/libraries/fbx/src/OBJSerializer.h b/libraries/fbx/src/OBJSerializer.h index 462d32a119..6fdd95e2c3 100644 --- a/libraries/fbx/src/OBJSerializer.h +++ b/libraries/fbx/src/OBJSerializer.h @@ -120,5 +120,6 @@ private: // What are these utilities doing here? One is used by fbx loading code in VHACD Utils, and the other a general debugging utility. void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID); +void hfmDebugDump(const HFMModel& hfmModel); #endif // hifi_OBJSerializer_h diff --git a/libraries/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp index 500aaaa842..236445bfda 100644 --- a/libraries/hfm/src/hfm/HFM.cpp +++ b/libraries/hfm/src/hfm/HFM.cpp @@ -76,7 +76,7 @@ QStringList HFMModel::getJointNames() const { } bool HFMModel::hasBlendedMeshes() const { - if (!meshes.empty()) { + if (!meshes.isEmpty()) { foreach (const HFMMesh& mesh, meshes) { if (!mesh.blendshapes.isEmpty()) { return true; @@ -166,16 +166,16 @@ void HFMModel::computeKdops() { glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3) }; - if (joints.size() != shapeVertices.size()) { + if (joints.size() != (int)shapeVertices.size()) { return; } // now that all joints have been scanned compute a k-Dop bounding volume of mesh - for (size_t i = 0; i < joints.size(); ++i) { + for (int i = 0; i < joints.size(); ++i) { HFMJoint& joint = joints[i]; // NOTE: points are in joint-frame ShapeVertices& points = shapeVertices.at(i); - glm::quat rotOffset = jointRotationOffsets.contains((int)i) ? glm::inverse(jointRotationOffsets[(int)i]) : quat(); + glm::quat rotOffset = jointRotationOffsets.contains(i) ? glm::inverse(jointRotationOffsets[i]) : quat(); if (points.size() > 0) { // compute average point glm::vec3 avgPoint = glm::vec3(0.0f); @@ -208,164 +208,3 @@ void HFMModel::computeKdops() { } } } - -void hfm::Model::debugDump() const { - qCDebug(modelformat) << "---------------- hfmModel ----------------"; - qCDebug(modelformat) << " hasSkeletonJoints =" << hasSkeletonJoints; - qCDebug(modelformat) << " offset =" << offset; - - qCDebug(modelformat) << " neckPivot = " << neckPivot; - - qCDebug(modelformat) << " bindExtents.size() = " << bindExtents.size(); - qCDebug(modelformat) << " meshExtents.size() = " << meshExtents.size(); - - qCDebug(modelformat) << "---------------- Shapes ----------------"; - qCDebug(modelformat) << " shapes.size() =" << shapes.size(); - for (const hfm::Shape& shape : shapes) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " mesh =" << shape.mesh; - qCDebug(modelformat) << " meshPart =" << shape.meshPart; - qCDebug(modelformat) << " material =" << shape.material; - qCDebug(modelformat) << " joint =" << shape.joint; - qCDebug(modelformat) << " transformedExtents =" << shape.transformedExtents; - qCDebug(modelformat) << " skinDeformer =" << shape.skinDeformer; - } - - qCDebug(modelformat) << " jointIndices.size() =" << jointIndices.size(); - qCDebug(modelformat) << " joints.size() =" << joints.size(); - qCDebug(modelformat) << "---------------- Meshes ----------------"; - qCDebug(modelformat) << " meshes.size() =" << meshes.size(); - qCDebug(modelformat) << " blendshapeChannelNames = " << blendshapeChannelNames; - for (const HFMMesh& mesh : meshes) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " meshpointer =" << mesh._mesh.get(); - qCDebug(modelformat) << " meshindex =" << mesh.meshIndex; - qCDebug(modelformat) << " vertices.size() =" << mesh.vertices.size(); - qCDebug(modelformat) << " colors.size() =" << mesh.colors.size(); - qCDebug(modelformat) << " normals.size() =" << mesh.normals.size(); - qCDebug(modelformat) << " tangents.size() =" << mesh.tangents.size(); - qCDebug(modelformat) << " colors.size() =" << mesh.colors.size(); - qCDebug(modelformat) << " texCoords.size() =" << mesh.texCoords.size(); - qCDebug(modelformat) << " texCoords1.size() =" << mesh.texCoords1.size(); - qCDebug(modelformat) << " clusterIndices.size() =" << mesh.clusterIndices.size(); - qCDebug(modelformat) << " clusterWeights.size() =" << mesh.clusterWeights.size(); - qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; - qCDebug(modelformat) << " parts.size() =" << mesh.parts.size(); - qCDebug(modelformat) << "---------------- Meshes (blendshapes)--------"; - for (HFMBlendshape bshape : mesh.blendshapes) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " bshape.indices.size() =" << bshape.indices.size(); - qCDebug(modelformat) << " bshape.vertices.size() =" << bshape.vertices.size(); - qCDebug(modelformat) << " bshape.normals.size() =" << bshape.normals.size(); - qCDebug(modelformat) << "\n"; - } - qCDebug(modelformat) << "---------------- Meshes (meshparts)--------"; - for (HFMMeshPart meshPart : mesh.parts) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " quadIndices.size() =" << meshPart.quadIndices.size(); - qCDebug(modelformat) << " triangleIndices.size() =" << meshPart.triangleIndices.size(); - qCDebug(modelformat) << "\n"; - } - } - qCDebug(modelformat) << "---------------- AnimationFrames ----------------"; - for (HFMAnimationFrame anim : animationFrames) { - qCDebug(modelformat) << " anim.translations = " << anim.translations; - qCDebug(modelformat) << " anim.rotations = " << anim.rotations; - } - QList mitomona_keys = meshIndicesToModelNames.keys(); - for (int key : mitomona_keys) { - qCDebug(modelformat) << " meshIndicesToModelNames key =" << key - << " val =" << meshIndicesToModelNames[key]; - } - - qCDebug(modelformat) << "---------------- Materials ----------------"; - - for (HFMMaterial mat : materials) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " mat.materialID =" << mat.materialID; - qCDebug(modelformat) << " diffuseColor =" << mat.diffuseColor; - qCDebug(modelformat) << " diffuseFactor =" << mat.diffuseFactor; - qCDebug(modelformat) << " specularColor =" << mat.specularColor; - qCDebug(modelformat) << " specularFactor =" << mat.specularFactor; - qCDebug(modelformat) << " emissiveColor =" << mat.emissiveColor; - qCDebug(modelformat) << " emissiveFactor =" << mat.emissiveFactor; - qCDebug(modelformat) << " shininess =" << mat.shininess; - qCDebug(modelformat) << " opacity =" << mat.opacity; - qCDebug(modelformat) << " metallic =" << mat.metallic; - qCDebug(modelformat) << " roughness =" << mat.roughness; - qCDebug(modelformat) << " emissiveIntensity =" << mat.emissiveIntensity; - qCDebug(modelformat) << " ambientFactor =" << mat.ambientFactor; - - qCDebug(modelformat) << " materialID =" << mat.materialID; - qCDebug(modelformat) << " name =" << mat.name; - qCDebug(modelformat) << " shadingModel =" << mat.shadingModel; - qCDebug(modelformat) << " _material =" << mat._material.get(); - - qCDebug(modelformat) << " normalTexture =" << mat.normalTexture.filename; - qCDebug(modelformat) << " albedoTexture =" << mat.albedoTexture.filename; - qCDebug(modelformat) << " opacityTexture =" << mat.opacityTexture.filename; - - qCDebug(modelformat) << " lightmapParams =" << mat.lightmapParams; - - qCDebug(modelformat) << " isPBSMaterial =" << mat.isPBSMaterial; - qCDebug(modelformat) << " useNormalMap =" << mat.useNormalMap; - qCDebug(modelformat) << " useAlbedoMap =" << mat.useAlbedoMap; - qCDebug(modelformat) << " useOpacityMap =" << mat.useOpacityMap; - qCDebug(modelformat) << " useRoughnessMap =" << mat.useRoughnessMap; - qCDebug(modelformat) << " useSpecularMap =" << mat.useSpecularMap; - qCDebug(modelformat) << " useMetallicMap =" << mat.useMetallicMap; - qCDebug(modelformat) << " useEmissiveMap =" << mat.useEmissiveMap; - qCDebug(modelformat) << " useOcclusionMap =" << mat.useOcclusionMap; - qCDebug(modelformat) << "\n"; - } - - qCDebug(modelformat) << "---------------- Joints ----------------"; - - for (const HFMJoint& joint : joints) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " shapeInfo.avgPoint =" << joint.shapeInfo.avgPoint; - qCDebug(modelformat) << " shapeInfo.debugLines =" << joint.shapeInfo.debugLines; - qCDebug(modelformat) << " shapeInfo.dots =" << joint.shapeInfo.dots; - qCDebug(modelformat) << " shapeInfo.points =" << joint.shapeInfo.points; - - qCDebug(modelformat) << " ---"; - - qCDebug(modelformat) << " parentIndex" << joint.parentIndex; - qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; - qCDebug(modelformat) << " localTransform" << joint.localTransform; - qCDebug(modelformat) << " transform" << joint.transform; - qCDebug(modelformat) << " globalTransform" << joint.globalTransform; - - qCDebug(modelformat) << " ---"; - - qCDebug(modelformat) << " translation" << joint.translation; - qCDebug(modelformat) << " preTransform" << joint.preTransform; - qCDebug(modelformat) << " preRotation" << joint.preRotation; - qCDebug(modelformat) << " rotation" << joint.rotation; - qCDebug(modelformat) << " postRotation" << joint.postRotation; - qCDebug(modelformat) << " postTransform" << joint.postTransform; - qCDebug(modelformat) << " rotationMin" << joint.rotationMin; - qCDebug(modelformat) << " rotationMax" << joint.rotationMax; - qCDebug(modelformat) << " inverseDefaultRotation" << joint.inverseDefaultRotation; - qCDebug(modelformat) << " inverseBindRotation" << joint.inverseBindRotation; - qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.bindTransformFoundInCluster; - qCDebug(modelformat) << " bindTransform" << joint.bindTransform; - qCDebug(modelformat) << " name" << joint.name; - qCDebug(modelformat) << " isSkeletonJoint" << joint.isSkeletonJoint; - qCDebug(modelformat) << " geometricOffset" << joint.geometricOffset; - qCDebug(modelformat) << "\n"; - } - - qCDebug(modelformat) << "------------- SkinDeformers ------------"; - qCDebug(modelformat) << " skinDeformers.size() =" << skinDeformers.size(); - for(const hfm::SkinDeformer& skinDeformer : skinDeformers) { - qCDebug(modelformat) << "------- SkinDeformers (Clusters) -------"; - for (const hfm::Cluster& cluster : skinDeformer.clusters) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; - qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; - qCDebug(modelformat) << "\n"; - } - } - qCDebug(modelformat) << "\n"; -} diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index ca73676f86..7111ad2e65 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -66,8 +66,6 @@ static const int DRACO_ATTRIBUTE_ORIGINAL_INDEX = DRACO_BEGIN_CUSTOM_HIFI_ATTRIB // High Fidelity Model namespace namespace hfm { -static const uint32_t UNDEFINED_KEY = (uint32_t)-1; - /// A single blendshape. class Blendshape { public: @@ -113,22 +111,19 @@ public: bool isSkeletonJoint; bool bindTransformFoundInCluster; - // geometric offset is applied in local space but does NOT affect children. - // TODO: Apply hfm::Joint.geometricOffset to transforms in the model preparation step - glm::mat4 geometricOffset; - - // globalTransform is the transform of the joint with all parent transforms applied, plus the geometric offset - glm::mat4 localTransform; - glm::mat4 globalTransform; + bool hasGeometricOffset; + glm::vec3 geometricTranslation; + glm::quat geometricRotation; + glm::vec3 geometricScaling; }; /// A single binding to a joint. class Cluster { public: - static const uint32_t INVALID_JOINT_INDEX { (uint32_t)-1 }; - uint32_t jointIndex { INVALID_JOINT_INDEX }; + + int jointIndex; glm::mat4 inverseBindMatrix; Transform inverseBindTransform; }; @@ -160,6 +155,8 @@ public: QVector quadIndices; // original indices from the FBX mesh QVector quadTrianglesIndices; // original indices from the FBX mesh of the quad converted as triangles QVector triangleIndices; // original indices from the FBX mesh + + QString materialID; }; class Material { @@ -230,20 +227,11 @@ public: bool needTangentSpace() const; }; - -/// Simple Triangle List Mesh -struct TriangleListMesh { - std::vector vertices; - std::vector indices; - std::vector parts; // Offset in the indices, Number of indices - std::vector partExtents; // Extents of each part with no transform applied. Same length as parts. -}; - /// A single mesh (with optional blendshapes). class Mesh { public: - std::vector parts; + QVector parts; QVector vertices; QVector normals; @@ -251,27 +239,21 @@ public: QVector colors; QVector texCoords; QVector texCoords1; + QVector clusterIndices; + QVector clusterWeights; + QVector originalIndices; - Extents meshExtents; // DEPRECATED (see hfm::Shape::transformedExtents) - glm::mat4 modelTransform; // DEPRECATED (see hfm::Joint::globalTransform, hfm::Shape::transform, hfm::Model::joints) + QVector clusters; - // Skinning cluster attributes - std::vector clusterIndices; - std::vector clusterWeights; - uint16_t clusterWeightsPerVertex { 0 }; + Extents meshExtents; + glm::mat4 modelTransform; - // Blendshape attributes QVector blendshapes; - // Simple Triangle List Mesh generated during baking - hfm::TriangleListMesh triangleListMesh; - - QVector originalIndices; // Original indices of the vertices unsigned int meshIndex; // the order the meshes appeared in the object file graphics::MeshPointer _mesh; bool wasCompressed { false }; - }; /// A single animation frame. @@ -308,30 +290,6 @@ public: bool shouldInitCollisions() const { return _collisionsConfig.size() > 0; } }; -// A different skinning representation, used by FBXSerializer. We convert this to our graphics-optimized runtime representation contained within the mesh. -class SkinCluster { -public: - std::vector indices; - std::vector weights; -}; - -class SkinDeformer { -public: - std::vector clusters; -}; - -// The lightweight model part description. -class Shape { -public: - uint32_t mesh { UNDEFINED_KEY }; - uint32_t meshPart { UNDEFINED_KEY }; - uint32_t material { UNDEFINED_KEY }; - uint32_t joint { UNDEFINED_KEY }; // The hfm::Joint associated with this shape, containing transform information - // TODO: Have all serializers calculate hfm::Shape::transformedExtents in world space where they previously calculated hfm::Mesh::meshExtents. Change all code that uses hfm::Mesh::meshExtents to use this instead. - Extents transformedExtents; // The precise extents of the meshPart vertices in world space, after transform information is applied, while not taking into account rigging/skinning - uint32_t skinDeformer { UNDEFINED_KEY }; -}; - /// The runtime model format. class Model { public: @@ -342,18 +300,15 @@ public: QString author; QString applicationName; ///< the name of the application that generated the model - std::vector shapes; - - std::vector meshes; - std::vector materials; - - std::vector skinDeformers; - - std::vector joints; + QVector joints; QHash jointIndices; ///< 1-based, so as to more easily detect missing indices bool hasSkeletonJoints; + + QVector meshes; QVector scripts; + QHash materials; + glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file glm::vec3 neckPivot; @@ -385,12 +340,19 @@ public: QMap jointRotationOffsets; std::vector shapeVertices; FlowData flowData; - - void debugDump() const; }; }; +class ExtractedMesh { +public: + hfm::Mesh mesh; + QMultiHash newIndices; + QVector > blendshapeIndexMaps; + QVector > partMaterialTextures; + QHash texcoordSetMap; +}; + typedef hfm::Blendshape HFMBlendshape; typedef hfm::JointShapeInfo HFMJointShapeInfo; typedef hfm::Joint HFMJoint; @@ -399,10 +361,8 @@ typedef hfm::Texture HFMTexture; typedef hfm::MeshPart HFMMeshPart; typedef hfm::Material HFMMaterial; typedef hfm::Mesh HFMMesh; -typedef hfm::SkinDeformer HFMSkinDeformer; typedef hfm::AnimationFrame HFMAnimationFrame; typedef hfm::Light HFMLight; -typedef hfm::Shape HFMShape; typedef hfm::Model HFMModel; typedef hfm::FlowData FlowData; diff --git a/libraries/hfm/src/hfm/HFMModelMath.cpp b/libraries/hfm/src/hfm/HFMModelMath.cpp deleted file mode 100644 index 436e520643..0000000000 --- a/libraries/hfm/src/hfm/HFMModelMath.cpp +++ /dev/null @@ -1,212 +0,0 @@ -// -// HFMModelMath.cpp -// model-baker/src/model-baker -// -// Created by Sabrina Shanman on 2019/10/04. -// 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 "HFMModelMath.h" - -#include - -#include - -#include -#include - -namespace hfm { - -void forEachIndex(const hfm::MeshPart& meshPart, std::function func) { - for (int i = 0; i <= meshPart.quadIndices.size() - 4; i += 4) { - func((uint32_t)meshPart.quadIndices[i]); - func((uint32_t)meshPart.quadIndices[i+1]); - func((uint32_t)meshPart.quadIndices[i+2]); - func((uint32_t)meshPart.quadIndices[i+3]); - } - for (int i = 0; i <= meshPart.triangleIndices.size() - 3; i += 3) { - func((uint32_t)meshPart.triangleIndices[i]); - func((uint32_t)meshPart.triangleIndices[i+1]); - func((uint32_t)meshPart.triangleIndices[i+2]); - } -} - -void thickenFlatExtents(Extents& extents) { - // Add epsilon to extents to compensate for flat plane - extents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); - extents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); -} - -void calculateExtentsForTriangleListMesh(TriangleListMesh& triangleListMesh) { - triangleListMesh.partExtents.resize(triangleListMesh.parts.size()); - for (size_t partIndex = 0; partIndex < triangleListMesh.parts.size(); ++partIndex) { - const auto& part = triangleListMesh.parts[partIndex]; - auto& extents = triangleListMesh.partExtents[partIndex]; - int partEnd = part.x + part.y; - for (int i = part.x; i < partEnd; ++i) { - auto index = triangleListMesh.indices[i]; - const auto& position = triangleListMesh.vertices[index]; - extents.addPoint(position); - } - } -} - -void calculateExtentsForShape(hfm::Shape& shape, const std::vector& triangleListMeshes, const std::vector& joints) { - auto& shapeExtents = shape.transformedExtents; - shapeExtents.reset(); - - const auto& triangleListMesh = triangleListMeshes[shape.mesh]; - const auto& partExtent = triangleListMesh.partExtents[shape.meshPart]; - - const glm::mat4& transform = joints[shape.joint].transform; - shapeExtents = partExtent; - shapeExtents.transform(transform); - - thickenFlatExtents(shapeExtents); -} - -void calculateExtentsForModel(Extents& modelExtents, const std::vector& shapes) { - modelExtents.reset(); - - for (size_t i = 0; i < shapes.size(); ++i) { - const auto& shape = shapes[i]; - const auto& shapeExtents = shape.transformedExtents; - modelExtents.addExtents(shapeExtents); - } -} - -ReweightedDeformers getReweightedDeformers(const size_t numMeshVertices, const std::vector skinClusters, const uint16_t weightsPerVertex) { - ReweightedDeformers reweightedDeformers; - if (skinClusters.size() == 0) { - return reweightedDeformers; - } - - size_t numClusterIndices = numMeshVertices * weightsPerVertex; - reweightedDeformers.indices.resize(numClusterIndices, (uint16_t)(skinClusters.size() - 1)); - reweightedDeformers.weights.resize(numClusterIndices, 0); - reweightedDeformers.weightsPerVertex = weightsPerVertex; - - std::vector weightAccumulators; - weightAccumulators.resize(numClusterIndices, 0.0f); - for (uint16_t i = 0; i < (uint16_t)skinClusters.size(); ++i) { - const hfm::SkinCluster& skinCluster = skinClusters[i]; - - if (skinCluster.indices.size() != skinCluster.weights.size()) { - reweightedDeformers.trimmedToMatch = true; - } - size_t numIndicesOrWeights = std::min(skinCluster.indices.size(), skinCluster.weights.size()); - for (size_t j = 0; j < numIndicesOrWeights; ++j) { - uint32_t index = skinCluster.indices[j]; - float weight = skinCluster.weights[j]; - - // look for an unused slot in the weights vector - uint32_t weightIndex = index * weightsPerVertex; - uint32_t lowestIndex = -1; - float lowestWeight = FLT_MAX; - uint16_t k = 0; - for (; k < weightsPerVertex; k++) { - if (weightAccumulators[weightIndex + k] == 0.0f) { - reweightedDeformers.indices[weightIndex + k] = i; - weightAccumulators[weightIndex + k] = weight; - break; - } - if (weightAccumulators[weightIndex + k] < lowestWeight) { - lowestIndex = k; - lowestWeight = weightAccumulators[weightIndex + k]; - } - } - if (k == weightsPerVertex && weight > lowestWeight) { - // no space for an additional weight; we must replace the lowest - weightAccumulators[weightIndex + lowestIndex] = weight; - reweightedDeformers.indices[weightIndex + lowestIndex] = i; - } - } - } - - // now that we've accumulated the most relevant weights for each vertex - // normalize and compress to 16-bits - for (size_t i = 0; i < numMeshVertices; ++i) { - size_t j = i * weightsPerVertex; - - // normalize weights into uint16_t - float totalWeight = 0.0f; - for (size_t k = j; k < j + weightsPerVertex; ++k) { - totalWeight += weightAccumulators[k]; - } - - const float ALMOST_HALF = 0.499f; - if (totalWeight > 0.0f) { - float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; - for (size_t k = j; k < j + weightsPerVertex; ++k) { - reweightedDeformers.weights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF); - } - } else { - reweightedDeformers.weights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); - } - } - - return reweightedDeformers; -} - -const TriangleListMesh generateTriangleListMesh(const std::vector& srcVertices, const std::vector& srcParts) { - - TriangleListMesh dest; - - // copy vertices for now - dest.vertices = srcVertices; - - std::vector oldToNewIndex(srcVertices.size()); - { - std::unordered_map uniqueVertexToNewIndex; - int oldIndex = 0; - int newIndex = 0; - for (const auto& srcVertex : srcVertices) { - auto foundIndex = uniqueVertexToNewIndex.find(srcVertex); - if (foundIndex != uniqueVertexToNewIndex.end()) { - oldToNewIndex[oldIndex] = foundIndex->second; - } else { - uniqueVertexToNewIndex[srcVertex] = newIndex; - oldToNewIndex[oldIndex] = newIndex; - dest.vertices[newIndex] = srcVertex; - ++newIndex; - } - ++oldIndex; - } - if (uniqueVertexToNewIndex.size() < srcVertices.size()) { - dest.vertices.resize(uniqueVertexToNewIndex.size()); - dest.vertices.shrink_to_fit(); - } - } - - auto newIndicesCount = 0; - for (const auto& part : srcParts) { - newIndicesCount += part.triangleIndices.size() + part.quadTrianglesIndices.size(); - } - - { - dest.indices.resize(newIndicesCount); - int i = 0; - for (const auto& part : srcParts) { - glm::ivec2 spart(i, 0); - for (const auto& qti : part.quadTrianglesIndices) { - dest.indices[i] = oldToNewIndex[qti]; - ++i; - } - for (const auto& ti : part.triangleIndices) { - dest.indices[i] = oldToNewIndex[ti]; - ++i; - } - spart.y = i - spart.x; - dest.parts.push_back(spart); - } - } - - calculateExtentsForTriangleListMesh(dest); - - return dest; -} - -}; diff --git a/libraries/hfm/src/hfm/HFMModelMath.h b/libraries/hfm/src/hfm/HFMModelMath.h deleted file mode 100644 index ef86e7379a..0000000000 --- a/libraries/hfm/src/hfm/HFMModelMath.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// HFMModelMath.h -// model-baker/src/model-baker -// -// Created by Sabrina Shanman on 2019/10/04. -// 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_hfm_ModelMath_h -#define hifi_hfm_ModelMath_h - -#include "HFM.h" - -namespace hfm { - -void forEachIndex(const hfm::MeshPart& meshPart, std::function func); - -void initializeExtents(Extents& extents); - -void calculateExtentsForTriangleListMesh(TriangleListMesh& triangleListMesh); - -// This can't be moved to model-baker until -void calculateExtentsForShape(hfm::Shape& shape, const std::vector& triangleListMeshes, const std::vector& joints); - -void calculateExtentsForModel(Extents& modelExtents, const std::vector& shapes); - -struct ReweightedDeformers { - std::vector indices; - std::vector weights; - uint16_t weightsPerVertex { 0 }; - bool trimmedToMatch { false }; -}; - -const uint16_t DEFAULT_SKINNING_WEIGHTS_PER_VERTEX = 4; - -ReweightedDeformers getReweightedDeformers(const size_t numMeshVertices, const std::vector skinClusters, const uint16_t weightsPerVertex = DEFAULT_SKINNING_WEIGHTS_PER_VERTEX); - -const TriangleListMesh generateTriangleListMesh(const std::vector& srcVertices, const std::vector& srcParts); - -}; - -#endif // #define hifi_hfm_ModelMath_h diff --git a/libraries/hfm/src/hfm/HFMSerializer.h b/libraries/hfm/src/hfm/HFMSerializer.h index f28ef9f9c3..d0be588d60 100644 --- a/libraries/hfm/src/hfm/HFMSerializer.h +++ b/libraries/hfm/src/hfm/HFMSerializer.h @@ -1,5 +1,5 @@ // -// HFMSerializer.h +// FBXSerializer.h // libraries/hfm/src/hfm // // Created by Sabrina Shanman on 2018/11/07. diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index d200df211d..47a8db82b8 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -13,61 +13,34 @@ #include "BakerTypes.h" #include "ModelMath.h" -#include "CollectShapeVerticesTask.h" #include "BuildGraphicsMeshTask.h" #include "CalculateMeshNormalsTask.h" #include "CalculateMeshTangentsTask.h" #include "CalculateBlendshapeNormalsTask.h" #include "CalculateBlendshapeTangentsTask.h" #include "PrepareJointsTask.h" -#include "CalculateTransformedExtentsTask.h" #include "BuildDracoMeshTask.h" #include "ParseFlowDataTask.h" -#include namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet9, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, std::vector, std::vector, std::vector, Extents, std::vector>; + using Output = VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, std::vector>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { const auto& hfmModelIn = input; - output.edit0() = hfmModelIn->meshes; + output.edit0() = hfmModelIn->meshes.toStdVector(); output.edit1() = hfmModelIn->originalURL; output.edit2() = hfmModelIn->meshIndicesToModelNames; auto& blendshapesPerMesh = output.edit3(); blendshapesPerMesh.reserve(hfmModelIn->meshes.size()); - for (size_t i = 0; i < hfmModelIn->meshes.size(); i++) { + for (int i = 0; i < hfmModelIn->meshes.size(); i++) { blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); } - output.edit4() = hfmModelIn->joints; - output.edit5() = hfmModelIn->shapes; - output.edit6() = hfmModelIn->skinDeformers; - output.edit7() = hfmModelIn->meshExtents; - output.edit8() = hfmModelIn->materials; - } - }; - - class BuildMeshTriangleListTask { - public: - using Input = std::vector; - using Output = std::vector; - using JobModel = Job::ModelIO; - - void run(const BakeContextPointer& context, const Input& input, Output& output) { - const auto& meshesIn = input; - auto& indexedTrianglesMeshOut = output; - indexedTrianglesMeshOut.clear(); - indexedTrianglesMeshOut.resize(meshesIn.size()); - - for (size_t i = 0; i < meshesIn.size(); i++) { - auto& mesh = meshesIn[i]; - const auto verticesStd = mesh.vertices.toStdVector(); - indexedTrianglesMeshOut[i] = hfm::generateTriangleListMesh(verticesStd, mesh.parts); - } + output.edit4() = hfmModelIn->joints.toStdVector(); } }; @@ -102,23 +75,21 @@ namespace baker { class BuildMeshesTask { public: - using Input = VaryingSet6, std::vector, std::vector, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>; + using Input = VaryingSet5, std::vector, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>; using Output = std::vector; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { auto& meshesIn = input.get0(); int numMeshes = (int)meshesIn.size(); - auto& triangleListMeshesIn = input.get1(); - auto& graphicsMeshesIn = input.get2(); - auto& normalsPerMeshIn = input.get3(); - auto& tangentsPerMeshIn = input.get4(); - auto& blendshapesPerMeshIn = input.get5(); + auto& graphicsMeshesIn = input.get1(); + auto& normalsPerMeshIn = input.get2(); + auto& tangentsPerMeshIn = input.get3(); + auto& blendshapesPerMeshIn = input.get4(); auto meshesOut = meshesIn; for (int i = 0; i < numMeshes; i++) { auto& meshOut = meshesOut[i]; - meshOut.triangleListMesh = triangleListMeshesIn[i]; meshOut._mesh = safeGet(graphicsMeshesIn, i); meshOut.normals = QVector::fromStdVector(safeGet(normalsPerMeshIn, i)); meshOut.tangents = QVector::fromStdVector(safeGet(tangentsPerMeshIn, i)); @@ -130,22 +101,17 @@ namespace baker { class BuildModelTask { public: - using Input = VaryingSet9, std::vector, QMap, QHash, FlowData, std::vector, std::vector, Extents>; + using Input = VaryingSet6, std::vector, QMap, QHash, FlowData>; 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 = input.get1(); - hfmModelOut->joints = input.get2(); + hfmModelOut->meshes = QVector::fromStdVector(input.get1()); + hfmModelOut->joints = QVector::fromStdVector(input.get2()); hfmModelOut->jointRotationOffsets = input.get3(); hfmModelOut->jointIndices = input.get4(); hfmModelOut->flowData = input.get5(); - hfmModelOut->shapeVertices = input.get6(); - hfmModelOut->shapes = input.get7(); - hfmModelOut->meshExtents = input.get8(); - // These depend on the ShapeVertices - // TODO: Create a task for this rather than calculating it here hfmModelOut->computeKdops(); output = hfmModelOut; } @@ -168,10 +134,6 @@ namespace baker { const auto meshIndicesToModelNames = modelPartsIn.getN(2); const auto blendshapesPerMeshIn = modelPartsIn.getN(3); const auto jointsIn = modelPartsIn.getN(4); - const auto shapesIn = modelPartsIn.getN(5); - const auto skinDeformersIn = modelPartsIn.getN(6); - const auto modelExtentsIn = modelPartsIn.getN(7); - const auto materialsIn = modelPartsIn.getN(8); // 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. @@ -183,15 +145,8 @@ namespace baker { const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn).asVarying(); const auto tangentsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs); - // Calculate shape vertices. These rely on the weight-normalized clusterIndices/clusterWeights in the mesh, and are used later for computing the joint kdops - const auto collectShapeVerticesInputs = CollectShapeVerticesTask::Input(meshesIn, shapesIn, jointsIn, skinDeformersIn).asVarying(); - const auto shapeVerticesPerJoint = model.addJob("CollectShapeVertices", collectShapeVerticesInputs); - - // Build the slim triangle list mesh for each hfm::mesh - const auto triangleListMeshes = model.addJob("BuildMeshTriangleListTask", meshesIn); - // Build the graphics::MeshPointer for each hfm::Mesh - const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh, shapesIn, skinDeformersIn).asVarying(); + const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying(); const auto graphicsMeshes = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); // Prepare joint information @@ -201,12 +156,6 @@ namespace baker { const auto jointRotationOffsets = jointInfoOut.getN(1); const auto jointIndices = jointInfoOut.getN(2); - // Use transform information to compute extents - const auto calculateExtentsInputs = CalculateTransformedExtentsTask::Input(modelExtentsIn, triangleListMeshes, shapesIn, jointsOut).asVarying(); - const auto calculateExtentsOutputs = model.addJob("CalculateExtents", calculateExtentsInputs); - const auto modelExtentsOut = calculateExtentsOutputs.getN(0); - const auto shapesOut = calculateExtentsOutputs.getN(1); - // Parse material mapping const auto parseMaterialMappingInputs = ParseMaterialMappingTask::Input(mapping, materialMappingBaseURL).asVarying(); const auto materialMapping = model.addJob("ParseMaterialMapping", parseMaterialMappingInputs); @@ -216,7 +165,7 @@ namespace baker { // TODO: Tangent support (Needs changes to FBXSerializer_Mesh as well) // NOTE: Due to an unresolved linker error, BuildDracoMeshTask is not functional on Android // TODO: Figure out why BuildDracoMeshTask.cpp won't link with draco on Android - const auto buildDracoMeshInputs = BuildDracoMeshTask::Input(shapesOut, meshesIn, materialsIn, normalsPerMesh, tangentsPerMesh).asVarying(); + const auto buildDracoMeshInputs = BuildDracoMeshTask::Input(meshesIn, normalsPerMesh, tangentsPerMesh).asVarying(); const auto buildDracoMeshOutputs = model.addJob("BuildDracoMesh", buildDracoMeshInputs); const auto dracoMeshes = buildDracoMeshOutputs.getN(0); const auto dracoErrors = buildDracoMeshOutputs.getN(1); @@ -228,9 +177,9 @@ namespace baker { // 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, triangleListMeshes, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); + 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, jointsOut, jointRotationOffsets, jointIndices, flowData, shapeVerticesPerJoint, shapesOut, modelExtentsOut).asVarying(); + const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices, flowData).asVarying(); const auto hfmModelOut = model.addJob("BuildModel", buildModelInputs); output = Output(hfmModelOut, materialMapping, dracoMeshes, dracoErrors, materialList); diff --git a/libraries/model-baker/src/model-baker/BakerTypes.h b/libraries/model-baker/src/model-baker/BakerTypes.h index 8760fa6db4..3d16afab2e 100644 --- a/libraries/model-baker/src/model-baker/BakerTypes.h +++ b/libraries/model-baker/src/model-baker/BakerTypes.h @@ -36,14 +36,6 @@ namespace baker { using TangentsPerBlendshape = std::vector>; using MeshIndicesToModelNames = QHash; - - class ReweightedDeformers { - public: - std::vector indices; - std::vector weights; - uint16_t weightsPerVertex { 0 }; - bool trimmedToMatch { false }; - }; }; #endif // hifi_BakerTypes_h diff --git a/libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp index 5c9d1dac25..12347c30b1 100644 --- a/libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp @@ -39,47 +39,19 @@ #include "ModelMath.h" #ifndef Q_OS_ANDROID - -void reindexMaterials(const std::vector& originalMaterialIndices, std::vector& materials, std::vector& materialIndices) { - materialIndices.resize(originalMaterialIndices.size()); - for (size_t i = 0; i < originalMaterialIndices.size(); ++i) { - uint32_t material = originalMaterialIndices[i]; - auto foundMaterial = std::find(materials.cbegin(), materials.cend(), material); - if (foundMaterial == materials.cend()) { - materials.push_back(material); - materialIndices[i] = (uint16_t)(materials.size() - 1); - } else { - materialIndices[i] = (uint16_t)(foundMaterial - materials.cbegin()); +std::vector createMaterialList(const hfm::Mesh& mesh) { + std::vector materialList; + for (const auto& meshPart : mesh.parts) { + auto materialID = QVariant(meshPart.materialID).toByteArray(); + const auto materialIt = std::find(materialList.cbegin(), materialList.cend(), materialID); + if (materialIt == materialList.cend()) { + materialList.push_back(materialID); } } + return materialList; } -void createMaterialLists(const std::vector& shapes, const std::vector& meshes, const std::vector& hfmMaterials, std::vector>& materialIndexLists, std::vector>& partMaterialIndicesPerMesh) { - std::vector> materialsPerMesh; - for (const auto& mesh : meshes) { - materialsPerMesh.emplace_back(mesh.parts.size(), hfm::UNDEFINED_KEY); - } - for (const auto& shape : shapes) { - materialsPerMesh[shape.mesh][shape.meshPart] = shape.material; - } - - materialIndexLists.resize(materialsPerMesh.size()); - partMaterialIndicesPerMesh.resize(materialsPerMesh.size()); - for (size_t i = 0; i < materialsPerMesh.size(); ++i) { - const std::vector& materials = materialsPerMesh[i]; - std::vector uniqueMaterials; - - reindexMaterials(materials, uniqueMaterials, partMaterialIndicesPerMesh[i]); - - materialIndexLists[i].reserve(uniqueMaterials.size()); - for (const uint32_t material : uniqueMaterials) { - const auto& hfmMaterial = hfmMaterials[material]; - materialIndexLists[i].push_back(QVariant(hfmMaterial.materialID).toByteArray()); - } - } -} - -std::tuple, bool> createDracoMesh(const hfm::Mesh& mesh, const std::vector& normals, const std::vector& tangents, const std::vector& partMaterialIndices) { +std::tuple, bool> createDracoMesh(const hfm::Mesh& mesh, const std::vector& normals, const std::vector& tangents, const std::vector& materialList) { Q_ASSERT(normals.size() == 0 || (int)normals.size() == mesh.vertices.size()); Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size()); Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size()); @@ -150,9 +122,11 @@ std::tuple, bool> createDracoMesh(const hfm::Mesh& auto partIndex = 0; draco::FaceIndex face; + uint16_t materialID; for (auto& part : mesh.parts) { - uint16_t materialID = partMaterialIndices[partIndex]; + auto materialIt = std::find(materialList.cbegin(), materialList.cend(), QVariant(part.materialID).toByteArray()); + materialID = (uint16_t)(materialIt - materialList.cbegin()); auto addFace = [&](const QVector& indices, int index, draco::FaceIndex face) { int32_t idx0 = indices[index]; @@ -240,33 +214,30 @@ void BuildDracoMeshTask::run(const baker::BakeContextPointer& context, const Inp #ifdef Q_OS_ANDROID qCWarning(model_baker) << "BuildDracoMesh is disabled on Android. Output meshes will be empty."; #else - const auto& shapes = input.get0(); - const auto& meshes = input.get1(); - const auto& materials = input.get2(); - const auto& normalsPerMesh = input.get3(); - const auto& tangentsPerMesh = input.get4(); + const auto& meshes = input.get0(); + const auto& normalsPerMesh = input.get1(); + const auto& tangentsPerMesh = input.get2(); auto& dracoBytesPerMesh = output.edit0(); auto& dracoErrorsPerMesh = output.edit1(); - auto& materialLists = output.edit2(); - std::vector> partMaterialIndicesPerMesh; - createMaterialLists(shapes, meshes, materials, materialLists, partMaterialIndicesPerMesh); dracoBytesPerMesh.reserve(meshes.size()); // vector is an exception to the std::vector conventions as it is a bit field // So a bool reference to an element doesn't work dracoErrorsPerMesh.resize(meshes.size()); + materialLists.reserve(meshes.size()); for (size_t i = 0; i < meshes.size(); i++) { const auto& mesh = meshes[i]; const auto& normals = baker::safeGet(normalsPerMesh, i); const auto& tangents = baker::safeGet(tangentsPerMesh, i); dracoBytesPerMesh.emplace_back(); auto& dracoBytes = dracoBytesPerMesh.back(); - const auto& partMaterialIndices = partMaterialIndicesPerMesh[i]; + materialLists.push_back(createMaterialList(mesh)); + const auto& materialList = materialLists.back(); bool dracoError; std::unique_ptr dracoMesh; - std::tie(dracoMesh, dracoError) = createDracoMesh(mesh, normals, tangents, partMaterialIndices); + std::tie(dracoMesh, dracoError) = createDracoMesh(mesh, normals, tangents, materialList); dracoErrorsPerMesh[i] = dracoError; if (dracoMesh) { diff --git a/libraries/model-baker/src/model-baker/BuildDracoMeshTask.h b/libraries/model-baker/src/model-baker/BuildDracoMeshTask.h index a83f2ae163..ac9ad648ab 100644 --- a/libraries/model-baker/src/model-baker/BuildDracoMeshTask.h +++ b/libraries/model-baker/src/model-baker/BuildDracoMeshTask.h @@ -33,7 +33,7 @@ public: class BuildDracoMeshTask { public: using Config = BuildDracoMeshConfig; - using Input = baker::VaryingSet5, std::vector, std::vector, baker::NormalsPerMesh, baker::TangentsPerMesh>; + using Input = baker::VaryingSet3, baker::NormalsPerMesh, baker::TangentsPerMesh>; using Output = baker::VaryingSet3, std::vector, std::vector>>; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp index 66429ed2c4..2467da7656 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp @@ -2,8 +2,8 @@ // BuildGraphicsMeshTask.h // model-baker/src/model-baker // -// Created by Sabrina Shanman on 2019/09/16. -// Copyright 2019 High Fidelity, Inc. +// Created by Sabrina Shanman on 2018/12/06. +// Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -15,7 +15,6 @@ #include #include "ModelBakerLogging.h" -#include #include "ModelMath.h" using vec2h = glm::tvec2; @@ -28,7 +27,7 @@ glm::vec3 normalizeDirForPacking(const glm::vec3& dir) { return dir; } -void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn, uint16_t numDeformerControllers) { +void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn) { auto graphicsMesh = std::make_shared(); // Fill tangents with a dummy value to force tangents to be present if there are normals @@ -87,24 +86,25 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics // Support for 4 skinning clusters: // 4 Indices are uint8 ideally, uint16 if more than 256. - const auto clusterIndiceElement = ((numDeformerControllers < (uint16_t)UINT8_MAX) ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); + const auto clusterIndiceElement = (hfmMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); // 4 Weights are normalized 16bits const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW); - // Record cluster sizes - const size_t numVertClusters = hfmMesh.clusterWeightsPerVertex == 0 ? 0 : hfmMesh.clusterIndices.size() / hfmMesh.clusterWeightsPerVertex; - const size_t clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); - const size_t clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); + // Cluster indices and weights must be the same sizes + const int NUM_CLUSTERS_PER_VERT = 4; + const int numVertClusters = (hfmMesh.clusterIndices.size() == hfmMesh.clusterWeights.size() ? hfmMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); + const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); + const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); // Decide on where to put what seequencially in a big buffer: - const size_t positionsOffset = 0; - const size_t normalsAndTangentsOffset = positionsOffset + positionsSize; - const size_t colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; - const size_t texCoordsOffset = colorsOffset + colorsSize; - const size_t texCoords1Offset = texCoordsOffset + texCoordsSize; - const size_t clusterIndicesOffset = texCoords1Offset + texCoords1Size; - const size_t clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; - const size_t totalVertsSize = clusterWeightsOffset + clusterWeightsSize; + const int positionsOffset = 0; + const int normalsAndTangentsOffset = positionsOffset + positionsSize; + const int colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; + const int texCoordsOffset = colorsOffset + colorsSize; + const int texCoords1Offset = texCoordsOffset + texCoordsSize; + const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; + const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; + const int totalVertsSize = clusterWeightsOffset + clusterWeightsSize; // Copy all vertex data in a single buffer auto vertBuffer = std::make_shared(); @@ -181,22 +181,22 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics // Clusters data if (clusterIndicesSize > 0) { - if (numDeformerControllers < (uint16_t)UINT8_MAX) { + if (hfmMesh.clusters.size() < UINT8_MAX) { // yay! we can fit the clusterIndices within 8-bits - int32_t numIndices = (int32_t)hfmMesh.clusterIndices.size(); - std::vector packedDeformerIndices; - packedDeformerIndices.resize(numIndices); + int32_t numIndices = hfmMesh.clusterIndices.size(); + QVector clusterIndices; + clusterIndices.resize(numIndices); for (int32_t i = 0; i < numIndices; ++i) { assert(hfmMesh.clusterIndices[i] <= UINT8_MAX); - packedDeformerIndices[i] = (uint8_t)(hfmMesh.clusterIndices[i]); + clusterIndices[i] = (uint8_t)(hfmMesh.clusterIndices[i]); } - vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) packedDeformerIndices.data()); + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); } else { - vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) hfmMesh.clusterIndices.data()); + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) hfmMesh.clusterIndices.constData()); } } if (clusterWeightsSize > 0) { - vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) hfmMesh.clusterWeights.data()); + vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) hfmMesh.clusterWeights.constData()); } @@ -206,7 +206,7 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics auto vertexBufferStream = std::make_shared(); gpu::BufferPointer attribBuffer; - size_t totalAttribBufferSize = totalVertsSize; + int totalAttribBufferSize = totalVertsSize; gpu::uint8 posChannel = 0; gpu::uint8 tangentChannel = posChannel; gpu::uint8 attribChannel = posChannel; @@ -377,17 +377,6 @@ void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const const auto& meshIndicesToModelNames = input.get2(); const auto& normalsPerMesh = input.get3(); const auto& tangentsPerMesh = input.get4(); - const auto& shapes = input.get5(); - const auto& skinDeformers = input.get6(); - - // Currently, there is only (at most) one skinDeformer per mesh - // An undefined shape.skinDeformer has the value hfm::UNDEFINED_KEY - std::vector skinDeformerPerMesh; - skinDeformerPerMesh.resize(meshes.size(), hfm::UNDEFINED_KEY); - for (const auto& shape : shapes) { - uint32_t skinDeformerIndex = shape.skinDeformer; - skinDeformerPerMesh[shape.mesh] = skinDeformerIndex; - } auto& graphicsMeshes = output; @@ -395,16 +384,9 @@ void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const for (int i = 0; i < n; i++) { graphicsMeshes.emplace_back(); auto& graphicsMesh = graphicsMeshes[i]; - - uint16_t numDeformerControllers = 0; - uint32_t skinDeformerIndex = skinDeformerPerMesh[i]; - if (skinDeformerIndex != hfm::UNDEFINED_KEY) { - const hfm::SkinDeformer& skinDeformer = skinDeformers[skinDeformerIndex]; - numDeformerControllers = (uint16_t)skinDeformer.clusters.size(); - } - + // Try to create the graphics::Mesh - buildGraphicsMesh(meshes[i], graphicsMesh, baker::safeGet(normalsPerMesh, i), baker::safeGet(tangentsPerMesh, i), numDeformerControllers); + buildGraphicsMesh(meshes[i], graphicsMesh, baker::safeGet(normalsPerMesh, i), baker::safeGet(tangentsPerMesh, i)); // Choose a name for the mesh if (graphicsMesh) { diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h index 34128eabe8..bb4136c086 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h @@ -2,8 +2,8 @@ // BuildGraphicsMeshTask.h // model-baker/src/model-baker // -// Created by Sabrina Shanman on 2019/09/16. -// Copyright 2019 High Fidelity, Inc. +// Created by Sabrina Shanman on 2018/12/06. +// Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -20,7 +20,7 @@ class BuildGraphicsMeshTask { public: - using Input = baker::VaryingSet7, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh, std::vector, std::vector>; + using Input = baker::VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh>; using Output = std::vector; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp index 6147ce72e7..297d8cbde7 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -30,7 +30,7 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co // Otherwise confirm if we have the normals and texcoords needed if (!tangentsIn.empty()) { tangentsOut = tangentsIn.toStdVector(); - } else if (!normals.empty() && mesh.vertices.size() <= mesh.texCoords.size()) { + } else if (!normals.empty() && mesh.vertices.size() == mesh.texCoords.size()) { tangentsOut.resize(normals.size()); baker::calculateTangents(mesh, [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { diff --git a/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.cpp deleted file mode 100644 index 028dba4939..0000000000 --- a/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// -// CalculateTransformedExtentsTask.cpp -// model-baker/src/model-baker -// -// Created by Sabrina Shanman on 2019/10/04. -// 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 "CalculateTransformedExtentsTask.h" - -#include "hfm/HFMModelMath.h" - -void CalculateTransformedExtentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { - const auto& modelExtentsIn = input.get0(); - const auto& triangleListMeshes = input.get1(); - const auto& shapesIn = input.get2(); - const auto& joints = input.get3(); - auto& modelExtentsOut = output.edit0(); - auto& shapesOut = output.edit1(); - - shapesOut.reserve(shapesIn.size()); - for (size_t i = 0; i < shapesIn.size(); ++i) { - shapesOut.push_back(shapesIn[i]); - auto& shapeOut = shapesOut.back(); - - auto& shapeExtents = shapeOut.transformedExtents; - if (shapeExtents.isValid()) { - continue; - } - - hfm::calculateExtentsForShape(shapeOut, triangleListMeshes, joints); - } - - modelExtentsOut = modelExtentsIn; - if (!modelExtentsOut.isValid()) { - hfm::calculateExtentsForModel(modelExtentsOut, shapesOut); - } -} diff --git a/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.h b/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.h deleted file mode 100644 index aed089a13d..0000000000 --- a/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// CalculateTransformedExtentsTask.h -// model-baker/src/model-baker -// -// Created by Sabrina Shanman on 2019/10/04. -// 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_CalculateExtentsTask_h -#define hifi_CalculateExtentsTask_h - -#include "Engine.h" -#include "hfm/HFM.h" - -// Calculates any undefined extents in the shapes and the model. Precalculated extents will be left alone. -// Bind extents will currently not be calculated -class CalculateTransformedExtentsTask { -public: - using Input = baker::VaryingSet4, std::vector, std::vector>; - using Output = baker::VaryingSet2>; - using JobModel = baker::Job::ModelIO; - - void run(const baker::BakeContextPointer& context, const Input& input, Output& output); -}; - -#endif // hifi_CalculateExtentsTask_h diff --git a/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.cpp b/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.cpp deleted file mode 100644 index 13bc75ced9..0000000000 --- a/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// -// CollectShapeVerticesTask.h -// model-baker/src/model-baker -// -// Created by Sabrina Shanman on 2019/09/27. -// 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 "CollectShapeVerticesTask.h" - -#include - -#include - -// Used to track and avoid duplicate shape vertices, as multiple shapes can have the same mesh and skinDeformer -class VertexSource { -public: - uint32_t mesh; - uint32_t skinDeformer; - - bool operator==(const VertexSource& other) const { - return mesh == other.mesh && - skinDeformer == other.skinDeformer; - } -}; - -void CollectShapeVerticesTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { - const auto& meshes = input.get0(); - const auto& shapes = input.get1(); - const auto& joints = input.get2(); - const auto& skinDeformers = input.get3(); - auto& shapeVerticesPerJoint = output; - - shapeVerticesPerJoint.resize(joints.size()); - std::vector> vertexSourcesPerJoint; - vertexSourcesPerJoint.resize(joints.size()); - for (size_t i = 0; i < shapes.size(); ++i) { - const auto& shape = shapes[i]; - const uint32_t skinDeformerKey = shape.skinDeformer; - if (skinDeformerKey == hfm::UNDEFINED_KEY) { - continue; - } - - VertexSource vertexSource; - vertexSource.mesh = shape.mesh; - vertexSource.skinDeformer = skinDeformerKey; - - const auto& skinDeformer = skinDeformers[skinDeformerKey]; - for (size_t j = 0; j < skinDeformer.clusters.size(); ++j) { - const auto& cluster = skinDeformer.clusters[j]; - const uint32_t jointIndex = cluster.jointIndex; - - auto& vertexSources = vertexSourcesPerJoint[jointIndex]; - if (std::find(vertexSources.cbegin(), vertexSources.cend(), vertexSource) == vertexSources.cend()) { - vertexSources.push_back(vertexSource); - auto& shapeVertices = shapeVerticesPerJoint[jointIndex]; - - const auto& mesh = meshes[shape.mesh]; - const auto& vertices = mesh.vertices; - const glm::mat4 meshToJoint = cluster.inverseBindMatrix; - - const uint16_t weightsPerVertex = mesh.clusterWeightsPerVertex; - if (weightsPerVertex == 0) { - for (int vertexIndex = 0; vertexIndex < (int)vertices.size(); ++vertexIndex) { - const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertices[vertexIndex]); - shapeVertices.push_back(extractTranslation(vertexTransform)); - } - } else { - for (int vertexIndex = 0; vertexIndex < (int)vertices.size(); ++vertexIndex) { - for (uint16_t weightIndex = 0; weightIndex < weightsPerVertex; ++weightIndex) { - const size_t index = vertexIndex*weightsPerVertex + weightIndex; - const uint16_t clusterIndex = mesh.clusterIndices[index]; - const uint16_t clusterWeight = mesh.clusterWeights[index]; - // Remember vertices associated with this joint with at least 1/4 weight - const uint16_t EXPANSION_WEIGHT_THRESHOLD = std::numeric_limits::max() / 4; - if (clusterIndex != j || clusterWeight < EXPANSION_WEIGHT_THRESHOLD) { - continue; - } - - const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertices[vertexIndex]); - shapeVertices.push_back(extractTranslation(vertexTransform)); - } - } - } - } - } - } -} diff --git a/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.h b/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.h deleted file mode 100644 index a665004d6b..0000000000 --- a/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// CollectShapeVerticesTask.h -// model-baker/src/model-baker -// -// Created by Sabrina Shanman on 2019/09/27. -// 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_CollectShapeVerticesTask_h -#define hifi_CollectShapeVerticesTask_h - -#include - -#include "Engine.h" -#include "BakerTypes.h" - -class CollectShapeVerticesTask { -public: - using Input = baker::VaryingSet4, std::vector, std::vector, std::vector>; - using Output = std::vector; - using JobModel = baker::Job::ModelIO; - - void run(const baker::BakeContextPointer& context, const Input& input, Output& output); -}; - -#endif // hifi_CollectShapeVerticesTask_h - diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index bb911c6914..1fcfcfcc70 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -203,23 +203,23 @@ QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) { return textureBaseUrl.isValid() ? textureBaseUrl : url; } -ModelResource::ModelResource(const ModelResource& other) : +GeometryResource::GeometryResource(const GeometryResource& other) : Resource(other), - NetworkModel(other), + Geometry(other), _modelLoader(other._modelLoader), _mappingPair(other._mappingPair), _textureBaseURL(other._textureBaseURL), _combineParts(other._combineParts), _isCacheable(other._isCacheable) { - if (other._modelResource) { + if (other._geometryResource) { _startedLoading = false; } } -void ModelResource::downloadFinished(const QByteArray& data) { +void GeometryResource::downloadFinished(const QByteArray& data) { if (_effectiveBaseURL.fileName().toLower().endsWith(".fst")) { - PROFILE_ASYNC_BEGIN(resource_parse_geometry, "ModelResource::downloadFinished", _url.toString(), { { "url", _url.toString() } }); + PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryResource::downloadFinished", _url.toString(), { { "url", _url.toString() } }); // store parsed contents of FST file _mapping = FSTReader::readMapping(data); @@ -267,19 +267,19 @@ void ModelResource::downloadFinished(const QByteArray& data) { auto modelCache = DependencyManager::get(); GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseURL, false }; - // Get the raw ModelResource - _modelResource = modelCache->getResource(url, QUrl(), &extra, std::hash()(extra)).staticCast(); + // Get the raw GeometryResource + _geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash()(extra)).staticCast(); // Avoid caching nested resources - their references will be held by the parent - _modelResource->_isCacheable = false; + _geometryResource->_isCacheable = false; - if (_modelResource->isLoaded()) { - onGeometryMappingLoaded(!_modelResource->getURL().isEmpty()); + if (_geometryResource->isLoaded()) { + onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty()); } else { if (_connection) { disconnect(_connection); } - _connection = connect(_modelResource.data(), &Resource::finished, this, &ModelResource::onGeometryMappingLoaded); + _connection = connect(_geometryResource.data(), &Resource::finished, this, &GeometryResource::onGeometryMappingLoaded); } } } else { @@ -291,31 +291,32 @@ void ModelResource::downloadFinished(const QByteArray& data) { } } -void ModelResource::onGeometryMappingLoaded(bool success) { - if (success && _modelResource) { - _hfmModel = _modelResource->_hfmModel; - _materialMapping = _modelResource->_materialMapping; - _meshes = _modelResource->_meshes; - _materials = _modelResource->_materials; +void GeometryResource::onGeometryMappingLoaded(bool success) { + if (success && _geometryResource) { + _hfmModel = _geometryResource->_hfmModel; + _materialMapping = _geometryResource->_materialMapping; + _meshParts = _geometryResource->_meshParts; + _meshes = _geometryResource->_meshes; + _materials = _geometryResource->_materials; // Avoid holding onto extra references - _modelResource.reset(); + _geometryResource.reset(); // Make sure connection will not trigger again disconnect(_connection); // FIXME Should not have to do this } - PROFILE_ASYNC_END(resource_parse_geometry, "ModelResource::downloadFinished", _url.toString()); + PROFILE_ASYNC_END(resource_parse_geometry, "GeometryResource::downloadFinished", _url.toString()); finishedLoading(success); } -void ModelResource::setExtra(void* extra) { +void GeometryResource::setExtra(void* extra) { const GeometryExtra* geometryExtra = static_cast(extra); _mappingPair = geometryExtra ? geometryExtra->mapping : GeometryMappingPair(QUrl(), QVariantHash()); _textureBaseURL = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl(); _combineParts = geometryExtra ? geometryExtra->combineParts : true; } -void ModelResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping) { +void GeometryResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping) { // Assume ownership of the processed HFMModel _hfmModel = hfmModel; _materialMapping = materialMapping; @@ -328,23 +329,31 @@ void ModelResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const Mate } std::shared_ptr meshes = std::make_shared(); + std::shared_ptr parts = std::make_shared(); int meshID = 0; for (const HFMMesh& mesh : _hfmModel->meshes) { // Copy mesh pointers meshes->emplace_back(mesh._mesh); + int partID = 0; + for (const HFMMeshPart& part : mesh.parts) { + // Construct local parts + parts->push_back(std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID])); + partID++; + } meshID++; } _meshes = meshes; + _meshParts = parts; finishedLoading(true); } -void ModelResource::deleter() { +void GeometryResource::deleter() { resetTextures(); Resource::deleter(); } -void ModelResource::setTextures() { +void GeometryResource::setTextures() { if (_hfmModel) { for (const HFMMaterial& material : _hfmModel->materials) { _materials.push_back(std::make_shared(material, _textureBaseURL)); @@ -352,7 +361,7 @@ void ModelResource::setTextures() { } } -void ModelResource::resetTextures() { +void GeometryResource::resetTextures() { _materials.clear(); } @@ -368,17 +377,17 @@ ModelCache::ModelCache() { } QSharedPointer ModelCache::createResource(const QUrl& url) { - return QSharedPointer(new ModelResource(url, _modelLoader), &ModelResource::deleter); + return QSharedPointer(new GeometryResource(url, _modelLoader), &GeometryResource::deleter); } QSharedPointer ModelCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new ModelResource(*resource.staticCast()), &ModelResource::deleter); + return QSharedPointer(new GeometryResource(*resource.staticCast()), &GeometryResource::deleter); } -ModelResource::Pointer ModelCache::getModelResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) { +GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) { bool combineParts = true; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - ModelResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); + GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -387,12 +396,12 @@ ModelResource::Pointer ModelCache::getModelResource(const QUrl& url, const Geome return resource; } -ModelResource::Pointer ModelCache::getCollisionModelResource(const QUrl& url, +GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) { bool combineParts = false; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - ModelResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); + GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -401,7 +410,7 @@ ModelResource::Pointer ModelCache::getCollisionModelResource(const QUrl& url, return resource; } -const QVariantMap NetworkModel::getTextures() const { +const QVariantMap Geometry::getTextures() const { QVariantMap textures; for (const auto& material : _materials) { for (const auto& texture : material->_textures) { @@ -415,21 +424,22 @@ const QVariantMap NetworkModel::getTextures() const { } // FIXME: The materials should only be copied when modified, but the Model currently caches the original -NetworkModel::NetworkModel(const NetworkModel& networkModel) { - _hfmModel = networkModel._hfmModel; - _materialMapping = networkModel._materialMapping; - _meshes = networkModel._meshes; +Geometry::Geometry(const Geometry& geometry) { + _hfmModel = geometry._hfmModel; + _materialMapping = geometry._materialMapping; + _meshes = geometry._meshes; + _meshParts = geometry._meshParts; - _materials.reserve(networkModel._materials.size()); - for (const auto& material : networkModel._materials) { + _materials.reserve(geometry._materials.size()); + for (const auto& material : geometry._materials) { _materials.push_back(std::make_shared(*material)); } - _animGraphOverrideUrl = networkModel._animGraphOverrideUrl; - _mapping = networkModel._mapping; + _animGraphOverrideUrl = geometry._animGraphOverrideUrl; + _mapping = geometry._mapping; } -void NetworkModel::setTextures(const QVariantMap& textureMap) { +void Geometry::setTextures(const QVariantMap& textureMap) { if (_meshes->size() > 0) { for (auto& material : _materials) { // Check if any material textures actually changed @@ -437,7 +447,7 @@ void NetworkModel::setTextures(const QVariantMap& textureMap) { [&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.second.texture && textureMap.contains(it.second.name); })) { // FIXME: The Model currently caches the materials (waste of space!) - // so they must be copied in the NetworkModel copy-ctor + // so they must be copied in the Geometry copy-ctor // if (material->isOriginal()) { // // Copy the material to avoid mutating the cached version // material = std::make_shared(*material); @@ -451,11 +461,11 @@ void NetworkModel::setTextures(const QVariantMap& textureMap) { // If we only use cached textures, they should all be loaded areTexturesLoaded(); } else { - qCWarning(modelnetworking) << "Ignoring setTextures(); NetworkModel not ready"; + qCWarning(modelnetworking) << "Ignoring setTextures(); geometry not ready"; } } -bool NetworkModel::areTexturesLoaded() const { +bool Geometry::areTexturesLoaded() const { if (!_areTexturesLoaded) { for (auto& material : _materials) { if (material->isMissingTexture()) { @@ -490,28 +500,30 @@ bool NetworkModel::areTexturesLoaded() const { return true; } -const std::shared_ptr NetworkModel::getShapeMaterial(int shapeID) const { - uint32_t materialID = getHFMModel().shapes[shapeID].material; - if (materialID < (uint32_t)_materials.size()) { - return _materials[materialID]; +const std::shared_ptr Geometry::getShapeMaterial(int partID) const { + if ((partID >= 0) && (partID < (int)_meshParts->size())) { + int materialID = _meshParts->at(partID)->materialID; + if ((materialID >= 0) && (materialID < (int)_materials.size())) { + return _materials[materialID]; + } } return nullptr; } -void ModelResourceWatcher::startWatching() { - connect(_resource.data(), &Resource::finished, this, &ModelResourceWatcher::resourceFinished); - connect(_resource.data(), &Resource::onRefresh, this, &ModelResourceWatcher::resourceRefreshed); +void GeometryResourceWatcher::startWatching() { + connect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); + connect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); if (_resource->isLoaded()) { resourceFinished(!_resource->getURL().isEmpty()); } } -void ModelResourceWatcher::stopWatching() { - disconnect(_resource.data(), &Resource::finished, this, &ModelResourceWatcher::resourceFinished); - disconnect(_resource.data(), &Resource::onRefresh, this, &ModelResourceWatcher::resourceRefreshed); +void GeometryResourceWatcher::stopWatching() { + disconnect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); + disconnect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); } -void ModelResourceWatcher::setResource(ModelResource::Pointer resource) { +void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) { if (_resource) { stopWatching(); } @@ -525,14 +537,14 @@ void ModelResourceWatcher::setResource(ModelResource::Pointer resource) { } } -void ModelResourceWatcher::resourceFinished(bool success) { +void GeometryResourceWatcher::resourceFinished(bool success) { if (success) { - _networkModelRef = std::make_shared(*_resource); + _geometryRef = std::make_shared(*_resource); } emit finished(success); } -void ModelResourceWatcher::resourceRefreshed() { +void GeometryResourceWatcher::resourceRefreshed() { // FIXME: Model is not set up to handle a refresh // _instance.reset(); } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index d61bd61696..615951345f 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -22,20 +22,23 @@ #include #include "ModelLoader.h" +class MeshPart; + using GeometryMappingPair = std::pair; Q_DECLARE_METATYPE(GeometryMappingPair) -class NetworkModel { +class Geometry { public: - using Pointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; + using Pointer = std::shared_ptr; + using WeakPointer = std::weak_ptr; - NetworkModel() = default; - NetworkModel(const NetworkModel& geometry); - virtual ~NetworkModel() = default; + Geometry() = default; + Geometry(const Geometry& geometry); + virtual ~Geometry() = default; // Immutable over lifetime using GeometryMeshes = std::vector>; + using GeometryMeshParts = std::vector>; // Mutable, but must retain structure of vector using NetworkMaterials = std::vector>; @@ -60,6 +63,7 @@ protected: HFMModel::ConstPointer _hfmModel; MaterialMapping _materialMapping; std::shared_ptr _meshes; + std::shared_ptr _meshParts; // Copied to each geometry, mutable throughout lifetime via setTextures NetworkMaterials _materials; @@ -72,22 +76,22 @@ private: }; /// A geometry loaded from the network. -class ModelResource : public Resource, public NetworkModel { +class GeometryResource : public Resource, public Geometry { Q_OBJECT public: - using Pointer = QSharedPointer; + using Pointer = QSharedPointer; - ModelResource(const QUrl& url, const ModelLoader& modelLoader) : Resource(url), _modelLoader(modelLoader) {} - ModelResource(const ModelResource& other); + GeometryResource(const QUrl& url, const ModelLoader& modelLoader) : Resource(url), _modelLoader(modelLoader) {} + GeometryResource(const GeometryResource& other); - QString getType() const override { return "Model"; } + QString getType() const override { return "Geometry"; } virtual void deleter() override; virtual void downloadFinished(const QByteArray& data) override; void setExtra(void* extra) override; - virtual bool areTexturesLoaded() const override { return isLoaded() && NetworkModel::areTexturesLoaded(); } + virtual bool areTexturesLoaded() const override { return isLoaded() && Geometry::areTexturesLoaded(); } private slots: void onGeometryMappingLoaded(bool success); @@ -111,21 +115,21 @@ private: QUrl _textureBaseURL; bool _combineParts; - ModelResource::Pointer _modelResource; + GeometryResource::Pointer _geometryResource; QMetaObject::Connection _connection; bool _isCacheable{ true }; }; -class ModelResourceWatcher : public QObject { +class GeometryResourceWatcher : public QObject { Q_OBJECT public: - using Pointer = std::shared_ptr; + using Pointer = std::shared_ptr; - ModelResourceWatcher() = delete; - ModelResourceWatcher(NetworkModel::Pointer& geometryPtr) : _networkModelRef(geometryPtr) {} + GeometryResourceWatcher() = delete; + GeometryResourceWatcher(Geometry::Pointer& geometryPtr) : _geometryRef(geometryPtr) {} - void setResource(ModelResource::Pointer resource); + void setResource(GeometryResource::Pointer resource); QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); } int getResourceDownloadAttempts() { return _resource ? _resource->getDownloadAttempts() : 0; } @@ -143,8 +147,8 @@ private slots: void resourceRefreshed(); private: - ModelResource::Pointer _resource; - NetworkModel::Pointer& _networkModelRef; + GeometryResource::Pointer _resource; + Geometry::Pointer& _geometryRef; }; /// Stores cached model geometries. @@ -154,18 +158,18 @@ class ModelCache : public ResourceCache, public Dependency { public: - ModelResource::Pointer getModelResource(const QUrl& url, + GeometryResource::Pointer getGeometryResource(const QUrl& url, const GeometryMappingPair& mapping = GeometryMappingPair(QUrl(), QVariantHash()), const QUrl& textureBaseUrl = QUrl()); - ModelResource::Pointer getCollisionModelResource(const QUrl& url, + GeometryResource::Pointer getCollisionGeometryResource(const QUrl& url, const GeometryMappingPair& mapping = GeometryMappingPair(QUrl(), QVariantHash()), const QUrl& textureBaseUrl = QUrl()); protected: - friend class ModelResource; + friend class GeometryResource; virtual QSharedPointer createResource(const QUrl& url) override; QSharedPointer createResourceCopy(const QSharedPointer& resource) override; @@ -176,4 +180,12 @@ private: ModelLoader _modelLoader; }; +class MeshPart { +public: + MeshPart(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {} + int meshID { -1 }; + int partID { -1 }; + int materialID { -1 }; +}; + #endif // hifi_ModelCache_h diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 43c6fc27dc..ef5213df8f 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -109,7 +109,7 @@ btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { glm::vec3 center = points[0]; glm::vec3 maxCorner = center; glm::vec3 minCorner = center; - for (size_t i = 1; i < points.size(); i++) { + for (int i = 1; i < points.size(); i++) { center += points[i]; maxCorner = glm::max(maxCorner, points[i]); minCorner = glm::min(minCorner, points[i]); @@ -149,7 +149,7 @@ btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { // add the points, correcting for margin glm::vec3 relativeScale = (diagonal - glm::vec3(2.0f * margin)) / diagonal; glm::vec3 correctedPoint; - for (size_t i = 0; i < points.size(); ++i) { + for (int i = 0; i < points.size(); ++i) { correctedPoint = (points[i] - center) * relativeScale + center; hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false); } @@ -217,7 +217,7 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { } const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); - int32_t numIndices = (int32_t)triangleIndices.size(); + int32_t numIndices = triangleIndices.size(); if (numIndices < 3) { // not enough indices to make a single triangle return nullptr; @@ -237,7 +237,7 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { mesh.m_indexType = PHY_INTEGER; mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t); } - mesh.m_numVertices = (int)pointList.size(); + mesh.m_numVertices = pointList.size(); mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices]; mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar); mesh.m_vertexType = PHY_FLOAT; @@ -362,7 +362,7 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); uint32_t numSubShapes = info.getNumSubShapes(); if (numSubShapes == 1) { - if (!pointCollection.empty()) { + if (!pointCollection.isEmpty()) { shape = createConvexHull(pointCollection[0]); } } else { @@ -380,7 +380,7 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) case SHAPE_TYPE_SIMPLE_COMPOUND: { const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); - uint32_t numIndices = (uint32_t)triangleIndices.size(); + uint32_t numIndices = triangleIndices.size(); uint32_t numMeshes = info.getNumSubShapes(); const uint32_t MIN_NUM_SIMPLE_COMPOUND_INDICES = 2; // END_OF_MESH_PART + END_OF_MESH if (numMeshes > 0 && numIndices > MIN_NUM_SIMPLE_COMPOUND_INDICES) { diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.h b/libraries/procedural/src/procedural/ProceduralMaterialCache.h index bf4b5191c2..0a44ccf0ef 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.h +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.h @@ -50,7 +50,7 @@ public: Textures getTextures() { return _textures; } protected: - friend class NetworkModel; + friend class Geometry; Textures _textures; diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index bfa974f1e2..94b7661b2f 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -32,11 +32,16 @@ bool CauterizedModel::updateGeometry() { bool needsFullUpdate = Model::updateGeometry(); if (_isCauterized && needsFullUpdate) { assert(_cauterizeMeshStates.empty()); - - // initialize the cauterizedDeforemrStates as a copy of the standard deformerStates - _cauterizeMeshStates.resize(_meshStates.size()); - for (int i = 0; i < (int) _meshStates.size(); ++i) { - _cauterizeMeshStates[i] = _meshStates[i]; + const HFMModel& hfmModel = getHFMModel(); + foreach (const HFMMesh& mesh, hfmModel.meshes) { + Model::MeshState state; + if (_useDualQuaternionSkinning) { + state.clusterDualQuaternions.resize(mesh.clusters.size()); + _cauterizeMeshStates.append(state); + } else { + state.clusterMatrices.resize(mesh.clusters.size()); + _cauterizeMeshStates.append(state); + } } } return needsFullUpdate; @@ -45,12 +50,20 @@ bool CauterizedModel::updateGeometry() { void CauterizedModel::createRenderItemSet() { if (_isCauterized) { assert(isLoaded()); + const auto& meshes = _renderGeometry->getMeshes(); + + // all of our mesh vectors must match in size + if (meshes.size() != _meshStates.size()) { + qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; + return; + } // We should not have any existing renderItems if we enter this section of code Q_ASSERT(_modelMeshRenderItems.isEmpty()); _modelMeshRenderItems.clear(); _modelMeshMaterialNames.clear(); + _modelMeshRenderItemShapes.clear(); Transform transform; transform.setTranslation(_translation); @@ -60,17 +73,25 @@ void CauterizedModel::createRenderItemSet() { offset.setScale(_scale); offset.postTranslate(_offset); - Transform::mult(transform, transform, offset); - // Run through all of the meshes, and place them into their segregated, but unsorted buckets - const auto& shapes = _renderGeometry->getHFMModel().shapes; - for (int shapeID = 0; shapeID < (int) shapes.size(); shapeID++) { - const auto& shape = shapes[shapeID]; + int shapeID = 0; + uint32_t numMeshes = (uint32_t)meshes.size(); + for (uint32_t i = 0; i < numMeshes; i++) { + const auto& mesh = meshes.at(i); + if (!mesh) { + continue; + } - _modelMeshRenderItems << std::make_shared(shared_from_this(), shape.mesh, shape.meshPart, shapeID, transform, offset, _created); - - auto material = getNetworkModel()->getShapeMaterial(shapeID); - _modelMeshMaterialNames.push_back(material ? material->getName() : ""); + // Create the render payloads + int numParts = (int)mesh->getNumParts(); + for (int partIndex = 0; partIndex < numParts; partIndex++) { + auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset, _created); + _modelMeshRenderItems << std::static_pointer_cast(ptr); + auto material = getGeometry()->getShapeMaterial(shapeID); + _modelMeshMaterialNames.push_back(material ? material->getName() : ""); + _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); + shapeID++; + } } } else { Model::createRenderItemSet(); @@ -83,26 +104,28 @@ void CauterizedModel::updateClusterMatrices() { if (!_needsUpdateClusterMatrices || !isLoaded()) { return; } - - updateShapeStatesFromRig(); - _needsUpdateClusterMatrices = false; + const HFMModel& hfmModel = getHFMModel(); - for (int skinDeformerIndex = 0; skinDeformerIndex < (int)_meshStates.size(); skinDeformerIndex++) { - MeshState& state = _meshStates[skinDeformerIndex]; - auto numClusters = state.getNumClusters(); - for (uint32_t clusterIndex = 0; clusterIndex < numClusters; clusterIndex++) { - const auto& cbmov = _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(skinDeformerIndex, clusterIndex); + for (int i = 0; i < (int)_meshStates.size(); i++) { + Model::MeshState& state = _meshStates[i]; + const HFMMesh& mesh = hfmModel.meshes.at(i); + int meshIndex = i; + + for (int j = 0; j < mesh.clusters.size(); j++) { + const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; if (_useDualQuaternionSkinning) { - auto jointPose = _rig.getJointPose(cbmov.jointIndex); + auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cbmov.inverseBindTransform); - state.clusterDualQuaternions[clusterIndex] = Model::TransformDualQuaternion(clusterTransform); + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); + state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); + state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans()); } else { - auto jointMatrix = _rig.getJointTransform(cbmov.jointIndex); - glm_mat4u_mul(jointMatrix, cbmov.inverseBindMatrix, state.clusterMatrices[clusterIndex]); + auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } @@ -112,7 +135,6 @@ void CauterizedModel::updateClusterMatrices() { AnimPose cauterizePose = _rig.getJointPose(_rig.indexOfJoint("Neck")); cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f); - Transform cauterizedDQTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); static const glm::mat4 zeroScale( glm::vec4(0.0001f, 0.0f, 0.0f, 0.0f), @@ -121,29 +143,32 @@ void CauterizedModel::updateClusterMatrices() { glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); auto cauterizeMatrix = _rig.getJointTransform(_rig.indexOfJoint("Neck")) * zeroScale; - for (int skinDeformerIndex = 0; skinDeformerIndex < (int) _cauterizeMeshStates.size(); skinDeformerIndex++) { - Model::MeshState& nonCauterizedState = _meshStates[skinDeformerIndex]; - Model::MeshState& state = _cauterizeMeshStates[skinDeformerIndex]; + for (int i = 0; i < _cauterizeMeshStates.size(); i++) { + Model::MeshState& state = _cauterizeMeshStates[i]; + const HFMMesh& mesh = hfmModel.meshes.at(i); + int meshIndex = i; - // Just reset cauterized state with normal state memcpy style - if (_useDualQuaternionSkinning) { - state.clusterDualQuaternions = nonCauterizedState.clusterDualQuaternions; - } else { - state.clusterMatrices = nonCauterizedState.clusterMatrices; - } - - // ANd only cauterize affected joints - auto numClusters = state.getNumClusters(); - for (uint32_t clusterIndex = 0; clusterIndex < numClusters; clusterIndex++) { - const auto& cbmov = _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(skinDeformerIndex, clusterIndex); - if (_cauterizeBoneSet.find(cbmov.jointIndex) != _cauterizeBoneSet.end()) { - if (_useDualQuaternionSkinning) { - Transform clusterTransform; - Transform::mult(clusterTransform, cauterizedDQTransform, cbmov.inverseBindTransform); - state.clusterDualQuaternions[clusterIndex] = Model::TransformDualQuaternion(clusterTransform); - state.clusterDualQuaternions[clusterIndex].setCauterizationParameters(1.0f, cauterizePose.trans()); + for (int j = 0; j < mesh.clusters.size(); j++) { + const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; + + if (_useDualQuaternionSkinning) { + if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { + // not cauterized so just copy the value from the non-cauterized version. + state.clusterDualQuaternions[j] = _meshStates[i].clusterDualQuaternions[j]; } else { - glm_mat4u_mul(cauterizeMatrix, cbmov.inverseBindMatrix, state.clusterMatrices[clusterIndex]); + Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); + Transform clusterTransform; + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); + state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); + state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans()); + } + } else { + if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { + // not cauterized so just copy the value from the non-cauterized version. + state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j]; + } else { + glm_mat4u_mul(cauterizeMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } @@ -187,60 +212,65 @@ void CauterizedModel::updateRenderItems() { render::Transaction transaction; for (int i = 0; i < (int)self->_modelMeshRenderItemIDs.size(); i++) { + auto itemID = self->_modelMeshRenderItemIDs[i]; + auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; - const auto& shapeState = self->getShapeState(i); + const auto& meshState = self->getMeshState(meshIndex); + const auto& cauterizedMeshState = self->getCauterizeMeshState(meshIndex); - auto skinDeformerIndex = shapeState._skinDeformerIndex; - - bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(shapeState._meshIndex); + bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); - if (skinDeformerIndex != hfm::UNDEFINED_KEY) { + transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey, + primitiveMode, renderItemKeyGlobalFlags, enableCauterization](ModelMeshPartPayload& mmppData) { + CauterizedMeshPartPayload& data = static_cast(mmppData); + if (useDualQuaternionSkinning) { + data.updateClusterBuffer(meshState.clusterDualQuaternions, + cauterizedMeshState.clusterDualQuaternions); + data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); + } else { + data.updateClusterBuffer(meshState.clusterMatrices, + cauterizedMeshState.clusterMatrices); + data.computeAdjustedLocalBound(meshState.clusterMatrices); + } - const auto& meshState = self->getMeshState(skinDeformerIndex); - const auto& cauterizedMeshState = self->getCauterizeMeshState(skinDeformerIndex); - - transaction.updateItem(itemID, - [modelTransform, shapeState, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey, - primitiveMode, renderItemKeyGlobalFlags, enableCauterization](ModelMeshPartPayload& mmppData) { - CauterizedMeshPartPayload& data = static_cast(mmppData); - if (useDualQuaternionSkinning) { - data.updateClusterBuffer(meshState.clusterDualQuaternions, cauterizedMeshState.clusterDualQuaternions); - } else { - data.updateClusterBuffer(meshState.clusterMatrices, cauterizedMeshState.clusterMatrices); + Transform renderTransform = modelTransform; + if (useDualQuaternionSkinning) { + if (meshState.clusterDualQuaternions.size() == 1 || meshState.clusterDualQuaternions.size() == 2) { + const auto& dq = meshState.clusterDualQuaternions[0]; + Transform transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + renderTransform = modelTransform.worldTransform(transform); } + } else { + if (meshState.clusterMatrices.size() == 1 || meshState.clusterMatrices.size() == 2) { + renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); + } + } + data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - Transform renderTransform = modelTransform; - // if (meshState.clusterMatrices.size() <= 2) { - // renderTransform = modelTransform.worldTransform(shapeState._rootFromJointTransform); - // } - data.updateTransform(renderTransform); - data.updateTransformForCauterizedMesh(renderTransform); - data.updateTransformAndBound(modelTransform.worldTransform(shapeState._rootFromJointTransform)); + renderTransform = modelTransform; + if (useDualQuaternionSkinning) { + if (cauterizedMeshState.clusterDualQuaternions.size() == 1 || cauterizedMeshState.clusterDualQuaternions.size() == 2) { + const auto& dq = cauterizedMeshState.clusterDualQuaternions[0]; + Transform transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + renderTransform = modelTransform.worldTransform(Transform(transform)); + } + } else { + if (cauterizedMeshState.clusterMatrices.size() == 1 || cauterizedMeshState.clusterMatrices.size() == 2) { + renderTransform = modelTransform.worldTransform(Transform(cauterizedMeshState.clusterMatrices[0])); + } + } + data.updateTransformForCauterizedMesh(renderTransform); - data.setEnableCauterization(enableCauterization); - data.updateKey(renderItemKeyGlobalFlags); - data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); - }); - } else { - transaction.updateItem(itemID, - [modelTransform, shapeState, invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, enableCauterization] - (ModelMeshPartPayload& mmppData) { - CauterizedMeshPartPayload& data = static_cast(mmppData); - - Transform renderTransform = modelTransform; - - renderTransform = modelTransform.worldTransform(shapeState._rootFromJointTransform); - data.updateTransform(renderTransform); - data.updateTransformForCauterizedMesh(renderTransform); - - data.setEnableCauterization(enableCauterization); - data.updateKey(renderItemKeyGlobalFlags); - data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, false); - }); - - } + data.setEnableCauterization(enableCauterization); + data.updateKey(renderItemKeyGlobalFlags); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); + }); } scene->enqueueTransaction(transaction); diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h index 7d241d7ac6..36a96fb006 100644 --- a/libraries/render-utils/src/CauterizedModel.h +++ b/libraries/render-utils/src/CauterizedModel.h @@ -40,7 +40,7 @@ public: protected: std::unordered_set _cauterizeBoneSet; - std::vector _cauterizeMeshStates; + QVector _cauterizeMeshStates; bool _isCauterized { false }; bool _enableCauterization { false }; }; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index e0d23535e4..ea66ac19ec 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -116,7 +116,7 @@ static const uint SHAPE_TANGENT_OFFSET = offsetof(GeometryCache::ShapeVertex, ta std::map, gpu::PipelinePointer> GeometryCache::_webPipelines; std::map, gpu::PipelinePointer> GeometryCache::_gridPipelines; -void GeometryCache::computeSimpleHullPointListForShape(const int entityShape, const glm::vec3 &entityExtents, ShapeInfo::PointList &outPointList) { +void GeometryCache::computeSimpleHullPointListForShape(const int entityShape, const glm::vec3 &entityExtents, QVector &outPointList) { auto geometryCache = DependencyManager::get(); const GeometryCache::Shape geometryShape = GeometryCache::getShapeForEntityShape( entityShape ); diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 03865c6cc7..179d49c076 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -155,7 +155,7 @@ public: static GeometryCache::Shape getShapeForEntityShape(int entityShapeEnum); static QString stringFromShape(GeometryCache::Shape geoShape); - static void computeSimpleHullPointListForShape(int entityShape, const glm::vec3 &entityExtents, ShapeInfo::PointList &outPointList); + static void computeSimpleHullPointListForShape(int entityShape, const glm::vec3 &entityExtents, QVector &outPointList); int allocateID() { return _nextID++; } void releaseID(int id); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 2ae6c5de8b..d150e18406 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -81,15 +81,11 @@ void MeshPartPayload::updateMeshPart(const std::shared_ptr } } -void MeshPartPayload::updateTransform(const Transform& transform) { - _worldFromLocalTransform = transform; +void MeshPartPayload::updateTransform(const Transform& transform, const Transform& offsetTransform) { + _transform = transform; + Transform::mult(_drawTransform, _transform, offsetTransform); _worldBound = _localBound; - _worldBound.transform(_worldFromLocalTransform); -} - -void MeshPartPayload::updateTransformAndBound(const Transform& transform) { - _worldBound = _localBound; - _worldBound.transform(transform); + _worldBound.transform(_drawTransform); } void MeshPartPayload::addMaterial(graphics::MaterialLayer material) { @@ -179,7 +175,7 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) { } void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { - batch.setModelTransform(_worldFromLocalTransform); + batch.setModelTransform(_drawTransform); } bool MeshPartPayload::passesZoneOcclusionTest(const std::unordered_set& containingZones) const { @@ -220,7 +216,7 @@ void MeshPartPayload::render(RenderArgs* args) { auto& schema = _drawMaterials.getSchemaBuffer().get(); glm::vec4 outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity); outColor = procedural->getColor(outColor); - procedural->prepare(batch, _worldFromLocalTransform.getTranslation(), _worldFromLocalTransform.getScale(), _worldFromLocalTransform.getRotation(), _created, + procedural->prepare(batch, _drawTransform.getTranslation(), _drawTransform.getScale(), _drawTransform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f)); batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); } else { @@ -281,21 +277,36 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in assert(model && model->isLoaded()); - auto shape = model->getHFMModel().shapes[shapeIndex]; - assert(shape.mesh == meshIndex); - assert(shape.meshPart == partIndex); + bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); - auto& modelMesh = model->getNetworkModel()->getMeshes().at(_meshIndex); + auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); _meshNumVertices = (int)modelMesh->getNumVertices(); + const Model::MeshState& state = model->getMeshState(_meshIndex); updateMeshPart(modelMesh, partIndex); - Transform renderTransform = transform; - const Model::ShapeState& shapeState = model->getShapeState(shapeIndex); - renderTransform = transform.worldTransform(shapeState._rootFromJointTransform); - updateTransform(renderTransform); + if (useDualQuaternionSkinning) { + computeAdjustedLocalBound(state.clusterDualQuaternions); + } else { + computeAdjustedLocalBound(state.clusterMatrices); + } - _deformerIndex = shape.skinDeformer; + updateTransform(transform, offsetTransform); + Transform renderTransform = transform; + if (useDualQuaternionSkinning) { + if (state.clusterDualQuaternions.size() == 1) { + const auto& dq = state.clusterDualQuaternions[0]; + Transform transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + renderTransform = transform.worldTransform(Transform(transform)); + } + } else { + if (state.clusterMatrices.size() == 1) { + renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0])); + } + } + updateTransformForSkinnedMesh(renderTransform, transform); initCache(model); @@ -319,9 +330,7 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { if (_drawMesh) { auto vertexFormat = _drawMesh->getVertexFormat(); _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); - if (_deformerIndex != hfm::UNDEFINED_KEY) { - _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX); - } + _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX); const HFMModel& hfmModel = model->getHFMModel(); const HFMMesh& mesh = hfmModel.meshes.at(_meshIndex); @@ -330,7 +339,7 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { _hasTangents = !mesh.tangents.isEmpty(); } - auto networkMaterial = model->getNetworkModel()->getShapeMaterial(_shapeID); + auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID); if (networkMaterial) { addMaterial(graphics::MaterialLayer(networkMaterial, 0)); } @@ -380,6 +389,12 @@ void ModelMeshPartPayload::updateClusterBuffer(const std::vector(); glm::vec4 outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity); outColor = procedural->getColor(outColor); - procedural->prepare(batch, _worldFromLocalTransform.getTranslation(), _worldFromLocalTransform.getScale(), _worldFromLocalTransform.getRotation(), _created, + procedural->prepare(batch, _drawTransform.getTranslation(), _drawTransform.getScale(), _drawTransform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f, _shapeKey.isDeformed(), _shapeKey.isDualQuatSkinned())); batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); } else { @@ -534,6 +549,38 @@ void ModelMeshPartPayload::render(RenderArgs* args) { args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; } +void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterMatrices) { + _adjustedLocalBound = _localBound; + if (clusterMatrices.size() > 0) { + _adjustedLocalBound.transform(clusterMatrices.back()); + + for (int i = 0; i < (int)clusterMatrices.size() - 1; ++i) { + AABox clusterBound = _localBound; + clusterBound.transform(clusterMatrices[i]); + _adjustedLocalBound += clusterBound; + } + } +} + +void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterDualQuaternions) { + _adjustedLocalBound = _localBound; + if (clusterDualQuaternions.size() > 0) { + Transform rootTransform(clusterDualQuaternions.back().getRotation(), + clusterDualQuaternions.back().getScale(), + clusterDualQuaternions.back().getTranslation()); + _adjustedLocalBound.transform(rootTransform); + + for (int i = 0; i < (int)clusterDualQuaternions.size() - 1; ++i) { + AABox clusterBound = _localBound; + Transform transform(clusterDualQuaternions[i].getRotation(), + clusterDualQuaternions[i].getScale(), + clusterDualQuaternions[i].getTranslation()); + clusterBound.transform(transform); + _adjustedLocalBound += clusterBound; + } + } +} + void ModelMeshPartPayload::setBlendshapeBuffer(const std::unordered_map& blendshapeBuffers, const QVector& blendedMeshSizes) { if (_meshIndex < blendedMeshSizes.length() && blendedMeshSizes.at(_meshIndex) == _meshNumVertices) { auto blendshapeBuffer = blendshapeBuffers.find(_meshIndex); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index be6a0e97f4..8ed799bd74 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -38,8 +38,7 @@ public: virtual void updateMeshPart(const std::shared_ptr& drawMesh, int partIndex); virtual void notifyLocationChanged() {} - void updateTransform(const Transform& transform); - void updateTransformAndBound(const Transform& transform ); + void updateTransform(const Transform& transform, const Transform& offsetTransform); // Render Item interface virtual render::ItemKey getKey() const; @@ -53,11 +52,13 @@ public: virtual void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const; // Payload resource cached values - Transform _worldFromLocalTransform; + Transform _drawTransform; + Transform _transform; int _partIndex = 0; bool _hasColorAttrib { false }; graphics::Box _localBound; + graphics::Box _adjustedLocalBound; mutable graphics::Box _worldBound; std::shared_ptr _drawMesh; @@ -110,6 +111,7 @@ public: // dual quaternion skinning void updateClusterBuffer(const std::vector& clusterDualQuaternions); + void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); // Render Item interface render::ShapeKey getShapeKey() const override; @@ -122,6 +124,12 @@ public: void bindMesh(gpu::Batch& batch) override; void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const override; + // matrix palette skinning + void computeAdjustedLocalBound(const std::vector& clusterMatrices); + + // dual quaternion skinning + void computeAdjustedLocalBound(const std::vector& clusterDualQuaternions); + gpu::BufferPointer _clusterBuffer; enum class ClusterBufferType { Matrices, DualQuaternions }; @@ -129,7 +137,6 @@ public: int _meshIndex; int _shapeID; - uint32_t _deformerIndex; bool _isSkinned{ false }; bool _isBlendShaped { false }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d0d878e294..3776c4e199 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -44,7 +44,7 @@ using namespace std; int nakedModelPointerTypeId = qRegisterMetaType(); -int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); +int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); int vec3VectorTypeId = qRegisterMetaType>(); int normalTypeVecTypeId = qRegisterMetaType>("QVector"); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; @@ -74,7 +74,7 @@ Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride, uint setSnapModelToRegistrationPoint(true, glm::vec3(0.5f)); - connect(&_renderWatcher, &ModelResourceWatcher::finished, this, &Model::loadURLFinished); + connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished); } Model::~Model() { @@ -154,7 +154,7 @@ void Model::setOffset(const glm::vec3& offset) { } void Model::calculateTextureInfo() { - if (!_hasCalculatedTextureInfo && isLoaded() && getNetworkModel()->areTexturesLoaded() && !_modelMeshRenderItems.isEmpty()) { + if (!_hasCalculatedTextureInfo && isLoaded() && getGeometry()->areTexturesLoaded() && !_modelMeshRenderItems.isEmpty()) { size_t textureSize = 0; int textureCount = 0; bool allTexturesLoaded = true; @@ -181,15 +181,15 @@ int Model::getRenderInfoTextureCount() { } bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) { - if (!getNetworkModel()) { + if (!getGeometry()) { return true; } const HFMModel& hfmModel = getHFMModel(); - const auto& networkMeshes = getNetworkModel()->getMeshes(); + const auto& networkMeshes = getGeometry()->getMeshes(); // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. - if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)hfmModel.meshes.size()) { + if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)hfmModel.meshes.size() || meshIndex >= (int)_meshStates.size()) { _needsFixupInScene = true; // trigger remove/add cycle invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid return true; @@ -231,45 +231,46 @@ void Model::updateRenderItems() { render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { + auto itemID = self->_modelMeshRenderItemIDs[i]; + auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; - const auto& shapeState = self->getShapeState(i); + const auto& meshState = self->getMeshState(meshIndex); - auto skinDeformerIndex = shapeState._skinDeformerIndex; + bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); + bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); - bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(shapeState._meshIndex); + transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, + invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, cauterized](ModelMeshPartPayload& data) { + if (useDualQuaternionSkinning) { + data.updateClusterBuffer(meshState.clusterDualQuaternions); + data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); + } else { + data.updateClusterBuffer(meshState.clusterMatrices); + data.computeAdjustedLocalBound(meshState.clusterMatrices); + } - if (skinDeformerIndex != hfm::UNDEFINED_KEY) { - const auto& meshState = self->getMeshState(skinDeformerIndex); - bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); + Transform renderTransform = modelTransform; - transaction.updateItem(itemID, [modelTransform, shapeState, meshState, useDualQuaternionSkinning, - invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, cauterized](ModelMeshPartPayload& data) { - if (useDualQuaternionSkinning) { - data.updateClusterBuffer(meshState.clusterDualQuaternions); - } else { - data.updateClusterBuffer(meshState.clusterMatrices); + if (useDualQuaternionSkinning) { + if (meshState.clusterDualQuaternions.size() == 1 || meshState.clusterDualQuaternions.size() == 2) { + const auto& dq = meshState.clusterDualQuaternions[0]; + Transform transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + renderTransform = modelTransform.worldTransform(Transform(transform)); } + } else { + if (meshState.clusterMatrices.size() == 1 || meshState.clusterMatrices.size() == 2) { + renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); + } + } + data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - Transform renderTransform = modelTransform; - data.updateTransform(renderTransform); - data.updateTransformAndBound(modelTransform.worldTransform(shapeState._rootFromJointTransform)); - - data.setCauterized(cauterized); - data.updateKey(renderItemKeyGlobalFlags); - data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); - }); - } else { - transaction.updateItem(itemID, [modelTransform, shapeState, invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags](ModelMeshPartPayload& data) { - - Transform renderTransform = modelTransform; - renderTransform = modelTransform.worldTransform(shapeState._rootFromJointTransform); - data.updateTransform(renderTransform); - - data.updateKey(renderItemKeyGlobalFlags); - data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, false); - }); - } + data.setCauterized(cauterized); + data.updateKey(renderItemKeyGlobalFlags); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); + }); } AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); @@ -295,15 +296,6 @@ void Model::reset() { } } -void Model::updateShapeStatesFromRig() { - for (auto& shape : _shapeStates) { - uint32_t jointId = shape._jointIndex; - if (jointId < (uint32_t) _rig.getJointStateCount()) { - shape._rootFromJointTransform = _rig.getJointTransform(jointId); - } - } -} - bool Model::updateGeometry() { bool needFullUpdate = false; @@ -319,27 +311,14 @@ bool Model::updateGeometry() { assert(_meshStates.empty()); const HFMModel& hfmModel = getHFMModel(); - - const auto& shapes = hfmModel.shapes; - _shapeStates.resize(shapes.size()); - for (uint32_t s = 0; s < (uint32_t) shapes.size(); ++s) { - auto& shapeState = _shapeStates[s]; - shapeState._jointIndex = shapes[s].joint; - shapeState._meshIndex = shapes[s].mesh; - shapeState._meshPartIndex = shapes[s].meshPart; - shapeState._skinDeformerIndex = shapes[s].skinDeformer; - } - updateShapeStatesFromRig(); - - const auto& hfmSkinDeformers = hfmModel.skinDeformers; - for (uint32_t i = 0; i < (uint32_t) hfmSkinDeformers.size(); i++) { - const auto& dynT = hfmSkinDeformers[i]; + int i = 0; + foreach (const HFMMesh& mesh, hfmModel.meshes) { MeshState state; - state.clusterDualQuaternions.resize(dynT.clusters.size()); - state.clusterMatrices.resize(dynT.clusters.size()); + state.clusterDualQuaternions.resize(mesh.clusters.size()); + state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.push_back(state); + i++; } - needFullUpdate = true; emit rigReady(); } @@ -667,8 +646,8 @@ glm::mat4 Model::getWorldToHFMMatrix() const { // TODO: deprecate and remove MeshProxyList Model::getMeshes() const { MeshProxyList result; - const NetworkModel::Pointer& renderGeometry = getNetworkModel(); - const NetworkModel::GeometryMeshes& meshes = renderGeometry->getMeshes(); + const Geometry::Pointer& renderGeometry = getGeometry(); + const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); if (!isLoaded()) { return result; @@ -737,9 +716,9 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe render::Transaction transaction; for (int i = 0; i < (int) _modelMeshRenderItemIDs.size(); i++) { auto itemID = _modelMeshRenderItemIDs[i]; - auto& shape = _shapeStates[i]; + auto shape = _modelMeshRenderItemShapes[i]; // TODO: check to see if .partIndex matches too - if (shape._meshIndex == (uint32_t) meshIndex) { + if (shape.meshIndex == meshIndex) { transaction.updateItem(itemID, [=](ModelMeshPartPayload& data) { data.updateMeshPart(mesh, partIndex); }); @@ -758,7 +737,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe for (int partID = 0; partID < numParts; partID++) { HFMMeshPart part; part.triangleIndices = buffer_helpers::bufferToVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); - mesh.parts.push_back(part); + mesh.parts << part; } { foreach (const glm::vec3& vertex, mesh.vertices) { @@ -769,7 +748,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe mesh.meshExtents.maximum = glm::max(mesh.meshExtents.maximum, transformedVertex); } } - hfmModel.meshes.push_back(mesh); + hfmModel.meshes << mesh; } calculateTriangleSets(hfmModel); } @@ -786,9 +765,9 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { } const HFMModel& hfmModel = getHFMModel(); - uint32_t numberOfMeshes = (uint32_t)hfmModel.meshes.size(); + int numberOfMeshes = hfmModel.meshes.size(); int shapeID = 0; - for (uint32_t i = 0; i < numberOfMeshes; i++) { + for (int i = 0; i < numberOfMeshes; i++) { const HFMMesh& hfmMesh = hfmModel.meshes.at(i); if (auto mesh = hfmMesh._mesh) { result.append(mesh); @@ -796,7 +775,7 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { auto& materialName = _modelMeshMaterialNames[shapeID]; - result.appendMaterial(graphics::MaterialLayer(getNetworkModel()->getShapeMaterial(shapeID), 0), shapeID, materialName); + result.appendMaterial(graphics::MaterialLayer(getGeometry()->getShapeMaterial(shapeID), 0), shapeID, materialName); { std::unique_lock lock(_materialMappingMutex); @@ -819,69 +798,77 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { void Model::calculateTriangleSets(const HFMModel& hfmModel) { PROFILE_RANGE(render, __FUNCTION__); - uint32_t meshInstanceCount = 0; - uint32_t lastMeshForInstanceCount = hfm::UNDEFINED_KEY; - for (const auto& shape : hfmModel.shapes) { - if (shape.mesh != lastMeshForInstanceCount) { - ++meshInstanceCount; - } - lastMeshForInstanceCount = shape.mesh; - } + int numberOfMeshes = hfmModel.meshes.size(); _triangleSetsValid = true; _modelSpaceMeshTriangleSets.clear(); - _modelSpaceMeshTriangleSets.reserve(meshInstanceCount); + _modelSpaceMeshTriangleSets.resize(numberOfMeshes); - uint32_t lastMeshForTriangleBuilding = hfm::UNDEFINED_KEY; - glm::mat4 lastTransformForTriangleBuilding { 0 }; - std::vector transformedPoints; - for (const auto& shape : hfmModel.shapes) { - const uint32_t meshIndex = shape.mesh; - const hfm::Mesh& mesh = hfmModel.meshes.at(meshIndex); - const auto& triangleListMesh = mesh.triangleListMesh; - const glm::vec2 part = triangleListMesh.parts[shape.meshPart]; - glm::mat4 worldFromMeshTransform; - if (shape.joint != hfm::UNDEFINED_KEY) { - // globalTransform includes hfmModel.offset, - // which includes the scaling, rotation, and translation specified by the FST, - // and the scaling from the unit conversion in FBX. - // This can't change at runtime, so we can safely store these in our TriangleSet. - worldFromMeshTransform = hfmModel.joints[shape.joint].globalTransform; - } + for (int i = 0; i < numberOfMeshes; i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); - if (meshIndex != lastMeshForTriangleBuilding || worldFromMeshTransform != lastTransformForTriangleBuilding) { - lastMeshForTriangleBuilding = meshIndex; - lastTransformForTriangleBuilding = worldFromMeshTransform; - _modelSpaceMeshTriangleSets.emplace_back(); - _modelSpaceMeshTriangleSets.back().reserve(mesh.parts.size()); + const int numberOfParts = mesh.parts.size(); + auto& meshTriangleSets = _modelSpaceMeshTriangleSets[i]; + meshTriangleSets.resize(numberOfParts); - transformedPoints = triangleListMesh.vertices; - if (worldFromMeshTransform != glm::mat4()) { - for (auto& point : transformedPoints) { - point = glm::vec3(worldFromMeshTransform * glm::vec4(point, 1.0f)); + for (int j = 0; j < numberOfParts; j++) { + const HFMMeshPart& part = mesh.parts.at(j); + + auto& partTriangleSet = meshTriangleSets[j]; + + const int INDICES_PER_TRIANGLE = 3; + const int INDICES_PER_QUAD = 4; + const int TRIANGLES_PER_QUAD = 2; + + // tell our triangleSet how many triangles to expect. + int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; + int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; + int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; + partTriangleSet.reserve(totalTriangles); + + auto meshTransform = hfmModel.offset * mesh.modelTransform; + + if (part.quadIndices.size() > 0) { + int vIndex = 0; + for (int q = 0; q < numberOfQuads; q++) { + int i0 = part.quadIndices[vIndex++]; + int i1 = part.quadIndices[vIndex++]; + int i2 = part.quadIndices[vIndex++]; + int i3 = part.quadIndices[vIndex++]; + + // track the model space version... these points will be transformed by the FST's offset, + // which includes the scaling, rotation, and translation specified by the FST/FBX, + // this can't change at runtime, so we can safely store these in our TriangleSet + glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + glm::vec3 v3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f)); + + Triangle tri1 = { v0, v1, v3 }; + Triangle tri2 = { v1, v2, v3 }; + partTriangleSet.insert(tri1); + partTriangleSet.insert(tri2); } } - } - auto& meshTriangleSets = _modelSpaceMeshTriangleSets.back(); - meshTriangleSets.emplace_back(); - auto& partTriangleSet = meshTriangleSets.back(); - const static size_t INDICES_PER_TRIANGLE = 3; - const size_t triangleCount = (size_t)(part.y) / INDICES_PER_TRIANGLE; - partTriangleSet.reserve(triangleCount); - const size_t indexStart = (uint32_t)part.x; - const size_t indexEnd = indexStart + (triangleCount * INDICES_PER_TRIANGLE); - for (size_t i = indexStart; i < indexEnd; i += INDICES_PER_TRIANGLE) { - const int i0 = triangleListMesh.indices[i]; - const int i1 = triangleListMesh.indices[i + 1]; - const int i2 = triangleListMesh.indices[i + 2]; + if (part.triangleIndices.size() > 0) { + int vIndex = 0; + for (int t = 0; t < numberOfTris; t++) { + int i0 = part.triangleIndices[vIndex++]; + int i1 = part.triangleIndices[vIndex++]; + int i2 = part.triangleIndices[vIndex++]; - const glm::vec3 v0 = transformedPoints[i0]; - const glm::vec3 v1 = transformedPoints[i1]; - const glm::vec3 v2 = transformedPoints[i2]; + // track the model space version... these points will be transformed by the FST's offset, + // which includes the scaling, rotation, and translation specified by the FST/FBX, + // this can't change at runtime, so we can safely store these in our TriangleSet + glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); - const Triangle tri = { v0, v1, v2 }; - partTriangleSet.insert(tri); + Triangle tri = { v0, v1, v2 }; + partTriangleSet.insert(tri); + } + } } } } @@ -964,8 +951,8 @@ void Model::setCauterized(bool cauterized, const render::ScenePointer& scene) { return; } render::Transaction transaction; - for (auto itemID : _modelMeshRenderItemIDs) { - transaction.updateItem(itemID, [cauterized](ModelMeshPartPayload& data) { + foreach (auto item, _modelMeshRenderItemsMap.keys()) { + transaction.updateItem(item, [cauterized](ModelMeshPartPayload& data) { data.setCauterized(cauterized); }); } @@ -1020,25 +1007,26 @@ bool Model::addToScene(const render::ScenePointer& scene, bool somethingAdded = false; - if (_modelMeshRenderItemIDs.empty()) { + if (_modelMeshRenderItemsMap.empty()) { bool hasTransparent = false; size_t verticesCount = 0; foreach(auto renderItem, _modelMeshRenderItems) { auto item = scene->allocateID(); auto renderPayload = std::make_shared(renderItem); - if (_modelMeshRenderItemIDs.empty() && statusGetters.size()) { + if (_modelMeshRenderItemsMap.empty() && statusGetters.size()) { renderPayload->addStatusGetters(statusGetters); } transaction.resetItem(item, renderPayload); hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent(); verticesCount += renderItem.get()->getVerticesCount(); + _modelMeshRenderItemsMap.insert(item, renderPayload); _modelMeshRenderItemIDs.emplace_back(item); } - somethingAdded = !_modelMeshRenderItemIDs.empty(); + somethingAdded = !_modelMeshRenderItemsMap.empty(); _renderInfoVertexCount = verticesCount; - _renderInfoDrawCalls = (uint32_t) _modelMeshRenderItemIDs.size(); + _renderInfoDrawCalls = _modelMeshRenderItemsMap.count(); _renderInfoHasTransparent = hasTransparent; } @@ -1053,12 +1041,14 @@ bool Model::addToScene(const render::ScenePointer& scene, } void Model::removeFromScene(const render::ScenePointer& scene, render::Transaction& transaction) { - for (auto itemID: _modelMeshRenderItemIDs) { - transaction.removeItem(itemID); + foreach (auto item, _modelMeshRenderItemsMap.keys()) { + transaction.removeItem(item); } _modelMeshRenderItemIDs.clear(); + _modelMeshRenderItemsMap.clear(); _modelMeshRenderItems.clear(); _modelMeshMaterialNames.clear(); + _modelMeshRenderItemShapes.clear(); _priorityMap.clear(); _addedToScene = false; @@ -1237,7 +1227,7 @@ void Model::setURL(const QUrl& url) { invalidCalculatedMeshBoxes(); deleteGeometry(); - auto resource = DependencyManager::get()->getModelResource(url); + auto resource = DependencyManager::get()->getGeometryResource(url); if (resource) { resource->setLoadPriority(this, _loadingPriority); _renderWatcher.setResource(resource); @@ -1426,25 +1416,25 @@ void Model::updateClusterMatrices() { return; } - updateShapeStatesFromRig(); - _needsUpdateClusterMatrices = false; - - for (int skinDeformerIndex = 0; skinDeformerIndex < (int)_meshStates.size(); skinDeformerIndex++) { - MeshState& state = _meshStates[skinDeformerIndex]; - auto numClusters = state.getNumClusters(); - for (uint32_t clusterIndex = 0; clusterIndex < numClusters; clusterIndex++) { - const auto& cbmov = _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(skinDeformerIndex, clusterIndex); + const HFMModel& hfmModel = getHFMModel(); + for (int i = 0; i < (int) _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + int meshIndex = i; + const HFMMesh& mesh = hfmModel.meshes.at(i); + for (int j = 0; j < mesh.clusters.size(); j++) { + const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; if (_useDualQuaternionSkinning) { - auto jointPose = _rig.getJointPose(cbmov.jointIndex); + auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cbmov.inverseBindTransform); - state.clusterDualQuaternions[clusterIndex] = Model::TransformDualQuaternion(clusterTransform); + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); + state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); } else { - auto jointMatrix = _rig.getJointTransform(cbmov.jointIndex); - glm_mat4u_mul(jointMatrix, cbmov.inverseBindMatrix, state.clusterMatrices[clusterIndex]); + auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } @@ -1463,7 +1453,6 @@ void Model::updateBlendshapes() { void Model::deleteGeometry() { _deleteGeometryCounter++; - _shapeStates.clear(); _meshStates.clear(); _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); @@ -1497,12 +1486,20 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { void Model::createRenderItemSet() { assert(isLoaded()); + const auto& meshes = _renderGeometry->getMeshes(); + + // all of our mesh vectors must match in size + if (meshes.size() != _meshStates.size()) { + qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! " << meshes.size() << _meshStates.size() << " We will not segregate mesh groups yet."; + return; + } // We should not have any existing renderItems if we enter this section of code Q_ASSERT(_modelMeshRenderItems.isEmpty()); _modelMeshRenderItems.clear(); _modelMeshMaterialNames.clear(); + _modelMeshRenderItemShapes.clear(); Transform transform; transform.setTranslation(_translation); @@ -1513,19 +1510,28 @@ void Model::createRenderItemSet() { offset.postTranslate(_offset); // Run through all of the meshes, and place them into their segregated, but unsorted buckets - const auto& shapes = _renderGeometry->getHFMModel().shapes; - for (uint32_t shapeID = 0; shapeID < shapes.size(); shapeID++) { - const auto& shape = shapes[shapeID]; + int shapeID = 0; + uint32_t numMeshes = (uint32_t)meshes.size(); + for (uint32_t i = 0; i < numMeshes; i++) { + const auto& mesh = meshes.at(i); + if (!mesh) { + continue; + } - _modelMeshRenderItems << std::make_shared(shared_from_this(), shape.mesh, shape.meshPart, shapeID, transform, offset, _created); - - auto material = getNetworkModel()->getShapeMaterial(shapeID); - _modelMeshMaterialNames.push_back(material ? material->getName() : ""); + // Create the render payloads + int numParts = (int)mesh->getNumParts(); + for (int partIndex = 0; partIndex < numParts; partIndex++) { + _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset, _created); + auto material = getGeometry()->getShapeMaterial(shapeID); + _modelMeshMaterialNames.push_back(material ? material->getName() : ""); + _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); + shapeID++; + } } } bool Model::isRenderable() const { - return (!_shapeStates.empty()) || (isLoaded() && _renderGeometry->getMeshes().empty()); + return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); } std::set Model::getMeshIDsFromMaterialID(QString parentMaterialName) { @@ -1581,11 +1587,11 @@ void Model::applyMaterialMapping() { PrimitiveMode primitiveMode = getPrimitiveMode(); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; auto modelMeshRenderItemIDs = _modelMeshRenderItemIDs; - auto shapeStates = _shapeStates; + auto modelMeshRenderItemShapes = _modelMeshRenderItemShapes; std::unordered_map shouldInvalidatePayloadShapeKeyMap; - for (auto& shape : _shapeStates) { - shouldInvalidatePayloadShapeKeyMap[shape._meshIndex] = shouldInvalidatePayloadShapeKey(shape._meshIndex); + for (auto& shape : _modelMeshRenderItemShapes) { + shouldInvalidatePayloadShapeKeyMap[shape.meshIndex] = shouldInvalidatePayloadShapeKey(shape.meshIndex); } auto& materialMapping = getMaterialMapping(); @@ -1608,7 +1614,7 @@ void Model::applyMaterialMapping() { std::weak_ptr weakSelf = shared_from_this(); auto materialLoaded = [networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning, - modelMeshRenderItemIDs, shapeStates, shouldInvalidatePayloadShapeKeyMap, weakSelf]() { + modelMeshRenderItemIDs, modelMeshRenderItemShapes, shouldInvalidatePayloadShapeKeyMap, weakSelf]() { std::shared_ptr self = weakSelf.lock(); if (!self || networkMaterialResource->isFailed() || networkMaterialResource->parsedMaterials.names.size() == 0) { return; @@ -1634,7 +1640,7 @@ void Model::applyMaterialMapping() { for (auto shapeID : shapeIDs) { if (shapeID < modelMeshRenderItemIDs.size()) { auto itemID = modelMeshRenderItemIDs[shapeID]; - auto meshIndex = shapeStates[shapeID]._meshIndex; + auto meshIndex = modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKeyMap.at(meshIndex); graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, priorityMapPerResource.at(shapeID)); { @@ -1672,7 +1678,7 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; - auto meshIndex = _shapeStates[shapeID]._meshIndex; + auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); transaction.updateItem(itemID, [material, renderItemsKey, invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) { @@ -1694,7 +1700,7 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string auto itemID = _modelMeshRenderItemIDs[shapeID]; auto renderItemsKey = _renderItemKeyGlobalFlags; PrimitiveMode primitiveMode = getPrimitiveMode(); - auto meshIndex = _shapeStates[shapeID]._meshIndex; + auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; transaction.updateItem(itemID, [material, renderItemsKey, @@ -1709,13 +1715,14 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); } -class CollisionRenderGeometry : public NetworkModel { +class CollisionRenderGeometry : public Geometry { public: CollisionRenderGeometry(graphics::MeshPointer mesh) { _hfmModel = std::make_shared(); std::shared_ptr meshes = std::make_shared(); meshes->push_back(mesh); _meshes = meshes; + _meshParts = std::shared_ptr(); } }; @@ -1866,7 +1873,7 @@ void Blender::run() { bool Model::maybeStartBlender() { if (isLoaded()) { - QThreadPool::globalInstance()->start(new Blender(getThisPointer(), getNetworkModel()->getConstHFMModelPointer(), + QThreadPool::globalInstance()->start(new Blender(getThisPointer(), getGeometry()->getConstHFMModelPointer(), ++_blendNumber, _blendshapeCoefficients)); return true; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index fd6bdec8ea..bdb4a35071 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -172,7 +172,7 @@ public: virtual void updateBlendshapes(); /// Returns a reference to the shared geometry. - const NetworkModel::Pointer& getNetworkModel() const { return _renderGeometry; } + const Geometry::Pointer& getGeometry() const { return _renderGeometry; } const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); @@ -291,16 +291,6 @@ public: int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; } bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; } - class ShapeState { - public: - glm::mat4 _rootFromJointTransform; - uint32_t _jointIndex{ hfm::UNDEFINED_KEY }; - uint32_t _meshIndex{ hfm::UNDEFINED_KEY }; - uint32_t _meshPartIndex{ hfm::UNDEFINED_KEY }; - uint32_t _skinDeformerIndex{ hfm::UNDEFINED_KEY }; - }; - const ShapeState& getShapeState(int index) { return _shapeStates.at(index); } - class TransformDualQuaternion { public: TransformDualQuaternion() {} @@ -343,13 +333,12 @@ public: public: std::vector clusterDualQuaternions; std::vector clusterMatrices; - - uint32_t getNumClusters() const { return (uint32_t) std::max(clusterMatrices.size(), clusterMatrices.size()); } }; + const MeshState& getMeshState(int index) { return _meshStates.at(index); } uint32_t getGeometryCounter() const { return _deleteGeometryCounter; } - + const QMap& getRenderItems() const { return _modelMeshRenderItemsMap; } BlendShapeOperator getModelBlendshapeOperator() const { return _modelBlendshapeOperator; } void renderDebugMeshBoxes(gpu::Batch& batch, bool forward); @@ -397,9 +386,9 @@ protected: /// \return true if joint exists bool getJointPosition(int jointIndex, glm::vec3& position) const; - NetworkModel::Pointer _renderGeometry; // only ever set by its watcher + Geometry::Pointer _renderGeometry; // only ever set by its watcher - ModelResourceWatcher _renderWatcher; + GeometryResourceWatcher _renderWatcher; SpatiallyNestable* _spatiallyNestableOverride; @@ -425,10 +414,6 @@ protected: bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to - - std::vector _shapeStates; - void updateShapeStatesFromRig(); - std::vector _meshStates; virtual void initJointStates(); @@ -473,7 +458,10 @@ protected: static AbstractViewStateInterface* _viewState; QVector> _modelMeshRenderItems; + QMap _modelMeshRenderItemsMap; render::ItemIDs _modelMeshRenderItemIDs; + using ShapeInfo = struct { int meshIndex; }; + std::vector _modelMeshRenderItemShapes; std::vector _modelMeshMaterialNames; bool _addedToScene { false }; // has been added to scene @@ -525,7 +513,7 @@ private: }; Q_DECLARE_METATYPE(ModelPointer) -Q_DECLARE_METATYPE(NetworkModel::WeakPointer) +Q_DECLARE_METATYPE(Geometry::WeakPointer) Q_DECLARE_METATYPE(BlendshapeOffset) /// Handle management of pending models that need blending diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index c5792f8331..24d6081743 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -41,29 +41,30 @@ void SoftAttachmentModel::updateClusterMatrices() { _needsUpdateClusterMatrices = false; + const HFMModel& hfmModel = getHFMModel(); - for (int skinDeformerIndex = 0; skinDeformerIndex < (int)_meshStates.size(); skinDeformerIndex++) { - MeshState& state = _meshStates[skinDeformerIndex]; - auto numClusters = state.getNumClusters(); - for (uint32_t clusterIndex = 0; clusterIndex < numClusters; clusterIndex++) { - const auto& cbmov = _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(skinDeformerIndex, clusterIndex); + for (int i = 0; i < (int) _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + const HFMMesh& mesh = hfmModel.meshes.at(i); + int meshIndex = i; + for (int j = 0; j < mesh.clusters.size(); j++) { + const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; // TODO: cache these look-ups as an optimization - int jointIndexOverride = getJointIndexOverride(cbmov.jointIndex); - auto rig = &_rigOverride; + int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); + glm::mat4 jointMatrix; if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { - rig = &_rig; - } - - if (_useDualQuaternionSkinning) { - auto jointPose = rig->getJointPose(cbmov.jointIndex); - Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); - Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cbmov.inverseBindTransform); - state.clusterDualQuaternions[clusterIndex] = Model::TransformDualQuaternion(clusterTransform); + jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); } else { - auto jointMatrix = rig->getJointTransform(cbmov.jointIndex); - glm_mat4u_mul(jointMatrix, cbmov.inverseBindMatrix, state.clusterMatrices[clusterIndex]); + jointMatrix = _rig.getJointTransform(cluster.jointIndex); + } + if (_useDualQuaternionSkinning) { + glm::mat4 m; + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, m); + state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m); + } else { + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 5787295da6..cfb4bb6398 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -392,14 +392,4 @@ inline glm::vec4 extractFov( const glm::mat4& m) { return result; } -inline bool operator<(const glm::vec3& lhs, const glm::vec3& rhs) { - return (lhs.x < rhs.x) || ( - (lhs.x == rhs.x) && ( - (lhs.y < rhs.y) || ( - (lhs.y == rhs.y) && (lhs.z < rhs.z) - ) - ) - ); -} - #endif // hifi_GLMHelpers_h diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index cb9ad41fd0..c60d1c2574 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -189,7 +189,7 @@ uint32_t ShapeInfo::getNumSubShapes() const { return 0; case SHAPE_TYPE_COMPOUND: case SHAPE_TYPE_SIMPLE_COMPOUND: - return (uint32_t)_pointCollection.size(); + return _pointCollection.size(); case SHAPE_TYPE_MULTISPHERE: case SHAPE_TYPE_SIMPLE_HULL: case SHAPE_TYPE_STATIC_MESH: @@ -200,10 +200,10 @@ uint32_t ShapeInfo::getNumSubShapes() const { } } -uint32_t ShapeInfo::getLargestSubshapePointCount() const { - uint32_t numPoints = 0; - for (uint32_t i = 0; i < (uint32_t)_pointCollection.size(); ++i) { - uint32_t n = (uint32_t)_pointCollection[i].size(); +int ShapeInfo::getLargestSubshapePointCount() const { + int numPoints = 0; + for (int i = 0; i < _pointCollection.size(); ++i) { + int n = _pointCollection[i].size(); if (n > numPoints) { numPoints = n; } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 676f38d087..6b0f981b24 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -12,7 +12,7 @@ #ifndef hifi_ShapeInfo_h #define hifi_ShapeInfo_h -#include +#include #include #include #include @@ -53,11 +53,11 @@ class ShapeInfo { public: - using PointList = std::vector; - using PointCollection = std::vector; - using TriangleIndices = std::vector; + using PointList = QVector; + using PointCollection = QVector; + using TriangleIndices = QVector; using SphereData = glm::vec4; - using SphereCollection = std::vector; + using SphereCollection = QVector; static QString getNameForShapeType(ShapeType type); static ShapeType getShapeTypeForName(QString string); @@ -85,7 +85,7 @@ public: TriangleIndices& getTriangleIndices() { return _triangleIndices; } const TriangleIndices& getTriangleIndices() const { return _triangleIndices; } - uint32_t getLargestSubshapePointCount() const; + int getLargestSubshapePointCount() const; float computeVolume() const; diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index da20339123..a5ad5bc891 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -149,11 +149,12 @@ void vhacd::VHACDUtil::fattenMesh(const HFMMesh& mesh, const glm::mat4& modelOff result.vertices << p3; // add the new point to the result mesh HFMMeshPart newMeshPart; + setMeshPartDefaults(newMeshPart, "unknown"); newMeshPart.triangleIndices << index0 << index1 << index2; newMeshPart.triangleIndices << index0 << index3 << index1; newMeshPart.triangleIndices << index1 << index3 << index2; newMeshPart.triangleIndices << index2 << index3 << index0; - result.parts.push_back(newMeshPart); + result.parts.append(newMeshPart); } } @@ -258,8 +259,8 @@ void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, HFMMesh& res VHACD::IVHACD::ConvexHull hull; convexifier->GetConvexHull(j, hull); - resultMesh.parts.push_back(HFMMeshPart()); - HFMMeshPart& resultMeshPart = resultMesh.parts.back(); + resultMesh.parts.append(HFMMeshPart()); + HFMMeshPart& resultMeshPart = resultMesh.parts.last(); int hullIndexStart = resultMesh.vertices.size(); resultMesh.vertices.reserve(hullIndexStart + hull.m_nPoints); @@ -299,8 +300,8 @@ bool vhacd::VHACDUtil::computeVHACD(HFMModel& hfmModel, } // count the mesh-parts - size_t numParts = 0; - for (const HFMMesh& mesh : hfmModel.meshes) { + int numParts = 0; + foreach (const HFMMesh& mesh, hfmModel.meshes) { numParts += mesh.parts.size(); } if (_verbose) { @@ -310,8 +311,8 @@ bool vhacd::VHACDUtil::computeVHACD(HFMModel& hfmModel, VHACD::IVHACD * convexifier = VHACD::CreateVHACD(); result.meshExtents.reset(); - result.meshes.push_back(HFMMesh()); - HFMMesh &resultMesh = result.meshes.back(); + result.meshes.append(HFMMesh()); + HFMMesh &resultMesh = result.meshes.last(); const uint32_t POINT_STRIDE = 3; const uint32_t TRIANGLE_STRIDE = 3; @@ -347,7 +348,7 @@ bool vhacd::VHACDUtil::computeVHACD(HFMModel& hfmModel, if (_verbose) { qDebug() << "mesh" << meshIndex << ": " - << " parts =" << mesh.parts.size() + << " parts =" << mesh.parts.size() << " clusters =" << mesh.clusters.size() << " vertices =" << numVertices; } ++meshIndex; diff --git a/tools/vhacd-util/src/VHACDUtilApp.cpp b/tools/vhacd-util/src/VHACDUtilApp.cpp index 61a6b38181..3d675f8baf 100644 --- a/tools/vhacd-util/src/VHACDUtilApp.cpp +++ b/tools/vhacd-util/src/VHACDUtilApp.cpp @@ -387,7 +387,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : } if (verbose) { - auto totalHulls = result.meshes[0].parts.size(); + int totalHulls = result.meshes[0].parts.size(); qDebug() << "output file =" << outputFilename; qDebug() << "vertices =" << totalVertices; qDebug() << "triangles =" << totalTriangles; @@ -402,7 +402,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : HFMMesh result; // count the mesh-parts - size_t meshCount = 0; + unsigned int meshCount = 0; foreach (const HFMMesh& mesh, fbx.meshes) { meshCount += mesh.parts.size(); } @@ -412,7 +412,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : vUtil.fattenMesh(mesh, fbx.offset, result); } - newFbx.meshes.push_back(result); + newFbx.meshes.append(result); writeOBJ(outputFilename, newFbx, outputCentimeters); } }