diff --git a/android/gltfio-android/src/main/cpp/FilamentAsset.cpp b/android/gltfio-android/src/main/cpp/FilamentAsset.cpp index f4c2fa20f1..37aa3cfa17 100644 --- a/android/gltfio-android/src/main/cpp/FilamentAsset.cpp +++ b/android/gltfio-android/src/main/cpp/FilamentAsset.cpp @@ -120,10 +120,34 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_gltfio_FilamentAsset_nGetLightEntities(JNIEnv* env, jclass, jlong nativeAsset, jintArray result) { FilamentAsset* asset = (FilamentAsset*) nativeAsset; - jsize available = env->GetArrayLength(result); + const jsize available = env->GetArrayLength(result); + const size_t minCount = std::min(available, (jsize) asset->getLightEntityCount()); + if (minCount == 0) { + return; + } Entity* entities = (Entity*) env->GetIntArrayElements(result, nullptr); - std::copy_n(asset->getLightEntities(), - std::min(available, (jsize) asset->getLightEntityCount()), entities); + std::copy_n(asset->getLightEntities(), minCount, entities); + env->ReleaseIntArrayElements(result, (jint*) entities, 0); +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_gltfio_FilamentAsset_nGetRenderableEntityCount(JNIEnv*, jclass, + jlong nativeAsset) { + FilamentAsset* asset = (FilamentAsset*) nativeAsset; + return asset->getRenderableEntityCount(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_gltfio_FilamentAsset_nGetRenderableEntities(JNIEnv* env, jclass, + jlong nativeAsset, jintArray result) { + FilamentAsset* asset = (FilamentAsset*) nativeAsset; + const jsize available = env->GetArrayLength(result); + const size_t minCount = std::min(available, (jsize) asset->getRenderableEntityCount()); + if (minCount == 0) { + return; + } + Entity* entities = (Entity*) env->GetIntArrayElements(result, nullptr); + std::copy_n(asset->getRenderableEntities(), minCount, entities); env->ReleaseIntArrayElements(result, (jint*) entities, 0); } @@ -131,10 +155,13 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_gltfio_FilamentAsset_nGetCameraEntities(JNIEnv* env, jclass, jlong nativeAsset, jintArray result) { FilamentAsset* asset = (FilamentAsset*) nativeAsset; - jsize available = env->GetArrayLength(result); + const jsize available = env->GetArrayLength(result); + const size_t minCount = std::min(available, (jsize) asset->getCameraEntityCount()); + if (minCount == 0) { + return; + } Entity* entities = (Entity*) env->GetIntArrayElements(result, nullptr); - std::copy_n(asset->getCameraEntities(), - std::min(available, (jsize) asset->getCameraEntityCount()), entities); + std::copy_n(asset->getCameraEntities(), minCount, entities); env->ReleaseIntArrayElements(result, (jint*) entities, 0); } diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentAsset.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentAsset.java index 47c1c6bed8..747db67333 100644 --- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentAsset.java +++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentAsset.java @@ -113,6 +113,15 @@ public class FilamentAsset { return result; } + /** + * Gets only the entities that have renderable components. + */ + public @NonNull @Entity int[] getRenderableEntities() { + int[] result = new int[nGetRenderableEntityCount(mNativeObject)]; + nGetRenderableEntities(mNativeObject, result); + return result; + } + /** * Gets only the entities that have camera components. * @@ -321,6 +330,9 @@ public class FilamentAsset { private static native int nGetLightEntityCount(long nativeAsset); private static native void nGetLightEntities(long nativeAsset, int[] result); + private static native int nGetRenderableEntityCount(long nativeAsset); + private static native void nGetRenderableEntities(long nativeAsset, int[] result); + private static native int nGetCameraEntityCount(long nativeAsset); private static native void nGetCameraEntities(long nativeAsset, int[] result); diff --git a/libs/gltfio/include/gltfio/FilamentAsset.h b/libs/gltfio/include/gltfio/FilamentAsset.h index 6078aa6cff..b6f4c2ae7c 100644 --- a/libs/gltfio/include/gltfio/FilamentAsset.h +++ b/libs/gltfio/include/gltfio/FilamentAsset.h @@ -77,6 +77,16 @@ public: */ size_t getLightEntityCount() const noexcept; + /** + * Gets the list of entities in the asset that have renderable components. + */ + const utils::Entity* getRenderableEntities() const noexcept; + + /** + * Gets the number of entities returned by getRenderableEntities(). + */ + size_t getRenderableEntityCount() const noexcept; + /** * Gets the list of entities in the scene representing cameras. All of these have a \c Camera * component. diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 327acbb47f..6beb6b09cb 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -299,6 +299,8 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances) } } + mResult->mRenderableCount = 0; + if (numInstances == 0) { // For each scene root, recursively create all entities. for (cgltf_size i = 0, len = scene->nodes_count; i < len; ++i) { @@ -317,6 +319,14 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances) } } + // Sort the entities so that the renderable ones come first. This allows us to expose + // a "renderables only" pointer without storing a separate list. + const auto& rm = mEngine->getRenderableManager(); + std::sort(mResult->mEntities.begin(), mResult->mEntities.end(), + [&rm](const Entity& a, const Entity& b) { + return rm.hasComponent(a); + }); + // Find every unique resource URI and store a pointer to any of the cgltf-owned cstrings // that match the URI. These strings get freed during releaseSourceData(). tsl::robin_map resourceUris; @@ -564,6 +574,8 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node } mRenderableManager.setMorphWeights(renderable, weights.data(), size); } + + ++mResult->mRenderableCount; } void FAssetLoader::createMaterialVariants(const cgltf_data* srcAsset, const cgltf_mesh* mesh, diff --git a/libs/gltfio/src/FFilamentAsset.h b/libs/gltfio/src/FFilamentAsset.h index 334cf4d0df..0c78e5688a 100644 --- a/libs/gltfio/src/FFilamentAsset.h +++ b/libs/gltfio/src/FFilamentAsset.h @@ -139,6 +139,14 @@ struct FFilamentAsset : public FilamentAsset { return mLightEntities.size(); } + const utils::Entity* getRenderableEntities() const noexcept { + return (mRenderableCount == 0) ? nullptr : mEntities.data(); + } + + size_t getRenderableEntityCount() const noexcept { + return mRenderableCount; + } + const utils::Entity* getCameraEntities() const noexcept { return mCameraEntities.empty() ? nullptr : mCameraEntities.data(); } @@ -250,9 +258,10 @@ struct FFilamentAsset : public FilamentAsset { filament::Engine* mEngine; utils::NameComponentManager* mNameManager; utils::EntityManager* mEntityManager; - std::vector mEntities; + std::vector mEntities; // sorted such that renderables come first std::vector mLightEntities; std::vector mCameraEntities; + size_t mRenderableCount = 0; std::vector mMaterialInstances; std::vector mVertexBuffers; std::vector mBufferObjects; diff --git a/libs/gltfio/src/FilamentAsset.cpp b/libs/gltfio/src/FilamentAsset.cpp index d61892ef2a..4c97aa77c1 100644 --- a/libs/gltfio/src/FilamentAsset.cpp +++ b/libs/gltfio/src/FilamentAsset.cpp @@ -280,6 +280,14 @@ size_t FilamentAsset::getLightEntityCount() const noexcept { return upcast(this)->getLightEntityCount(); } +const Entity* FilamentAsset::getRenderableEntities() const noexcept { + return upcast(this)->getRenderableEntities(); +} + +size_t FilamentAsset::getRenderableEntityCount() const noexcept { + return upcast(this)->getRenderableEntityCount(); +} + const utils::Entity* FilamentAsset::getCameraEntities() const noexcept { return upcast(this)->getCameraEntities(); } diff --git a/libs/viewer/include/viewer/ViewerGui.h b/libs/viewer/include/viewer/ViewerGui.h index 3e3b84364d..8ad7a64c8c 100644 --- a/libs/viewer/include/viewer/ViewerGui.h +++ b/libs/viewer/include/viewer/ViewerGui.h @@ -76,15 +76,25 @@ public: ~ViewerGui(); /** - * Adds the asset's ready-to-render entities into the scene. + * Sets the viewer's current asset. * * The viewer does not claim ownership over the asset or its entities. Clients should use * AssetLoader and ResourceLoader to load an asset before passing it in. * + * This method does not add renderables to the scene; see populateScene(). + * * @param asset The asset to view. * @param instanceToAnimate Optional instance from which to get the animator. */ - void populateScene(FilamentAsset* asset, FilamentInstance* instanceToAnimate = nullptr); + void setAsset(FilamentAsset* asset, FilamentInstance* instanceToAnimate = nullptr); + + /** + * Adds the asset's ready-to-render entities into the scene. + * + * This is used for asychronous loading. It can be called once per frame to gradually add + * entities into the scene as their textures are loaded. + */ + void populateScene(); /** * Removes the current asset from the viewer. diff --git a/libs/viewer/src/ViewerGui.cpp b/libs/viewer/src/ViewerGui.cpp index 9bc9ac4864..bf2bf618aa 100644 --- a/libs/viewer/src/ViewerGui.cpp +++ b/libs/viewer/src/ViewerGui.cpp @@ -394,7 +394,7 @@ ViewerGui::~ViewerGui() { delete mImGuiHelper; } -void ViewerGui::populateScene(FilamentAsset* asset, FilamentInstance* instanceToAnimate) { +void ViewerGui::setAsset(FilamentAsset* asset, FilamentInstance* instanceToAnimate) { if (mAsset != asset) { removeAsset(); mAsset = asset; @@ -407,7 +407,9 @@ void ViewerGui::populateScene(FilamentAsset* asset, FilamentInstance* instanceT updateRootTransform(); mScene->addEntities(asset->getLightEntities(), asset->getLightEntityCount()); } +} +void ViewerGui::populateScene() { auto& tcm = mEngine->getRenderableManager(); static constexpr int kNumAvailable = 128; @@ -847,8 +849,9 @@ void ViewerGui::updateUserInterface() { if (ImGui::CollapsingHeader("Scene")) { ImGui::Indent(); - ImGui::Checkbox("Scale to unit cube", &mSettings.viewer.autoScaleEnabled); - updateRootTransform(); + if (ImGui::Checkbox("Scale to unit cube", &mSettings.viewer.autoScaleEnabled)) { + updateRootTransform(); + } ImGui::Checkbox("Show skybox", &mSettings.viewer.skyboxEnabled); ImGui::ColorEdit3("Background color", &mSettings.viewer.backgroundColor.r); diff --git a/samples/gltf_instances.cpp b/samples/gltf_instances.cpp index 126cc1dd98..c023425e12 100644 --- a/samples/gltf_instances.cpp +++ b/samples/gltf_instances.cpp @@ -254,6 +254,10 @@ int main(int argc, char** argv) { loadAsset(filename); } + FilamentInstance* const instance = app.instanceToAnimate > -1 ? + app.instances[app.instanceToAnimate] : nullptr; + app.viewer->setAsset(app.asset, instance); + arrangeIntoCircle(); loadResources(filename); }; @@ -271,12 +275,8 @@ int main(int argc, char** argv) { auto animate = [&app, arrangeIntoCircle](Engine* engine, View* view, double now) { app.resourceLoader->asyncUpdateLoad(); - FilamentInstance* instance = nullptr; - if (app.instanceToAnimate > -1) { - instance = app.instances[app.instanceToAnimate]; - } app.viewer->updateRootTransform(); - app.viewer->populateScene(app.asset, instance); + app.viewer->populateScene(); app.viewer->applyAnimation(now); // Add a new instance every second until reaching 100 instances. diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index bd3fb9371a..6f71aee402 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -505,6 +505,7 @@ int main(int argc, char** argv) { } else { loadAsset(filename); } + app.viewer->setAsset(app.asset); loadResources(filename); @@ -665,8 +666,8 @@ int main(int argc, char** argv) { // Optionally fit the model into a unit cube at the origin. app.viewer->updateRootTransform(); - // Add renderables to the scene as they become ready. - app.viewer->populateScene(app.asset); + // Gradually add renderables to the scene as their textures become ready. + app.viewer->populateScene(); app.viewer->applyAnimation(now); }; diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index d09b9d285f..346fa02aff 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -702,6 +702,10 @@ Filament.loadClassExtensions = function() { return Filament.vectorToArray(this._getLightEntities()); }; + Filament.gltfio$FilamentAsset.prototype.getRenderableEntities = function() { + return Filament.vectorToArray(this._getRenderableEntities()); + }; + Filament.gltfio$FilamentAsset.prototype.getCameraEntities = function() { return Filament.vectorToArray(this._getCameraEntities()); }; diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index 9f003bbbfb..b8a992eddc 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -629,6 +629,7 @@ export class gltfio$FilamentAsset { public getEntityByName(name: string): Entity; public getEntitiesByPrefix(name: string): Entity[]; public getLightEntities(): Entity[]; + public getRenderableEntities(): Entity[]; public getCameraEntities(): Entity[]; public getRoot(): Entity; public popRenderable(): Entity; diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 52c8cb1e85..997b2662a7 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1826,6 +1826,11 @@ class_("gltfio$FilamentAsset") return EntityVector(ptr, ptr + self->getLightEntityCount()); }), allow_raw_pointers()) + .function("_getRenderableEntities", EMBIND_LAMBDA(EntityVector, (FilamentAsset* self), { + const utils::Entity* ptr = self->getRenderableEntities(); + return EntityVector(ptr, ptr + self->getRenderableEntityCount()); + }), allow_raw_pointers()) + .function("_getCameraEntities", EMBIND_LAMBDA(EntityVector, (FilamentAsset* self), { const utils::Entity* ptr = self->getCameraEntities(); return EntityVector(ptr, ptr + self->getCameraEntityCount()); diff --git a/web/samples/helmet.html b/web/samples/helmet.html index ed0d039eab..12e01f2a09 100644 --- a/web/samples/helmet.html +++ b/web/samples/helmet.html @@ -87,15 +87,6 @@ class App { const onDone = () => { this.allowRefresh = true; - // Enable shadows on every renderable. - const entities = asset.getEntities(); - const rm = engine.getRenderableManager(); - for (const entity of entities) { - const instance = rm.getInstance(entity); - rm.setCastShadows(instance, true); - instance.delete(); - } - // Hide the HUD. messages.remove(); }; @@ -149,7 +140,7 @@ class App { tcm.setTransform(inst, this.trackball.getMatrix()); inst.delete(); - // Add renderable entities to the scene as they become ready. + // Gradually add renderables to the scene as their textures become ready. let entity; const popRenderable = () => { entity = this.asset.popRenderable();