#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "material/FileMaterialProvider.hpp" #include "material/UnlitMaterialProvider.hpp" #include "material/unlit.h" #include "material/gizmo.h" #include "StreamBufferAdapter.hpp" #include "Log.hpp" #include "scene/SceneManager.hpp" #include "scene/CustomGeometry.hpp" #include "scene/GeometrySceneAsset.hpp" #include "scene/GltfSceneAsset.hpp" #include "scene/Gizmo.hpp" #include "scene/SceneAsset.hpp" #include "scene/GeometrySceneAssetBuilder.hpp" #include "TextureProjection.hpp" #include "resources/translation_gizmo_glb.h" #include "resources/rotation_gizmo_glb.h" extern "C" { #include "material/image.h" #include "material/unlit_fixed_size.h" } namespace thermion { using namespace std::chrono; using namespace image; using namespace utils; using namespace filament; using namespace filament::gltfio; using std::unique_ptr; SceneManager::SceneManager(const ResourceLoaderWrapperImpl *const resourceLoaderWrapper, Engine *engine, Scene *scene, const char *uberArchivePath, Camera *mainCamera) : _resourceLoaderWrapper(resourceLoaderWrapper), _engine(engine), _scene(scene), _mainCamera(mainCamera) { _stbDecoder = createStbProvider(_engine); _ktxDecoder = createKtx2Provider(_engine); _gltfResourceLoader = new ResourceLoader({.engine = _engine, .normalizeSkinningWeights = true}); if (uberArchivePath) { auto uberdata = resourceLoaderWrapper->load(uberArchivePath); if (!uberdata.data) { Log("Failed to load ubershader material. This is fatal."); } _ubershaderProvider = gltfio::createUbershaderProvider(_engine, uberdata.data, uberdata.size); resourceLoaderWrapper->free(uberdata); } else { _ubershaderProvider = gltfio::createUbershaderProvider( _engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE); } _unlitMaterialProvider = new UnlitMaterialProvider(_engine, UNLIT_PACKAGE, UNLIT_UNLIT_SIZE); utils::EntityManager &em = utils::EntityManager::get(); _ncm = new NameComponentManager(em); _assetLoader = AssetLoader::create({_engine, _ubershaderProvider, _ncm, &em}); _gltfResourceLoader->addTextureProvider("image/ktx2", _ktxDecoder); _gltfResourceLoader->addTextureProvider("image/png", _stbDecoder); _gltfResourceLoader->addTextureProvider("image/jpeg", _stbDecoder); auto &tm = _engine->getTransformManager(); _collisionComponentManager = std::make_unique(tm); _animationManager = std::make_unique(_engine, _scene); _unlitFixedSizeMaterial = Material::Builder() .package(UNLIT_FIXED_SIZE_UNLIT_FIXED_SIZE_DATA, UNLIT_FIXED_SIZE_UNLIT_FIXED_SIZE_SIZE) .build(*_engine); _gizmoMaterial = Material::Builder() .package(GIZMO_GIZMO_DATA, GIZMO_GIZMO_SIZE) .build(*_engine); } SceneManager::~SceneManager() { TRACE("Destroying cameras"); for (auto camera : _cameras) { auto entity = camera->getEntity(); _engine->destroyCameraComponent(entity); _engine->getEntityManager().destroy(entity); } TRACE("Cameras destroyed"); destroyAll(); TRACE("Destroyed all assets"); _engine->destroy(_unlitFixedSizeMaterial); _engine->destroy(_gizmoMaterial); TRACE("Destroyed materials"); _cameras.clear(); _grid = nullptr; _gltfResourceLoader->asyncCancelLoad(); _ubershaderProvider->destroyMaterials(); _animationManager = std::nullptr_t(); _collisionComponentManager = std::nullptr_t(); delete _ncm; delete _gltfResourceLoader; delete _stbDecoder; delete _ktxDecoder; delete _ubershaderProvider; TRACE("Destroying asset loader"); AssetLoader::destroy(&_assetLoader); TRACE("Destroyed asset loader"); } SceneAsset *SceneManager::createGrid(Material *material) { if (!_grid) { if (!material) { material = Material::Builder() .package(GRID_PACKAGE, GRID_GRID_SIZE) .build(*_engine); } _grid = std::make_unique(*_engine, material); } return _grid.get(); } bool SceneManager::isGridEntity(utils::Entity entity) { if (!_grid) { TRACE("No grid"); return false; } if (entity == _grid->getEntity()) { TRACE("%d is a grid entity.", entity); return true; } for (int i = 0; i < _grid->getChildEntityCount(); i++) { if (entity == _grid->getChildEntities()[i]) { TRACE("%d is a child entity of grid.", entity); return true; } } return false; } Gizmo *SceneManager::createGizmo(View *view, Scene *scene, GizmoType type) { TRACE("Creating gizmo type %d", type); Gizmo *raw; switch (type) { case GizmoType::TRANSLATION: if (!_translationGizmoGlb) { TRACE("Translation gizmo source not found, loading"); _translationGizmoGlb = loadGlbFromBuffer(TRANSLATION_GIZMO_GLB_TRANSLATION_GIZMO_DATA, TRANSLATION_GIZMO_GLB_TRANSLATION_GIZMO_SIZE, 100, true, 4, 0, false, false); } raw = new Gizmo(_translationGizmoGlb, _engine, view, scene, _unlitFixedSizeMaterial); TRACE("Built translation gizmo"); break; case GizmoType::ROTATION: if (!_rotationGizmoGlb) { TRACE("Rotation gizmo source not found, loading"); _rotationGizmoGlb = loadGlbFromBuffer(ROTATION_GIZMO_GLB_ROTATION_GIZMO_DATA, ROTATION_GIZMO_GLB_ROTATION_GIZMO_SIZE, 100, true, 4, 0, false, false); } raw = new Gizmo(_rotationGizmoGlb, _engine, view, scene, _unlitFixedSizeMaterial); TRACE("Built rotation gizmo"); break; } _sceneAssets.push_back(std::unique_ptr(raw)); return raw; } int SceneManager::getInstanceCount(EntityId entityId) { auto entity = utils::Entity::import(entityId); for (auto &asset : _sceneAssets) { if (asset->getEntity() == entity) { return asset->getInstanceCount(); } } return -1; } void SceneManager::getInstances(EntityId entityId, EntityId *out) { auto entity = utils::Entity::import(entityId); for (auto &asset : _sceneAssets) { if (asset->getEntity() == entity) { for (int i = 0; i < asset->getInstanceCount(); i++) { out[i] = Entity::smuggle(asset->getInstanceAt(i)->getEntity()); } return; } } } SceneAsset *SceneManager::loadGltf(const char *uri, const char *relativeResourcePath, int numInstances, bool keepData) { if (numInstances < 1) { return std::nullptr_t(); } ResourceBuffer rbuf = _resourceLoaderWrapper->load(uri); std::vector instances(numInstances); FilamentAsset *asset = _assetLoader->createInstancedAsset((uint8_t *)rbuf.data, rbuf.size, instances.data(), numInstances); if (!asset) { Log("Unable to load glTF asset at %d", uri); return std::nullptr_t(); } const char *const *const resourceUris = asset->getResourceUris(); const size_t resourceUriCount = asset->getResourceUriCount(); std::vector resourceBuffers; for (size_t i = 0; i < resourceUriCount; i++) { std::string uri = std::string(relativeResourcePath) + std::string("/") + std::string(resourceUris[i]); ResourceBuffer buf = _resourceLoaderWrapper->load(uri.c_str()); resourceBuffers.push_back(buf); ResourceLoader::BufferDescriptor b(buf.data, buf.size); _gltfResourceLoader->addResourceData(resourceUris[i], std::move(b)); } #ifdef __EMSCRIPTEN__ if (!_gltfResourceLoader->asyncBeginLoad(asset)) { Log("Unknown error loading glTF asset"); _resourceLoaderWrapper->free(rbuf); for (auto &rb : resourceBuffers) { _resourceLoaderWrapper->free(rb); } return 0; } while (_gltfResourceLoader->asyncGetLoadProgress() < 1.0f) { _gltfResourceLoader->asyncUpdateLoad(); } #else // load resources synchronously if (!_gltfResourceLoader->loadResources(asset)) { Log("Unknown error loading glTF asset"); _resourceLoaderWrapper->free(rbuf); for (auto &rb : resourceBuffers) { _resourceLoaderWrapper->free(rb); } return std::nullptr_t(); } #endif auto sceneAsset = std::make_unique( asset, _assetLoader, _engine, _ncm); auto filamentInstance = asset->getInstance(); size_t entityCount = filamentInstance->getEntityCount(); _scene->addEntities(filamentInstance->getEntities(), entityCount); for (auto &rb : resourceBuffers) { _resourceLoaderWrapper->free(rb); } _resourceLoaderWrapper->free(rbuf); auto lights = asset->getLightEntities(); _scene->addEntities(lights, asset->getLightEntityCount()); sceneAsset->createInstance(); auto entityId = Entity::smuggle(sceneAsset->getEntity()); auto *raw = sceneAsset.get(); _sceneAssets.push_back(std::move(sceneAsset)); Log("Loaded glTF asset from uri: %s", uri); return raw; } void SceneManager::setVisibilityLayer(EntityId entityId, int layer) { utils::Entity entity = utils::Entity::import(entityId); for (auto &asset : _sceneAssets) { if (asset->getEntity() == entity) { asset->setLayer(_engine->getRenderableManager(), layer); } } } SceneAsset *SceneManager::loadGlbFromBuffer(const uint8_t *data, size_t length, int numInstances, bool keepData, int priority, int layer, bool loadResourcesAsync, bool addToScene) { auto &rm = _engine->getRenderableManager(); std::vector instances(numInstances); FilamentAsset *asset = _assetLoader->createInstancedAsset((const uint8_t *)data, length, instances.data(), numInstances); Log("Created glTF asset with %d instances.", numInstances); if (!asset) { Log("Unknown error loading GLB asset."); return std::nullptr_t(); } #ifdef __EMSCRIPTEN__ if (!_gltfResourceLoader->asyncBeginLoad(asset)) { Log("Unknown error loading glb asset"); return 0; } while (_gltfResourceLoader->asyncGetLoadProgress() < 1.0f) { _gltfResourceLoader->asyncUpdateLoad(); } #else if (loadResourcesAsync) { if (!_gltfResourceLoader->asyncBeginLoad(asset)) { Log("Unknown error loading glb asset"); return 0; } } else { if (!_gltfResourceLoader->loadResources(asset)) { Log("Unknown error loading glb asset"); return 0; } } #endif auto sceneAsset = std::make_unique( asset, _assetLoader, _engine, _ncm); auto sceneAssetInstance = sceneAsset->createInstance(); if (addToScene) { sceneAssetInstance->addAllEntities(_scene); } sceneAssetInstance->setPriority(_engine->getRenderableManager(), priority); sceneAssetInstance->setLayer(_engine->getRenderableManager(), layer); auto *raw = sceneAsset.get(); _sceneAssets.push_back(std::move(sceneAsset)); return raw; } SceneAsset *SceneManager::createInstance(SceneAsset *asset, MaterialInstance **materialInstances, size_t materialInstanceCount) { std::lock_guard lock(_mutex); auto instance = asset->createInstance(materialInstances, materialInstanceCount); if (instance) { instance->addAllEntities(_scene); } else { Log("Failed to create instance"); } return instance; } SceneAsset *SceneManager::loadGlb(const char *uri, int numInstances, bool keepData) { ResourceBuffer rbuf = _resourceLoaderWrapper->load(uri); auto entity = loadGlbFromBuffer((const uint8_t *)rbuf.data, rbuf.size, numInstances, keepData); _resourceLoaderWrapper->free(rbuf); return entity; } bool SceneManager::removeFromScene(EntityId entityId) { _scene->remove(Entity::import(entityId)); return true; } bool SceneManager::addToScene(EntityId entityId) { _scene->addEntity(Entity::import(entityId)); return true; } void SceneManager::destroyAll() { destroyLights(); destroyAssets(); std::lock_guard lock(_mutex); for (auto *materialInstance : _materialInstances) { _engine->destroy(materialInstance); } _materialInstances.clear(); } void SceneManager::destroy(SceneAsset *asset) { std::lock_guard lock(_mutex); auto entity = asset->getEntity(); _collisionComponentManager->removeComponent(entity); _animationManager->removeAnimationComponent(utils::Entity::smuggle(entity)); for (int i = 0; i < asset->getChildEntityCount(); i++) { auto childEntity = asset->getChildEntities()[i]; _collisionComponentManager->removeComponent(childEntity); _animationManager->removeAnimationComponent(utils::Entity::smuggle(childEntity)); } asset->removeAllEntities(_scene); if (asset->isInstance()) { asset->destroyInstance(asset); } else { auto it = std::remove_if(_sceneAssets.begin(), _sceneAssets.end(), [=](auto &sceneAsset) { return sceneAsset.get() == asset; }); _sceneAssets.erase(it, _sceneAssets.end()); } } utils::Entity SceneManager::addLight( LightManager::Type t, float colour, float intensity, float posX, float posY, float posZ, float dirX, float dirY, float dirZ, float falloffRadius, float spotLightConeInner, float spotLightConeOuter, float sunAngularRadius, float sunHaloSize, float sunHaloFallof, bool shadows) { auto light = EntityManager::get().create(); // LightManager::ShadowOptions shadowOptions; // shadowOptions.stable = true; auto result = LightManager::Builder(t) .color(Color::cct(colour)) .intensity(intensity) .falloff(falloffRadius) .spotLightCone(spotLightConeInner, spotLightConeOuter) .sunAngularRadius(sunAngularRadius) .sunHaloSize(sunHaloSize) .sunHaloFalloff(sunHaloFallof) .position(filament::math::float3(posX, posY, posZ)) .direction(filament::math::float3(dirX, dirY, dirZ)) // .shadowOptions(shadowOptions) .castShadows(shadows) .build(*_engine, light); if (result != LightManager::Builder::Result::Success) { Log("ERROR : failed to create light"); } else { _scene->addEntity(light); _lights.push_back(light); TRACE("Created light"); } return light; } void SceneManager::removeLight(utils::Entity entity) { auto removed = remove(_lights.begin(), _lights.end(), entity); _scene->remove(entity); EntityManager::get().destroy(1, &entity); } void SceneManager::destroyLights() { std::lock_guard lock(_mutex); _scene->removeEntities(_lights.data(), _lights.size()); EntityManager::get().destroy(_lights.size(), _lights.data()); _lights.clear(); } void SceneManager::destroyAssets() { std::lock_guard lock(_mutex); for (auto &asset : _sceneAssets) { asset->removeAllEntities(_scene); for(int i = 0; i < asset->getInstanceCount(); i++) { asset->getInstanceAt(i)->removeAllEntities(_scene); } } _sceneAssets.clear(); } void SceneManager::addCollisionComponent(EntityId entityId, void (*onCollisionCallback)(const EntityId entityId1, const EntityId entityId2), bool affectsTransform) { std::lock_guard lock(_mutex); utils::Entity entity = utils::Entity::import(entityId); for (auto &asset : _sceneAssets) { auto *instance = reinterpret_cast(asset->getInstanceByEntity(entity)); if (instance) { auto collisionInstance = _collisionComponentManager->addComponent(instance->getInstance()->getRoot()); _collisionComponentManager->elementAt<0>(collisionInstance) = instance->getInstance()->getBoundingBox(); _collisionComponentManager->elementAt<1>(collisionInstance) = onCollisionCallback; _collisionComponentManager->elementAt<2>(collisionInstance) = affectsTransform; return; } } } void SceneManager::removeCollisionComponent(EntityId entityId) { std::lock_guard lock(_mutex); utils::Entity entity = utils::Entity::import(entityId); _collisionComponentManager->removeComponent(entity); } void SceneManager::testCollisions(EntityId entityId) { utils::Entity entity = utils::Entity::import(entityId); for (auto &asset : _sceneAssets) { auto *instance = reinterpret_cast(asset->getInstanceByEntity(entity)); if (instance) { const auto &tm = _engine->getTransformManager(); auto transformInstance = tm.getInstance(entity); auto worldTransform = tm.getWorldTransform(transformInstance); auto aabb = instance->getInstance()->getBoundingBox(); aabb = aabb.transform(worldTransform); _collisionComponentManager->collides(entity, aabb); } } } void SceneManager::update() { _animationManager->update(); _updateTransforms(); } void SceneManager::_updateTransforms() { std::lock_guard lock(_mutex); // auto &tm = _engine->getTransformManager(); // tm.openLocalTransformTransaction(); // for (const auto &[entityId, transformUpdate] : _transformUpdates) // { // const auto &pos = _instances.find(entityId); // bool isCollidable = true; // Entity entity; // filament::TransformManager::Instance transformInstance; // filament::math::mat4f transform; // Aabb boundingBox; // if (pos == _instances.end()) // { // isCollidable = false; // entity = Entity::import(entityId); // } // else // { // const auto *instance = pos->second; // entity = instance->getRoot(); // boundingBox = instance->getBoundingBox(); // } // transformInstance = tm.getInstance(entity); // transform = tm.getTransform(transformInstance); // if (isCollidable) // { // auto transformedBB = boundingBox.transform(transform); // auto collisionAxes = _collisionComponentManager->collides(entity, transformedBB); // if (collisionAxes.size() == 1) // { // // auto globalAxis = collisionAxes[0]; // // globalAxis *= norm(relativeTranslation); // // auto newRelativeTranslation = relativeTranslation + globalAxis; // // translation -= relativeTranslation; // // translation += newRelativeTranslation; // // transform = composeMatrix(translation, rotation, scale); // } // else if (collisionAxes.size() > 1) // { // // translation -= relativeTranslation; // // transform = composeMatrix(translation, rotation, scale); // } // } // tm.setTransform(transformInstance, transformUpdate); // } // tm.commitLocalTransformTransaction(); // _transformUpdates.clear(); } void SceneManager::queueRelativePositionUpdateFromViewportVector(View *view, EntityId entityId, float viewportCoordX, float viewportCoordY) { // Get the camera and viewport const auto &camera = view->getCamera(); const auto &vp = view->getViewport(); // Convert viewport coordinates to NDC space float ndcX = (2.0f * viewportCoordX) / vp.width - 1.0f; float ndcY = 1.0f - (2.0f * viewportCoordY) / vp.height; // Get the current position of the entity auto &tm = _engine->getTransformManager(); auto entity = Entity::import(entityId); auto transformInstance = tm.getInstance(entity); auto currentTransform = tm.getTransform(transformInstance); // get entity model origin in camera space auto entityPositionInCameraSpace = camera.getViewMatrix() * currentTransform * filament::math::float4{0.0f, 0.0f, 0.0f, 1.0f}; // get entity model origin in clip space auto entityPositionInClipSpace = camera.getProjectionMatrix() * entityPositionInCameraSpace; auto entityPositionInNdcSpace = entityPositionInClipSpace / entityPositionInClipSpace.w; // Viewport coords in NDC space (use entity position in camera space Z to project onto near plane) math::float4 ndcNearPlanePos = {ndcX, ndcY, -1.0f, 1.0f}; math::float4 ndcFarPlanePos = {ndcX, ndcY, 0.99f, 1.0f}; math::float4 ndcEntityPlanePos = {ndcX, ndcY, entityPositionInNdcSpace.z, 1.0f}; // Get viewport coords in clip space math::float4 nearPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcNearPlanePos; auto nearPlaneInCameraSpace = nearPlaneInClipSpace / nearPlaneInClipSpace.w; math::float4 farPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcFarPlanePos; auto farPlaneInCameraSpace = farPlaneInClipSpace / farPlaneInClipSpace.w; math::float4 entityPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcEntityPlanePos; auto entityPlaneInCameraSpace = entityPlaneInClipSpace / entityPlaneInClipSpace.w; auto entityPlaneInWorldSpace = camera.getModelMatrix() * entityPlaneInCameraSpace; } void SceneManager::queueTransformUpdates(EntityId *entities, math::mat4 *transforms, int numEntities) { std::lock_guard lock(_mutex); for (int i = 0; i < numEntities; i++) { auto entity = entities[i]; const auto &pos = _transformUpdates.find(entity); if (pos == _transformUpdates.end()) { _transformUpdates.emplace(entity, transforms[i]); } auto curr = _transformUpdates[entity]; _transformUpdates[entity] = curr; } } Aabb3 SceneManager::getRenderableBoundingBox(EntityId entityId) { auto &rm = _engine->getRenderableManager(); auto instance = rm.getInstance(Entity::import(entityId)); if (!instance.isValid()) { return Aabb3{}; } auto box = rm.getAxisAlignedBoundingBox(instance); return Aabb3{box.center.x, box.center.y, box.center.z, box.halfExtent.x, box.halfExtent.y, box.halfExtent.z}; } Aabb2 SceneManager::getScreenSpaceBoundingBox(View *view, EntityId entityId) { const auto &camera = view->getCamera(); const auto &viewport = view->getViewport(); auto &tcm = _engine->getTransformManager(); auto &rcm = _engine->getRenderableManager(); // Get the projection and view matrices math::mat4 projMatrix = camera.getProjectionMatrix(); math::mat4 viewMatrix = camera.getViewMatrix(); math::mat4 vpMatrix = projMatrix * viewMatrix; auto entity = Entity::import(entityId); auto renderable = rcm.getInstance(entity); auto worldTransform = tcm.getWorldTransform(tcm.getInstance(entity)); // Get the axis-aligned bounding box in model space Box aabb = rcm.getAxisAlignedBoundingBox(renderable); auto min = aabb.getMin(); auto max = aabb.getMax(); // Transform the 8 corners of the AABB to clip space std::array corners = { worldTransform * math::float4(min.x, min.y, min.z, 1.0f), worldTransform * math::float4(max.x, min.y, min.z, 1.0f), worldTransform * math::float4(min.x, max.y, min.z, 1.0f), worldTransform * math::float4(max.x, max.y, min.z, 1.0f), worldTransform * math::float4(min.x, min.y, max.z, 1.0f), worldTransform * math::float4(max.x, min.y, max.z, 1.0f), worldTransform * math::float4(min.x, max.y, max.z, 1.0f), worldTransform * math::float4(max.x, max.y, max.z, 1.0f)}; // Project corners to clip space and convert to viewport space float minX = std::numeric_limits::max(); float minY = std::numeric_limits::max(); float maxX = std::numeric_limits::lowest(); float maxY = std::numeric_limits::lowest(); for (const auto &corner : corners) { math::float4 clipSpace = vpMatrix * corner; // Check if the point is behind the camera if (clipSpace.w <= 0) { continue; // Skip this point } // Perform perspective division math::float3 ndcSpace = clipSpace.xyz / clipSpace.w; // Clamp NDC coordinates to [-1, 1] range ndcSpace.x = std::max(-1.0f, std::min(1.0f, ndcSpace.x)); ndcSpace.y = std::max(-1.0f, std::min(1.0f, ndcSpace.y)); // Convert NDC to viewport space float viewportX = (ndcSpace.x * 0.5f + 0.5f) * viewport.width; float viewportY = (1.0f - (ndcSpace.y * 0.5f + 0.5f)) * viewport.height; // Flip Y-axis minX = std::min(minX, viewportX); minY = std::min(minY, viewportY); maxX = std::max(maxX, viewportX); maxY = std::max(maxY, viewportY); } return Aabb2{minX, minY, maxX, maxY}; } static filament::gltfio::MaterialKey getDefaultUnlitMaterialConfig(int numUvs) { filament::gltfio::MaterialKey config; memset(&config, 0, sizeof(config)); config.unlit = false; config.doubleSided = false; config.useSpecularGlossiness = false; config.alphaMode = filament::gltfio::AlphaMode::OPAQUE; config.hasBaseColorTexture = numUvs > 0; config.baseColorUV = 0; config.hasVertexColors = false; return config; } SceneAsset *SceneManager::createGeometry( float *vertices, uint32_t numVertices, float *normals, uint32_t numNormals, float *uvs, uint32_t numUvs, uint16_t *indices, uint32_t numIndices, filament::RenderableManager::PrimitiveType primitiveType, filament::MaterialInstance **materialInstances, size_t materialInstanceCount, bool keepData) { utils::Entity entity; auto builder = GeometrySceneAssetBuilder(_engine) .vertices(vertices, numVertices) .indices(indices, numIndices) .primitiveType(primitiveType); if (normals) { builder.normals(normals, numNormals); } if (uvs) { builder.uvs(uvs, numUvs); } builder.materials(materialInstances, materialInstanceCount); auto sceneAsset = builder.build(); if (!sceneAsset) { Log("Failed to create geometry"); return std::nullptr_t(); } sceneAsset->addAllEntities(_scene); auto *raw = sceneAsset.get(); _sceneAssets.push_back(std::move(sceneAsset)); return raw; } void SceneManager::destroy(filament::MaterialInstance *instance) { auto it = std::find(_materialInstances.begin(), _materialInstances.end(), instance); if (it != _materialInstances.end()) { _materialInstances.erase(it); } _engine->destroy(instance); } MaterialInstance *SceneManager::createUnlitFixedSizeMaterialInstance() { auto instance = _unlitFixedSizeMaterial->createInstance(); instance->setParameter("scale", 1.0f); return instance; } MaterialInstance *SceneManager::createUnlitMaterialInstance() { UvMap uvmap; auto instance = _unlitMaterialProvider->createMaterialInstance(nullptr, &uvmap); instance->setParameter("baseColorFactor", filament::math::float4{1.0f, 1.0f, 1.0f, 1.0f}); instance->setParameter("baseColorIndex", -1); instance->setParameter("uvScale", filament::math::float2{1.0f, 1.0f}); _materialInstances.push_back(instance); return instance; } Camera *SceneManager::createCamera() { auto entity = EntityManager::get().create(); auto camera = _engine->createCamera(entity); _cameras.push_back(camera); return camera; } void SceneManager::destroyCamera(Camera *camera) { auto entity = camera->getEntity(); _engine->destroyCameraComponent(entity); _engine->getEntityManager().destroy(entity); auto it = std::find(_cameras.begin(), _cameras.end(), camera); if (it != _cameras.end()) { _cameras.erase(it); } } size_t SceneManager::getCameraCount() { return _cameras.size() + 1; } Camera *SceneManager::getCameraAt(size_t index) { if (index == 0) { return _mainCamera; } if (index - 1 > _cameras.size() - 1) { return nullptr; } return _cameras[index - 1]; } void SceneManager::transformToUnitCube(EntityId entityId) { auto entity = utils::Entity::import(entityId); for (auto &asset : _sceneAssets) { auto *instance = reinterpret_cast(asset->getInstanceByEntity(entity)); if (instance) { auto &transformManager = _engine->getTransformManager(); const auto &entity = utils::Entity::import(entityId); auto transformInstance = transformManager.getInstance(entity); if (!transformInstance) { return; } auto aabb = instance->getInstance()->getBoundingBox(); auto center = aabb.center(); auto halfExtent = aabb.extent(); auto maxExtent = max(halfExtent) * 2; auto scaleFactor = 2.0f / maxExtent; auto transform = math::mat4f::scaling(scaleFactor) * math::mat4f::translation(-center); transformManager.setTransform(transformManager.getInstance(entity), transform); return; } } } } // namespace thermion