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 d9435109fc..59341e7094 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 @@ -70,6 +70,9 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { val progress get() = resourceLoader.asyncGetLoadProgress() + var normalizeSkinningWeights = true + var recomputeBoundingBoxes = false + val scene: Scene val view: View val camera: Camera @@ -101,7 +104,7 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { view.camera = camera assetLoader = AssetLoader(engine, MaterialProvider(engine), EntityManager.get()) - resourceLoader = ResourceLoader(engine, false, false) + resourceLoader = ResourceLoader(engine, normalizeSkinningWeights, recomputeBoundingBoxes) // Always add a direct light source since it is required for shadowing. // We highly recommend adding an indirect light as well. diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index b2439f48ae..1a8342b400 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -296,7 +296,7 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { } asset->mResourcesLoaded = true; mPool->addAsset(asset); - auto gltf = (cgltf_data*) asset->mSourceAsset; + const cgltf_data* gltf = asset->mSourceAsset; cgltf_options options {}; // For emscripten and Android builds we have a custom implementation of cgltf_load_buffers which @@ -361,7 +361,7 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { #else // Read data from the file system and base64 URIs. - cgltf_result result = cgltf_load_buffers(&options, gltf, pImpl->mGltfPath.c_str()); + cgltf_result result = cgltf_load_buffers(&options, (cgltf_data*) gltf, pImpl->mGltfPath.c_str()); if (result != cgltf_result_success) { slog.e << "Unable to load resources." << io::endl; return false; @@ -371,7 +371,7 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { SYSTRACE_NAME_END(); #ifndef NDEBUG - if (cgltf_validate(gltf) != cgltf_result_success) { + if (cgltf_validate((cgltf_data*) gltf) != cgltf_result_success) { slog.e << "Failed cgltf validation." << io::endl; return false; } @@ -381,13 +381,19 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { // tangent generation. decodeDracoMeshes(asset); - // To be robust against the glTF conformance suite, we optionally ensure that skinning weights - // sum to 1.0 at every vertex. Note that if the same weights buffer is shared in multiple - // places, this will needlessly repeat the work. In the future we would like to remove this - // feature, and instead simply require correct models. See also: - // https://github.com/KhronosGroup/glTF-Sample-Models/issues/215 - if (pImpl->mNormalizeSkinningWeights) { - normalizeSkinningWeights(asset); + // Normalize skinning weights, then "import" each skin into the asset by building a mapping of + // skins to their affected entities. + if (gltf->skins_count > 0) { + if (pImpl->mNormalizeSkinningWeights) { + normalizeSkinningWeights(asset); + } + if (asset->mInstances.empty()) { + importSkins(gltf, asset->mNodeMap, asset->mSkins); + } else { + for (FFilamentInstance* instance : asset->mInstances) { + importSkins(gltf, instance->nodeMap, instance->skins); + } + } } if (pImpl->mRecomputeBoundingBoxes) { @@ -425,16 +431,6 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { slot.indexBuffer->setBuffer(engine, std::move(bd)); } - // For each skin, build a list of renderables that it affects. - const cgltf_data* const_gltf = gltf; - if (asset->mInstances.empty()) { - importSkins(const_gltf, asset->mNodeMap, asset->mSkins); - } else { - for (FFilamentInstance* instance : asset->mInstances) { - importSkins(const_gltf, instance->nodeMap, instance->skins); - } - } - // Apply sparse data modifications to base arrays, then upload the result. applySparseData(asset); diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index f3c1d61a5a..3a993c2daa 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -620,6 +620,7 @@ int main(int argc, char** argv) { configuration.engine = app.engine; configuration.gltfPath = gltfPath.c_str(); configuration.recomputeBoundingBoxes = app.recomputeAabb; + configuration.normalizeSkinningWeights = true; if (!app.resourceLoader) { app.resourceLoader = new gltfio::ResourceLoader(configuration); } diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index 85b20a3e6e..9894d2d486 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -396,12 +396,20 @@ 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`. Filament.gltfio$FilamentAsset.prototype.loadResources = function(onDone, onFetched, basePath, - asyncInterval) { + asyncInterval, config) { const asset = this; const engine = this.getEngine(); const names = this.getResourceUris(); const interval = asyncInterval || 30; + const defaults = { + normalizeSkinningWeights: true, + recomputeBoundingBoxes: false + }; + config = Object.assign(defaults, config || {}); basePath = basePath || document.location; onFetched = onFetched || ((name) => {}); @@ -420,7 +428,9 @@ Filament.loadClassExtensions = function() { } // Construct a resource loader and start decoding after all textures are fetched. - const resourceLoader = new Filament.gltfio$ResourceLoader(engine, false, false); + const resourceLoader = new Filament.gltfio$ResourceLoader(engine, + config.normalizeSkinningWeights, + config.recomputeBoundingBoxes); const onComplete = () => { resourceLoader.asyncBeginLoad(asset); diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index a9f7a1df94..87914b0d0e 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -465,7 +465,7 @@ export class gltfio$AssetLoader { export class gltfio$FilamentAsset { public loadResources(onDone: () => void|null, onFetched: (s: string) => void|null, - basePath: string|null, asyncInterval: number|null): void; + basePath: string|null, asyncInterval: number|null, options?: object): void; public getEntities(): Entity[]; public getEntitiesByName(name: string): Entity[]; public getEntityByName(name: string): Entity;