diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5a48331eec..adcc178f23 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,7 @@ A new header is inserted each time a *tag* is created. - WebGL: reduce max instance count to work around Chrome issues [⚠️ **Recompile Materials**] - engine: rework material/shader sampler binding code [⚠️ **Recompile Materials**] +- gltfio: move the API for `recomputeBoundingBoxes` [⚠️ **API Change**] ## v1.26.0 diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt index 4b5c7f4e33..deafd62f3c 100644 --- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt @@ -73,8 +73,6 @@ class ModelViewer( get() = resourceLoader.asyncGetLoadProgress() var normalizeSkinningWeights = true - var recomputeBoundingBoxes = false - var ignoreBindTransform = false var cameraFocalLength = 28f set(value) { @@ -116,7 +114,7 @@ class ModelViewer( materialProvider = UbershaderProvider(engine) assetLoader = AssetLoader(engine, materialProvider, EntityManager.get()) - resourceLoader = ResourceLoader(engine, normalizeSkinningWeights, recomputeBoundingBoxes, ignoreBindTransform) + resourceLoader = ResourceLoader(engine, normalizeSkinningWeights) // Always add a direct light source since it is required for shadowing. // We highly recommend adding an indirect light as well. diff --git a/android/gltfio-android/src/main/cpp/ResourceLoader.cpp b/android/gltfio-android/src/main/cpp/ResourceLoader.cpp index 86f884a7c2..7d6e243d69 100644 --- a/android/gltfio-android/src/main/cpp/ResourceLoader.cpp +++ b/android/gltfio-android/src/main/cpp/ResourceLoader.cpp @@ -36,11 +36,9 @@ static void destroy(void*, size_t, void *userData) { extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_gltfio_ResourceLoader_nCreateResourceLoader(JNIEnv*, jclass, - jlong nativeEngine, jboolean normalizeSkinningWeights, jboolean recomputeBoundingBoxes, - jboolean ignoreBindTransform) { + jlong nativeEngine, jboolean normalizeSkinningWeights) { Engine* engine = (Engine*) nativeEngine; - return (jlong) new ResourceLoader({ engine, {}, (bool) normalizeSkinningWeights, - (bool) recomputeBoundingBoxes, (bool) ignoreBindTransform}); + return (jlong) new ResourceLoader({ engine, {}, (bool) normalizeSkinningWeights}); } extern "C" JNIEXPORT void JNICALL diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java index a8d1fffaba..37be1dbac6 100644 --- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java +++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java @@ -47,7 +47,7 @@ public class ResourceLoader { */ public ResourceLoader(@NonNull Engine engine) { long nativeEngine = engine.getNativeObject(); - mNativeObject = nCreateResourceLoader(nativeEngine, false, false, false); + mNativeObject = nCreateResourceLoader(nativeEngine, false); mNativeStbProvider = nCreateStbProvider(nativeEngine); mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine); nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider); @@ -60,16 +60,12 @@ public class ResourceLoader { * * @param engine the engine that gets passed to all builder methods * @param normalizeSkinningWeights scale non-conformant skinning weights so they sum to 1 - * @param recomputeBoundingBoxes use computed bounding boxes rather than the ones in the asset - * @param ignoreBindTransform ignore skinned primitives bind transform when compute bounding boxes * @throws IllegalAccessException * @throws InvocationTargetException */ - public ResourceLoader(@NonNull Engine engine, boolean normalizeSkinningWeights, - boolean recomputeBoundingBoxes, boolean ignoreBindTransform) { + public ResourceLoader(@NonNull Engine engine, boolean normalizeSkinningWeights) { long nativeEngine = engine.getNativeObject(); - mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights, - recomputeBoundingBoxes, ignoreBindTransform); + mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights); mNativeStbProvider = nCreateStbProvider(nativeEngine); mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine); nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider); @@ -181,7 +177,7 @@ public class ResourceLoader { } private static native long nCreateResourceLoader(long nativeEngine, - boolean normalizeSkinningWeights, boolean recomputeBoundingBoxes, boolean ignoreBindTransform); + boolean normalizeSkinningWeights); private static native void nDestroyResourceLoader(long nativeLoader); private static native void nAddResourceData(long nativeLoader, String url, Buffer buffer, int remaining); diff --git a/ios/samples/gltf-viewer/gltf-viewer/FILModelView.mm b/ios/samples/gltf-viewer/gltf-viewer/FILModelView.mm index d36e8b994e..ac5cf7ee18 100644 --- a/ios/samples/gltf-viewer/gltf-viewer/FILModelView.mm +++ b/ios/samples/gltf-viewer/gltf-viewer/FILModelView.mm @@ -133,7 +133,7 @@ const float kSensitivity = 100.0f; NameComponentManager* ncm = new NameComponentManager(em); _assetLoader = AssetLoader::create({_engine, _materialProvider, ncm, &em}); _resourceLoader = new ResourceLoader( - {.engine = _engine, .normalizeSkinningWeights = true, .recomputeBoundingBoxes = false}); + {.engine = _engine, .normalizeSkinningWeights = true}); _stbDecoder = createStbProvider(_engine); _ktxDecoder = createKtx2Provider(_engine); _resourceLoader->addTextureProvider("image/png", _stbDecoder); diff --git a/ios/samples/hello-gltf/hello-gltf/FilamentView/App.cpp b/ios/samples/hello-gltf/hello-gltf/FilamentView/App.cpp index 2fcb918929..ba97df9671 100644 --- a/ios/samples/hello-gltf/hello-gltf/FilamentView/App.cpp +++ b/ios/samples/hello-gltf/hello-gltf/FilamentView/App.cpp @@ -141,8 +141,7 @@ void App::setupMesh() { filament::gltfio::ResourceLoader({ .engine = engine, - .normalizeSkinningWeights = true, - .recomputeBoundingBoxes = false + .normalizeSkinningWeights = true }).loadResources(app.asset); scene->addEntities(app.asset->getEntities(), app.asset->getEntityCount()); diff --git a/libs/gltfio/include/gltfio/FilamentAsset.h b/libs/gltfio/include/gltfio/FilamentAsset.h index 970e7e9a1f..a8419aafa0 100644 --- a/libs/gltfio/include/gltfio/FilamentAsset.h +++ b/libs/gltfio/include/gltfio/FilamentAsset.h @@ -318,6 +318,11 @@ public: */ void detachMaterialInstances(); + /** + * Convenience function to get the first instance (which always exists). + */ + FilamentInstance* getInstance() noexcept { return getAssetInstances()[0]; } + /*! \cond PRIVATE */ FilamentInstance** getAssetInstances() noexcept; diff --git a/libs/gltfio/include/gltfio/FilamentInstance.h b/libs/gltfio/include/gltfio/FilamentInstance.h index b6de4eeac2..ceed2fc5d5 100644 --- a/libs/gltfio/include/gltfio/FilamentInstance.h +++ b/libs/gltfio/include/gltfio/FilamentInstance.h @@ -20,6 +20,8 @@ #include #include +#include + namespace filament::gltfio { class Animator; @@ -109,6 +111,24 @@ public: * This is a no-op if the given skin index or target is invalid. */ void detachSkin(size_t skinIndex, utils::Entity target) noexcept; + + /** + * Resets the AABB on all renderables by manually computing the bounding box. + * + * THIS IS ONLY USEFUL FOR MALFORMED ASSETS THAT DO NOT HAVE MIN/MAX SET UP CORRECTLY. + * + * Does not affect the return value of getBoundingBox() on the owning asset. + * Cannot be called after releaseSourceData() on the owning asset. + * Can only be called after loadResources() or asyncBeginLoad(). + */ + void recomputeBoundingBoxes(); + + /** + * Gets the axis-aligned bounding box from the supplied min / max values in glTF accessors. + * + * If recomputeBoundingBoxes() has been called, then this returns the recomputed AABB. + */ + Aabb getBoundingBox() const noexcept; }; } // namespace filament::gltfio diff --git a/libs/gltfio/include/gltfio/ResourceLoader.h b/libs/gltfio/include/gltfio/ResourceLoader.h index 205a2b6770..ab0a6f10fa 100644 --- a/libs/gltfio/include/gltfio/ResourceLoader.h +++ b/libs/gltfio/include/gltfio/ResourceLoader.h @@ -49,14 +49,6 @@ struct ResourceConfiguration { //! If true, adjusts skinning weights to sum to 1. Well formed glTF files do not need this, //! but it is useful for robustness. bool normalizeSkinningWeights; - - //! If true, computes the bounding boxes of all \c POSITION attibutes. Well formed glTF files - //! do not need this, but it is useful for robustness. - bool recomputeBoundingBoxes; - - //! If true, ignores skinning when computing bounding boxes. Implicitly true for instanced - //! assets. Only applicable when recomputeBoundingBoxes is set to true. - bool ignoreBindTransform; }; /** @@ -162,7 +154,6 @@ public: private: bool loadResources(FFilamentAsset* asset, bool async); void normalizeSkinningWeights(FFilamentAsset* asset) const; - void updateBoundingBoxes(FFilamentAsset* asset) const; AssetPool* mPool; struct Impl; Impl* pImpl; diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 6fd1f8371a..9d3b3e1fb3 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -373,6 +373,8 @@ FFilamentInstance* FAssetLoader::createInstance(FFilamentAsset* primary, for (const auto& pair : mRootNodes) { createEntity(srcAsset, pair.first, pair.second, instanceRoot, false, instance); } + + instance->boundingBox = primary->mBoundingBox; return instance; } @@ -539,8 +541,7 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node } // Per the spec, glTF models must have valid mix / max annotations for position attributes. - // However in practice these can be missing and we should be as robust as other glTF viewers. - // If desired, clients can enable the "recomputeBoundingBoxes" feature in ResourceLoader. + // If desired, clients can call "recomputeBoundingBoxes()" in FilamentInstance. Box box = Box().set(aabb.min, aabb.max); if (box.isEmpty()) { slog.w << "Missing bounding box in " << name << io::endl; diff --git a/libs/gltfio/src/FFilamentInstance.h b/libs/gltfio/src/FFilamentInstance.h index af9806528e..2c7ada6ad4 100644 --- a/libs/gltfio/src/FFilamentInstance.h +++ b/libs/gltfio/src/FFilamentInstance.h @@ -80,6 +80,7 @@ struct FFilamentInstance : public FilamentInstance { FFilamentAsset* owner; SkinVector skins; NodeMap nodeMap; + filament::Aabb boundingBox; void createAnimator(); Animator* getAnimator() const noexcept; size_t getSkinCount() const noexcept; @@ -89,6 +90,7 @@ struct FFilamentInstance : public FilamentInstance { void attachSkin(size_t skinIndex, utils::Entity target) noexcept; void detachSkin(size_t skinIndex, utils::Entity target) noexcept; void applyMaterialVariant(size_t variantIndex) noexcept; + void recomputeBoundingBoxes(); }; FILAMENT_UPCAST(FilamentInstance) diff --git a/libs/gltfio/src/FilamentInstance.cpp b/libs/gltfio/src/FilamentInstance.cpp index fccd3f8ec6..606438afe2 100644 --- a/libs/gltfio/src/FilamentInstance.cpp +++ b/libs/gltfio/src/FilamentInstance.cpp @@ -19,9 +19,11 @@ #include +#include #include using namespace filament; +using namespace filament::math; using namespace utils; namespace filament::gltfio { @@ -87,6 +89,189 @@ void FFilamentInstance::applyMaterialVariant(size_t variantIndex) noexcept { } } +void FFilamentInstance::recomputeBoundingBoxes() { + ASSERT_PRECONDITION(owner->mSourceAsset, + "Do not call releaseSourceData before recomputeBoundingBoxes"); + + ASSERT_PRECONDITION(owner->mResourcesLoaded, + "Do not call recomputeBoundingBoxes before loadResources or asyncBeginLoad"); + + auto& rm = owner->mEngine->getRenderableManager(); + auto& tm = owner->mEngine->getTransformManager(); + + // The purpose of the root node is to give the client a place for custom transforms. + // Since it is not part of the source model, it should be ignored when computing the + // bounding box. + TransformManager::Instance root = tm.getInstance(owner->getRoot()); + utils::FixedCapacityVector modelRoots(tm.getChildCount(root)); + tm.getChildren(root, modelRoots.data(), modelRoots.size()); + for (auto e : modelRoots) { + tm.setParent(tm.getInstance(e), 0); + } + + auto computeBoundingBox = [](const cgltf_primitive* prim) -> Aabb { + Aabb aabb; + for (cgltf_size slot = 0; slot < prim->attributes_count; slot++) { + const cgltf_attribute& attr = prim->attributes[slot]; + const cgltf_accessor* accessor = attr.data; + const size_t dim = cgltf_num_components(accessor->type); + if (attr.type == cgltf_attribute_type_position && dim >= 3) { + utils::FixedCapacityVector unpacked(accessor->count * dim); + cgltf_accessor_unpack_floats(accessor, unpacked.data(), unpacked.size()); + for (cgltf_size i = 0, j = 0, n = accessor->count; i < n; ++i, j += dim) { + float3 pt(unpacked[j + 0], unpacked[j + 1], unpacked[j + 2]); + aabb.min = min(aabb.min, pt); + aabb.max = max(aabb.max, pt); + } + break; + } + } + return aabb; + }; + + struct Prim { + cgltf_primitive const* prim; + Skin const* skin; + Entity node; + }; + + auto computeBoundingBoxSkinned = [&tm](const Prim& prim) -> Aabb { + FixedCapacityVector verts; + FixedCapacityVector joints; + FixedCapacityVector weights; + for (cgltf_size slot = 0, n = prim.prim->attributes_count; slot < n; ++slot) { + const cgltf_attribute& attr = prim.prim->attributes[slot]; + const cgltf_accessor& accessor = *attr.data; + switch (attr.type) { + case cgltf_attribute_type_position: + verts = FixedCapacityVector(accessor.count); + cgltf_accessor_unpack_floats(&accessor, &verts.data()->x, accessor.count * 3); + break; + case cgltf_attribute_type_joints: { + FixedCapacityVector tmp(accessor.count); + cgltf_accessor_unpack_floats(&accessor, &tmp.data()->x, accessor.count * 4); + joints = FixedCapacityVector(accessor.count); + for (size_t i = 0, n = accessor.count; i < n; ++i) { + joints[i] = uint4(tmp[i]); + } + break; + } + case cgltf_attribute_type_weights: + weights = FixedCapacityVector(accessor.count); + cgltf_accessor_unpack_floats(&accessor, &weights.data()->x, accessor.count * 4); + break; + default: + break; + } + } + + Aabb aabb; + TransformManager::Instance transformable = tm.getInstance(prim.node); + const mat4f inverseGlobalTransform = inverse(tm.getWorldTransform(transformable)); + for (size_t i = 0, n = verts.size(); i < n; i++) { + mat4f tmp = mat4f(0.0f); + for (size_t j = 0; j < 4; j++) { + size_t jointIndex = joints[i][j]; + Entity jointEntity = prim.skin->joints[jointIndex]; + mat4f globalJointTransform = tm.getWorldTransform(tm.getInstance(jointEntity)); + mat4f inverseBindMatrix = prim.skin->inverseBindMatrices[jointIndex]; + tmp += weights[i][j] * globalJointTransform * inverseBindMatrix; + } + const mat4f skinMatrix = inverseGlobalTransform * tmp; + + const float3 point = verts[i]; + + // NOTE: Filament's vertex shader assumes that last row is [0,0,0,1] + // so we make the same assumption in the following transformation. + + const float3 skinnedPoint = + point.x * skinMatrix[0].xyz + + point.y * skinMatrix[1].xyz + + point.z * skinMatrix[2].xyz + + skinMatrix[3].xyz; + + aabb.min = min(aabb.min, skinnedPoint); + aabb.max = max(aabb.max, skinnedPoint); + } + return aabb; + }; + + // Collect all mesh primitives that we wish to find bounds for. For each mesh primitive, we also + // collect the skin it is bound to (nullptr if not skinned) for bounds computation. + size_t primCount = 0; + for (auto iter : nodeMap) { + const cgltf_mesh* mesh = iter.first->mesh; + if (mesh) { + primCount += mesh->primitives_count; + } + } + auto primitives = FixedCapacityVector::with_capacity(primCount); + const cgltf_skin* baseSkin = &owner->mSourceAsset->hierarchy->skins[0]; + for (auto iter : nodeMap) { + const cgltf_mesh* mesh = iter.first->mesh; + if (mesh) { + for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) { + primitives.push_back({&mesh->primitives[index], nullptr, iter.second}); + } + if (cgltf_skin* const skin = iter.first->skin; skin) { + primitives.back().skin = &skins[skin - baseSkin]; + } + } + } + + // Kick off a bounding box job for every primitive. + FixedCapacityVector bounds(primitives.size()); + JobSystem* js = &owner->mEngine->getJobSystem(); + JobSystem::Job* parent = js->createJob(); + for (size_t i = 0; i < primitives.size(); ++i) { + Aabb* result = &bounds[i]; + if (primitives[i].skin == nullptr) { + cgltf_primitive const* prim = primitives[i].prim; + js->run(jobs::createJob(*js, parent, [prim, result, computeBoundingBox] { + *result = computeBoundingBox(prim); + })); + } else { + const Prim& prim = primitives[i]; + js->run(jobs::createJob(*js, parent, [&prim, result, computeBoundingBoxSkinned] { + *result = computeBoundingBoxSkinned(prim); + })); + } + } + js->runAndWait(parent); + + // Compute the asset-level bounding box. + size_t primIndex = 0; + Aabb assetBounds; + for (auto iter : nodeMap) { + const cgltf_mesh* mesh = iter.first->mesh; + if (mesh) { + // Find the object-space bounds for the renderable by unioning the bounds of each prim. + Aabb aabb; + for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) { + Aabb primBounds = bounds[primIndex++]; + aabb.min = min(aabb.min, primBounds.min); + aabb.max = max(aabb.max, primBounds.max); + } + auto renderable = rm.getInstance(iter.second); + rm.setAxisAlignedBoundingBox(renderable, Box().set(aabb.min, aabb.max)); + + // Transform this bounding box, then update the asset-level bounding box. + auto transformable = tm.getInstance(iter.second); + const mat4f worldTransform = tm.getWorldTransform(transformable); + const Aabb transformed = aabb.transform(worldTransform); + assetBounds.min = min(assetBounds.min, transformed.min); + assetBounds.max = max(assetBounds.max, transformed.max); + } + } + + // Restore the root node. + for (auto e : modelRoots) { + tm.setParent(tm.getInstance(e), root); + } + + boundingBox = assetBounds; +} + FilamentAsset* FilamentInstance::getAsset() const noexcept { return upcast(this)->owner; } @@ -136,5 +321,12 @@ void FilamentInstance::detachSkin(size_t skinIndex, Entity target) noexcept { return upcast(this)->detachSkin(skinIndex, target); } +void FilamentInstance::recomputeBoundingBoxes() { + return upcast(this)->recomputeBoundingBoxes(); +} + +Aabb FilamentInstance::getBoundingBox() const noexcept { + return upcast(this)->boundingBox; +} } // namespace filament::gltfio diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 2bbd1bc43e..f31fb4563e 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -67,15 +67,11 @@ struct ResourceLoader::Impl { Impl(const ResourceConfiguration& config) : mEngine(config.engine), mNormalizeSkinningWeights(config.normalizeSkinningWeights), - mRecomputeBoundingBoxes(config.recomputeBoundingBoxes), - mGltfPath(config.gltfPath ? config.gltfPath : ""), - mIgnoreBindTransform(config.ignoreBindTransform) {} + mGltfPath(config.gltfPath ? config.gltfPath : "") {} Engine* const mEngine; const bool mNormalizeSkinningWeights; - const bool mRecomputeBoundingBoxes; const std::string mGltfPath; - const bool mIgnoreBindTransform; // User-provided resource data with URI string keys, populated with addResourceData(). // This is used on platforms without traditional file systems, such as Android, iOS, and WebGL. @@ -446,10 +442,6 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { } } - if (pImpl->mRecomputeBoundingBoxes) { - updateBoundingBoxes(asset); - } - Engine& engine = *pImpl->mEngine; // Upload VertexBuffer and IndexBuffer data to the GPU. @@ -854,176 +846,4 @@ void ResourceLoader::normalizeSkinningWeights(FFilamentAsset* asset) const { } } -void ResourceLoader::updateBoundingBoxes(FFilamentAsset* asset) const { - SYSTRACE_CALL(); - auto& rm = pImpl->mEngine->getRenderableManager(); - auto& tm = pImpl->mEngine->getTransformManager(); - const NodeMap& nodeMap = asset->mInstances[0]->nodeMap; - - // The purpose of the root node is to give the client a place for custom transforms. - // Since it is not part of the source model, it should be ignored when computing the - // bounding box. - TransformManager::Instance root = tm.getInstance(asset->getRoot()); - std::vector modelRoots(tm.getChildCount(root)); - tm.getChildren(root, modelRoots.data(), modelRoots.size()); - for (auto e : modelRoots) { - tm.setParent(tm.getInstance(e), 0); - } - - auto computeBoundingBox = [](const cgltf_primitive* prim, Aabb* result) { - Aabb aabb; - for (cgltf_size slot = 0; slot < prim->attributes_count; slot++) { - const cgltf_attribute& attr = prim->attributes[slot]; - const cgltf_accessor* accessor = attr.data; - const size_t dim = cgltf_num_components(accessor->type); - if (attr.type == cgltf_attribute_type_position && dim >= 3) { - std::vector unpacked(accessor->count * dim); - cgltf_accessor_unpack_floats(accessor, unpacked.data(), unpacked.size()); - for (cgltf_size i = 0, j = 0, n = accessor->count; i < n; ++i, j += dim) { - float3 pt(unpacked[j + 0], unpacked[j + 1], unpacked[j + 2]); - aabb.min = min(aabb.min, pt); - aabb.max = max(aabb.max, pt); - } - break; - } - } - *result = aabb; - }; - - struct Prim { - cgltf_primitive const* prim; - Skin const* skin; - Entity node; - }; - - auto computeBoundingBoxSkinned = [&](const Prim& prim, Aabb* result) { - FixedCapacityVector verts; - FixedCapacityVector joints; - FixedCapacityVector weights; - for (cgltf_size slot = 0, n = prim.prim->attributes_count; slot < n; ++slot) { - const cgltf_attribute& attr = prim.prim->attributes[slot]; - const cgltf_accessor& accessor = *attr.data; - switch (attr.type) { - case cgltf_attribute_type_position: - verts = FixedCapacityVector(accessor.count); - cgltf_accessor_unpack_floats(&accessor, &verts.data()->x, accessor.count * 3); - break; - case cgltf_attribute_type_joints: { - FixedCapacityVector tmp(accessor.count); - cgltf_accessor_unpack_floats(&accessor, &tmp.data()->x, accessor.count * 4); - joints = FixedCapacityVector(accessor.count); - for (size_t i = 0, n = accessor.count; i < n; ++i) { - joints[i] = uint4(tmp[i]); - } - break; - } - case cgltf_attribute_type_weights: - weights = FixedCapacityVector(accessor.count); - cgltf_accessor_unpack_floats(&accessor, &weights.data()->x, accessor.count * 4); - break; - default: - break; - } - } - - Aabb aabb; - TransformManager::Instance transformable = tm.getInstance(prim.node); - const mat4f inverseGlobalTransform = inverse(tm.getWorldTransform(transformable)); - for (size_t i = 0, n = verts.size(); i < n; i++) { - float3 point = verts[i]; - mat4f tmp = mat4f(0.0f); - for (size_t j = 0; j < 4; j++) { - size_t jointIndex = joints[i][j]; - Entity jointEntity = prim.skin->joints[jointIndex]; - mat4f globalJointTransform = tm.getWorldTransform(tm.getInstance(jointEntity)); - mat4f inverseBindMatrix = prim.skin->inverseBindMatrices[jointIndex]; - tmp += weights[i][j] * globalJointTransform * inverseBindMatrix; - } - mat4f skinMatrix = inverseGlobalTransform * tmp; - if (!pImpl->mNormalizeSkinningWeights) { - skinMatrix /= skinMatrix[3].w; - } - float3 skinnedPoint = (point.x * skinMatrix[0] + - point.y * skinMatrix[1] + point.z * skinMatrix[2] + skinMatrix[3]).xyz; - aabb.min = min(aabb.min, skinnedPoint); - aabb.max = max(aabb.max, skinnedPoint); - } - *result = aabb; - }; - - // Collect all mesh primitives that we wish to find bounds for. For each mesh primitive, we also - // collect the skin it is bound to (nullptr if not skinned) for bounds computation. - size_t primCount = 0; - for (auto iter : nodeMap) { - const cgltf_mesh* mesh = iter.first->mesh; - if (mesh) { - primCount += mesh->primitives_count; - } - } - auto primitives = FixedCapacityVector::with_capacity(primCount); - const cgltf_skin* baseSkin = &asset->mSourceAsset->hierarchy->skins[0]; - for (auto iter : nodeMap) { - const cgltf_mesh* mesh = iter.first->mesh; - if (mesh) { - for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) { - primitives.push_back({&mesh->primitives[index], nullptr, iter.second}); - } - if (cgltf_skin* const skin = iter.first->skin; skin) { - primitives.back().skin = &asset->mInstances[0]->skins[skin - baseSkin]; - } - } - } - - // Kick off a bounding box job for every primitive. - FixedCapacityVector bounds(primitives.size()); - JobSystem* js = &pImpl->mEngine->getJobSystem(); - JobSystem::Job* parent = js->createJob(); - for (size_t i = 0; i < primitives.size(); ++i) { - Aabb* result = &bounds[i]; - if (pImpl->mIgnoreBindTransform || primitives[i].skin == nullptr) { - cgltf_primitive const* prim = primitives[i].prim; - js->run(jobs::createJob(*js, parent, [prim, result, computeBoundingBox] { - computeBoundingBox(prim, result); - })); - } else { - const Prim& prim = primitives[i]; - js->run(jobs::createJob(*js, parent, [&prim, result, computeBoundingBoxSkinned] { - computeBoundingBoxSkinned(prim, result); - })); - } - } - js->runAndWait(parent); - - // Compute the asset-level bounding box. - size_t primIndex = 0; - Aabb assetBounds; - for (auto iter : nodeMap) { - const cgltf_mesh* mesh = iter.first->mesh; - if (mesh) { - // Find the object-space bounds for the renderable by unioning the bounds of each prim. - Aabb aabb; - for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) { - Aabb primBounds = bounds[primIndex++]; - aabb.min = min(aabb.min, primBounds.min); - aabb.max = max(aabb.max, primBounds.max); - } - auto renderable = rm.getInstance(iter.second); - rm.setAxisAlignedBoundingBox(renderable, Box().set(aabb.min, aabb.max)); - - // Transform this bounding box, then update the asset-level bounding box. - auto transformable = tm.getInstance(iter.second); - const mat4f worldTransform = tm.getWorldTransform(transformable); - const Aabb transformed = aabb.transform(worldTransform); - assetBounds.min = min(assetBounds.min, transformed.min); - assetBounds.max = max(assetBounds.max, transformed.max); - } - } - - for (auto e : modelRoots) { - tm.setParent(tm.getInstance(e), root); - } - - asset->mBoundingBox = assetBounds; -} - } // namespace filament::gltfio diff --git a/libs/viewer/src/ViewerGui.cpp b/libs/viewer/src/ViewerGui.cpp index b000af2352..62f7b5500e 100644 --- a/libs/viewer/src/ViewerGui.cpp +++ b/libs/viewer/src/ViewerGui.cpp @@ -457,7 +457,7 @@ void ViewerGui::updateRootTransform() { auto root = tcm.getInstance(mAsset->getRoot()); filament::math::mat4f transform; if (mSettings.viewer.autoScaleEnabled) { - transform = fitIntoUnitCube(mAsset->getBoundingBox(), 4); + transform = fitIntoUnitCube(mAsset->getInstance()->getBoundingBox(), 4); } tcm.setTransform(root, transform); } diff --git a/samples/gltf_instances.cpp b/samples/gltf_instances.cpp index 828891c7fe..55734a0b7c 100644 --- a/samples/gltf_instances.cpp +++ b/samples/gltf_instances.cpp @@ -211,7 +211,6 @@ int main(int argc, char** argv) { configuration.engine = app.engine; configuration.gltfPath = gltfPath.c_str(); configuration.normalizeSkinningWeights = true; - configuration.recomputeBoundingBoxes = false; if (!app.resourceLoader) { app.resourceLoader = new gltfio::ResourceLoader(configuration); app.stbDecoder = createStbProvider(app.engine); diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 0e85722232..b8782df84d 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -89,7 +89,6 @@ struct App { gltfio::TextureProvider* stbDecoder = nullptr; gltfio::TextureProvider* ktxDecoder = nullptr; bool recomputeAabb = false; - bool ignoreBindTransform = false; bool actualSize = false; @@ -139,8 +138,6 @@ static void printUsage(char* name) { " Do not scale the model to fit into a unit cube\n\n" " --recompute-aabb, -r\n" " Ignore the min/max attributes in the glTF file\n\n" - " --ignore-bind-transform, -g\n" - " Ignore bind transform when recomputing aabb (use with -r)\n\n" " --settings=, -t\n" " Apply the settings in the given JSON file\n\n" " --ubershader, -u\n" @@ -169,7 +166,7 @@ static std::ifstream::pos_type getFileSize(const char* filename) { } static int handleCommandLineArguments(int argc, char* argv[], App* app) { - static constexpr const char* OPTSTR = "ha:i:usc:rgt:b:ev"; + static constexpr const char* OPTSTR = "ha:i:usc:rt:b:ev"; static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' }, { "api", required_argument, nullptr, 'a' }, @@ -180,7 +177,6 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { { "actual-size", no_argument, nullptr, 's' }, { "camera", required_argument, nullptr, 'c' }, { "recompute-aabb", no_argument, nullptr, 'r' }, - { "ignore-bind-transform", no_argument, nullptr, 'g' }, { "settings", required_argument, nullptr, 't' }, { "split-view", no_argument, nullptr, 'v' }, { nullptr, 0, nullptr, 0 } @@ -229,9 +225,6 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { case 'r': app->recomputeAabb = true; break; - case 'g': - app->ignoreBindTransform = true; - break; case 't': app->settingsFile = arg; break; @@ -429,8 +422,6 @@ int main(int argc, char** argv) { ResourceConfiguration configuration = {}; configuration.engine = app.engine; configuration.gltfPath = gltfPath.c_str(); - configuration.recomputeBoundingBoxes = app.recomputeAabb; - configuration.ignoreBindTransform = app.ignoreBindTransform; configuration.normalizeSkinningWeights = true; if (!app.resourceLoader) { @@ -447,6 +438,10 @@ int main(int argc, char** argv) { exit(1); } + if (app.recomputeAabb) { + app.asset->getInstance()->recomputeBoundingBoxes(); + } + app.asset->releaseSourceData(); auto ibl = FilamentApp::get().getIBL(); diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index 1a7c0e6a8f..74b533d1df 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -550,8 +550,7 @@ Filament.loadClassExtensions = function() { // The optional asyncInterval argument allows clients to control how decoding is amortized // over time. It represents the number of milliseconds between each texture decoding task. // - // The optional config argument is an object with boolean fields `normalizeSkinningWeights` and - // `recomputeBoundingBoxes`. + // The optional config argument is an object with boolean field `normalizeSkinningWeights`. Filament.gltfio$FilamentAsset.prototype.loadResources = function(onDone, onFetched, basePath, asyncInterval, config) { const asset = this; @@ -559,8 +558,6 @@ Filament.loadClassExtensions = function() { const interval = asyncInterval || 30; const defaults = { normalizeSkinningWeights: true, - recomputeBoundingBoxes: false, - ignoreBindTransform: false }; config = Object.assign(defaults, config || {}); @@ -586,9 +583,7 @@ Filament.loadClassExtensions = function() { // Construct a resource loader and start decoding after all textures are fetched. const resourceLoader = new Filament.gltfio$ResourceLoader(engine, - config.normalizeSkinningWeights, - config.recomputeBoundingBoxes, - config.ignoreBindTransform); + config.normalizeSkinningWeights); const stbProvider = new Filament.gltfio$StbProvider(engine); const ktx2Provider = new Filament.gltfio$Ktx2Provider(engine); diff --git a/web/filament-js/filament-viewer.js b/web/filament-js/filament-viewer.js index 714b78fc17..b7021b3c42 100644 --- a/web/filament-js/filament-viewer.js +++ b/web/filament-js/filament-viewer.js @@ -304,8 +304,6 @@ class FilamentViewer extends LitElement { const config = { normalizeSkinningWeights: true, - recomputeBoundingBoxes: false, - ignoreBindTransform: false, asyncInterval: 30 }; @@ -333,9 +331,7 @@ class FilamentViewer extends LitElement { this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset); const resourceLoader = new Filament.gltfio$ResourceLoader(this.engine, - config.normalizeSkinningWeights, - config.recomputeBoundingBoxes, - config.ignoreBindTransform); + config.normalizeSkinningWeights); const stbProvider = new Filament.gltfio$StbProvider(this.engine); const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine); diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 3cc50a17f3..0ce003b771 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1920,14 +1920,11 @@ class_("gltfio$AssetLoader") .function("destroyAsset", &AssetLoader::destroyAsset, allow_raw_pointers()); class_("gltfio$ResourceLoader") - .constructor(EMBIND_LAMBDA(ResourceLoader*, (Engine* engine, bool normalizeSkinningWeights, - bool recomputeBoundingBoxes, bool ignoreBindTransform), { + .constructor(EMBIND_LAMBDA(ResourceLoader*, (Engine* engine, bool normalizeSkinningWeights), { return new ResourceLoader({ .engine = engine, .gltfPath = nullptr, - .normalizeSkinningWeights = normalizeSkinningWeights, - .recomputeBoundingBoxes = recomputeBoundingBoxes, - .ignoreBindTransform = ignoreBindTransform + .normalizeSkinningWeights = normalizeSkinningWeights }); }), allow_raw_pointers())