/* * Copyright (c) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "scene_holder.h" #include <algorithm> #include <chrono> #include <inttypes.h> #include <scene_plugin/api/material.h> #include <scene_plugin/interface/intf_scene.h> #include <3d/ecs/components/environment_component.h> #include <3d/ecs/components/local_matrix_component.h> #include <3d/ecs/components/post_process_component.h> #include <3d/ecs/components/render_configuration_component.h> #include <3d/ecs/components/render_handle_component.h> #include <3d/ecs/components/render_mesh_batch_component.h> #include <3d/ecs/components/transform_component.h> #include <3d/ecs/components/uri_component.h> #include <3d/ecs/components/world_matrix_component.h> #include <3d/ecs/systems/intf_render_preprocessor_system.h> #include <3d/implementation_uids.h> #include <3d/render/default_material_constants.h> #include <3d/util/intf_mesh_builder.h> #include <3d/util/intf_scene_util.h> #include <base/util/uid_util.h> #include <core/ecs/intf_system_graph_loader.h> #include <core/intf_engine.h> #include <core/plugin/intf_class_factory.h> #include <render/device/intf_gpu_resource_manager.h> #include <render/device/intf_shader_manager.h> #include <render/intf_render_context.h> #include <render/intf_renderer.h> #include <render/util/intf_render_util.h> #include <meta/api/make_callback.h> #include <meta/base/shared_ptr.h> #include <meta/interface/intf_task_queue.h> #include <meta/interface/intf_task_queue_registry.h> #include "asset_loader.h" #include "asset_manager.h" #include "ecs_util.h" #include "entity_collection.h" #include "intf_node_private.h" #include "task_utils.h" // Initialize ecs on scene holder initialization or do it lazily when there's a scene to load using namespace BASE_NS; using namespace CORE_NS; using namespace CORE3D_NS; using namespace RENDER_NS; using SCENE_NS::MakeTask; namespace { bool TickFrame(IEcs& ecs, uint64_t totalTime, uint64_t deltaTime) { // run garbage collection before updating the systems to ensure only valid entities/ components are available. ecs.ProcessEvents(); const bool needRender = ecs.Update(totalTime, deltaTime); // do gc also after the systems have been updated to ensure any deletes done by systems are effective // and client doesn't see stale entities. ecs.ProcessEvents(); return needRender; } constexpr GpuImageDesc GetColorImageDesc(const uint32_t width, const uint32_t height) { GpuImageDesc resolveDesc; resolveDesc.width = width; resolveDesc.height = height; resolveDesc.depth = 1; resolveDesc.format = BASE_NS::Format::BASE_FORMAT_R8G8B8A8_SRGB; resolveDesc.memoryPropertyFlags = MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; resolveDesc.usageFlags = ImageUsageFlagBits::CORE_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | ImageUsageFlagBits::CORE_IMAGE_USAGE_SAMPLED_BIT; resolveDesc.imageType = ImageType::CORE_IMAGE_TYPE_2D; resolveDesc.imageTiling = ImageTiling::CORE_IMAGE_TILING_OPTIMAL; resolveDesc.imageViewType = ImageViewType::CORE_IMAGE_VIEW_TYPE_2D; resolveDesc.engineCreationFlags = EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_DYNAMIC_BARRIERS; return resolveDesc; } constexpr GpuImageDesc GetDepthImageDesc(const uint32_t width, const uint32_t height) { GpuImageDesc resolveDesc; resolveDesc.width = width; resolveDesc.height = height; resolveDesc.depth = 1; resolveDesc.format = BASE_NS::Format::BASE_FORMAT_D16_UNORM; resolveDesc.memoryPropertyFlags = MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; resolveDesc.usageFlags = ImageUsageFlagBits::CORE_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | ImageUsageFlagBits::CORE_IMAGE_USAGE_SAMPLED_BIT; resolveDesc.imageType = ImageType::CORE_IMAGE_TYPE_2D; resolveDesc.imageTiling = ImageTiling::CORE_IMAGE_TILING_OPTIMAL; resolveDesc.imageViewType = ImageViewType::CORE_IMAGE_VIEW_TYPE_2D; resolveDesc.engineCreationFlags = EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_DYNAMIC_BARRIERS; return resolveDesc; } static BASE_NS::string ROOTNODE_NAME = "rootNode_"; static BASE_NS::string ROOTNODE_PATH = "/" + ROOTNODE_NAME + "/"; BASE_NS::string_view RemoveRootNodeFromPath(const BASE_NS::string_view& path) { if (path.starts_with(ROOTNODE_PATH)) { return path.substr(ROOTNODE_PATH.length()); } if (path.starts_with("/")) { return path.substr(1); } return path; } } // namespace SceneHolder::SceneHolder(META_NS::InstanceId uid, META_NS::IObjectRegistry& registry, const BASE_NS::shared_ptr<RENDER_NS::IRenderContext>& gc, META_NS::ITaskQueue::Ptr appQueue, META_NS::ITaskQueue::Ptr engineQueue) : instanceId_(uid), renderContext_(gc), appTaskQueue_(appQueue), engineTaskQueue_(engineQueue), objectRegistry_(registry) {} SceneHolder::~SceneHolder() { ResetScene(false); } CORE_NS::IEcs::Ptr SceneHolder::GetEcs() { // once we offer Ecs to anyone, we cannot know it is intact scenePrepared_ = false; return ecs_; } void SceneHolder::Initialize(WeakPtr me) { me_ = me; InitializeScene(); } void SceneHolder::Uninitialize() { // Remove callbacks, these are no longer needed. SetInitializeCallback({}, me_); SetSceneLoadedCallback({}, me_); // Move processing to engine thread. UninitializeScene(); } void SceneHolder::SetRenderSize(uint32_t width, uint32_t height, uint64_t cameraHandle) { // Render size changed, schedule update engine thread. UpdateViewportSize(width, height, cameraHandle); } void SceneHolder::SetCameraTarget( const SCENE_NS::ICamera::Ptr& camera, BASE_NS::Math::UVec2 size, RENDER_NS::RenderHandleReference ref) { auto cameraEnt = interface_cast<SCENE_NS::IEcsObject>(camera)->GetEntity(); CameraData::Ptr cd; for (auto c : cameras_) { if (c->entity == cameraEnt) { cd = c; break; } } if (!cd) { // invalid. return; } cd->width = size.x; cd->height = size.y; if (!ref) { cd->colorImage = {}; cd->ownsColorImage = true; RecreateOutputTexture(cd); } else { cd->colorImage = ref; cd->ownsColorImage = false; } cd->updateTargets = true; } void SceneHolder::RequestReload() { LoadScene(); } void SceneHolder::Load(const BASE_NS::string& uri) { // Scene systems graph uri changed, schedule update to engine thread. if (sceneUri_ != uri) { sceneUri_ = uri; RequestReload(); } } void SceneHolder::SetSystemGraphUri(const string& uri) { // Scene systems graph uri changed, schedule update to engine thread. if (uri != sceneSystemGraphUri_) { sceneSystemGraphUri_ = uri; RequestReload(); } } void SceneHolder::SetInitializeCallback(ISceneInitialized::Ptr callback, SceneHolder::WeakPtr weakMe) { // Schedule update of callback to engine thread. sceneInitializedCallback_ = callback; } void SceneHolder::SetSceneLoadedCallback(ISceneLoaded::Ptr callback, SceneHolder::WeakPtr weakMe) { // Schedule update of callback to engine thread. sceneLoadedCallback_ = callback; } void SceneHolder::SetUninitializeCallback(ISceneUninitialized::Ptr callback, SceneHolder::WeakPtr weakMe) { // Schedule update of callback to engine thread. sceneUninitializedCallback_ = callback; } void SceneHolder::ActivateCamera(const Entity& cameraEntity) const { if (!(cameraComponentManager_ && EntityUtil::IsValid(cameraEntity))) { CORE_LOG_W("SceneHolder::ActivateCamera: Can not be activated. cameraEntity: %" PRIx64 ", cameraComponentManager_: %d, IsValid(cameraEntity): %d", cameraEntity.id, static_cast<bool>(cameraComponentManager_), EntityUtil::IsValid(cameraEntity)); return; } CameraComponent cameraComponent = cameraComponentManager_->Get(cameraEntity); cameraComponent.sceneFlags |= CameraComponent::ACTIVE_RENDER_BIT; cameraComponentManager_->Set(cameraEntity, cameraComponent); } void SceneHolder::DeactivateCamera(const Entity& cameraEntity) const { if (!(cameraComponentManager_ && EntityUtil::IsValid(cameraEntity))) { CORE_LOG_W("SceneHolder::DeactivateCamera: Can not be deactivated. cameraEntity: %" PRIx64 ", cameraComponentManager_: %d, IsValid(cameraEntity): %d", cameraEntity.id, static_cast<bool>(cameraComponentManager_), EntityUtil::IsValid(cameraEntity)); return; } CameraComponent cameraComponent = cameraComponentManager_->Get(cameraEntity); cameraComponent.sceneFlags &= ~(CameraComponent::ACTIVE_RENDER_BIT /* | CameraComponent::MAIN_CAMERA_BIT*/); cameraComponentManager_->Set(cameraEntity, cameraComponent); } bool SceneHolder::IsCameraActive(const Entity& cameraEntity) const { if (!(cameraComponentManager_ && EntityUtil::IsValid(cameraEntity))) { CORE_LOG_W("SceneHolder::DeactivateCamera: Can not be deactivated. cameraEntity: %" PRIx64 ", cameraComponentManager_: %d, IsValid(cameraEntity): %d", cameraEntity.id, static_cast<bool>(cameraComponentManager_), EntityUtil::IsValid(cameraEntity)); return false; } CameraComponent cameraComponent = cameraComponentManager_->Get(cameraEntity); return cameraComponent.sceneFlags & (CameraComponent::ACTIVE_RENDER_BIT); } bool SceneHolder::InitializeScene() { graphicsContext3D_ = CreateInstance<CORE3D_NS::IGraphicsContext>( *renderContext_->GetInterface<IClassFactory>(), UID_GRAPHICS_CONTEXT); graphicsContext3D_->Init(); picking_ = GetInstance<CORE3D_NS::IPicking>(*renderContext_->GetInterface<IClassRegister>(), UID_PICKING); // scene loading was attempted before initialization, retry now if (loadSceneFailed_) { loadSceneFailed_ = false; RequestReload(); } else { // business as usual, set up things according build time settings #ifdef INITIALIZE_ECS_IMMEDIATELY ResetScene(true); scenePrepared_ = true; #else ResetScene(false); #endif } // This is one-time init. return false; } BASE_NS::vector<CORE_NS::Entity> SceneHolder::RenderCameras() { // nobody has asked for updates, yet if (!ecs_ /*|| isReadyForNewFrame_.empty()*/) { return {}; } bool busy = false; BASE_NS::vector<CORE_NS::Entity> activeCameras; for (auto& camera : cameras_) { if (!EntityUtil::IsValid(camera->entity)) continue; if (auto cc = cameraComponentManager_->Read(camera->entity)) { if (cc->sceneFlags & CameraComponent::SceneFlagBits::ACTIVE_RENDER_BIT) { if (camera->updateTargets) { UpdateCameraRenderTarget(camera); camera->updateTargets = false; } activeCameras.push_back(camera->entity); } } } isRunningFrame_ = true; // Tick frame. One camera could have static view while other is moving IEcs* ecs = ecs_.get(); ecs->ProcessEvents(); using namespace std::chrono; const auto currentTime = static_cast<uint64_t>(duration_cast<microseconds>(high_resolution_clock::now().time_since_epoch()).count()); if (firstTime_ == ~0u) { previousFrameTime_ = firstTime_ = currentTime; } auto deltaTime = currentTime - previousFrameTime_; constexpr auto limitHz = duration_cast<microseconds>(duration<float, std::ratio<1, 15u>>(1)).count(); if (deltaTime > limitHz) { deltaTime = limitHz; // clampthe time step to no longer than 15hz. } previousFrameTime_ = currentTime; const uint64_t totalTime = currentTime - firstTime_; const bool needsRender = ecs->Update(totalTime, deltaTime); ecs->ProcessEvents(); auto renderHandles = graphicsContext3D_->GetRenderNodeGraphs(*ecs_); if ((needsRender) && (!renderHandles.empty())) { // The scene needs to be rendered. RENDER_NS::IRenderer& renderer = renderContext_->GetRenderer(); renderer.RenderDeferred(renderHandles); } else { // did not render anything. bool nr = needsRender; activeCameras.clear(); } isRunningFrame_ = false; if (requiresEventProcessing_) { ProcessEvents(); } // Schedule new update to scene. return activeCameras; } bool SceneHolder::UninitializeScene() { ResetScene(); if (ecs_) { ecs_->Uninitialize(); ecs_.reset(); } if (sceneUninitializedCallback_) { QueueApplicationTask(MakeTask( [](auto callback) { callback->Invoke(); return false; }, sceneUninitializedCallback_), false); } sceneInitializedCallback_.reset(); sceneUpdatedCallback_.reset(); sceneUninitializedCallback_.reset(); return false; } void SceneHolder::ProcessEvents() { if (isRunningFrame_) { requiresEventProcessing_ = true; return; } if (ecsListener_ && ecsListener_->IsCallbackActive()) { requiresEventProcessing_ = true; return; } if (ecs_) { ecs_->ProcessEvents(); requiresEventProcessing_ = false; } } bool SceneHolder::CreateDefaultEcs() { if (!renderContext_) { CORE_LOG_W("%s: Graphics contexts is not available, can not create ecs", __func__); return false; } auto& engine = renderContext_->GetEngine(); ecs_ = engine.CreateEcs(); ecs_->SetRenderMode(renderMode_); if (!ecsListener_) { ecsListener_ = BASE_NS::shared_ptr<SCENE_NS::EcsListener>(new SCENE_NS::EcsListener()); } ecsListener_->SetEcs(ecs_); // Get systemGraphLoader factory from global plugin registry. auto* factory = GetInstance<ISystemGraphLoaderFactory>(UID_SYSTEM_GRAPH_LOADER); auto systemGraphLoader = factory->Create(engine.GetFileManager()); // First try to load project-specific system graph. auto result = sceneSystemGraphUri_.empty() ? false : systemGraphLoader->Load(sceneSystemGraphUri_, *ecs_).success; if (!result) { // Fall back to default graph. systemGraphLoader->Load("rofs3D://systemGraph.json", *ecs_); } auto renderPreprocessorSystem = GetSystem<IRenderPreprocessorSystem>(*ecs_); string dataStorePrefix = "renderDataStore:" + GetUniqueName(); auto& renderDataStoreManager = renderContext_->GetRenderDataStoreManager(); auto sceneDataStore = dataStorePrefix + "RenderDataStoreDefaultScene"; auto cameraDataStore = dataStorePrefix + "RenderDataStoreDefaultCamera"; auto lightDataStore = dataStorePrefix + "RenderDataStoreDefaultLight"; auto materialDataStore = dataStorePrefix + "RenderDataStoreDefaultMaterial"; auto morphDataStore = dataStorePrefix + "RenderDataStoreMorph"; if (renderPreprocessorSystem) { IRenderPreprocessorSystem::Properties props; props.dataStorePrefix = dataStorePrefix; props.dataStoreScene = sceneDataStore; props.dataStoreCamera = cameraDataStore; props.dataStoreLight = lightDataStore; props.dataStoreMaterial = materialDataStore; props.dataStoreMorph = morphDataStore; *ScopedHandle<IRenderPreprocessorSystem::Properties>(renderPreprocessorSystem->GetProperties()) = props; } if (auto callback = ecsInitializationCallback_.lock()) { auto result = callback->Invoke(ecs_); if (!result) { CORE_LOG_W("Ecs initialization failed in the callback, expect trouble"); } } ecs_->Initialize(); animationComponentManager_ = CORE_NS::GetManager<CORE3D_NS::IAnimationComponentManager>(*ecs_); cameraComponentManager_ = GetManager<CORE3D_NS::ICameraComponentManager>(*ecs_); envComponentManager_ = GetManager<CORE3D_NS::IEnvironmentComponentManager>(*ecs_); layerComponentManager_ = GetManager<CORE3D_NS::ILayerComponentManager>(*ecs_); lightComponentManager_ = GetManager<CORE3D_NS::ILightComponentManager>(*ecs_); materialComponentManager_ = GetManager<CORE3D_NS::IMaterialComponentManager>(*ecs_); meshComponentManager_ = GetManager<CORE3D_NS::IMeshComponentManager>(*ecs_); nameComponentManager_ = GetManager<CORE3D_NS::INameComponentManager>(*ecs_); nodeComponentManager_ = GetManager<CORE3D_NS::INodeComponentManager>(*ecs_); renderMeshComponentManager_ = GetManager<CORE3D_NS::IRenderMeshComponentManager>(*ecs_); rhComponentManager_ = GetManager<CORE3D_NS::IRenderHandleComponentManager>(*ecs_); transformComponentManager_ = GetManager<CORE3D_NS::ITransformComponentManager>(*ecs_); uriComponentManager_ = GetManager<CORE3D_NS::IUriComponentManager>(*ecs_); if (animationComponentManager_) { animationQuery_.reset(new CORE_NS::ComponentQuery()); animationQuery_->SetEcsListenersEnabled(true); const ComponentQuery::Operation operations[] = { { *nodeComponentManager_, ComponentQuery::Operation::OPTIONAL }, { *nameComponentManager_, ComponentQuery::Operation::OPTIONAL } }; animationQuery_->SetupQuery(*animationComponentManager_, operations); } if (meshComponentManager_) { meshQuery_.reset(new CORE_NS::ComponentQuery()); meshQuery_->SetEcsListenersEnabled(true); const ComponentQuery::Operation operations[] = { { *nodeComponentManager_, ComponentQuery::Operation::OPTIONAL }, { *nameComponentManager_, ComponentQuery::Operation::OPTIONAL } }; meshQuery_->SetupQuery(*meshComponentManager_, operations); } if (materialComponentManager_) { materialQuery_.reset(new CORE_NS::ComponentQuery()); materialQuery_->SetEcsListenersEnabled(true); const ComponentQuery::Operation operations[] = { { *nodeComponentManager_, ComponentQuery::Operation::OPTIONAL }, { *nameComponentManager_, ComponentQuery::Operation::OPTIONAL }, { *uriComponentManager_, ComponentQuery::Operation::OPTIONAL } }; materialQuery_->SetupQuery(*materialComponentManager_, operations); } nodeSystem_ = static_cast<CORE3D_NS::INodeSystem*>(ecs_->GetSystem(CORE3D_NS::INodeSystem::UID)); return true; } CORE_NS::Entity SceneHolder::CreateCamera(const BASE_NS::string& name, uint32_t flagBits) { return CreateCamera({}, name, flagBits); } CORE_NS::Entity SceneHolder::CreateCamera(const BASE_NS::string& path, const BASE_NS::string& name, uint32_t flagBits) { if (ecs_) { auto pathWithoutRootNode = RemoveRootNodeFromPath(path); // Create camera. Scene util produces an invalid node so set up our camera from node system ourself auto nodeSystem = GetSystem<INodeSystem>(*ecs_); auto node = nodeSystem->CreateNode(); auto entity = node->GetEntity(); CORE3D_NS::ISceneNode* parent = nullptr; if (pathWithoutRootNode.empty()) { parent = rootNode_; } else { parent = rootNode_->LookupNodeByPath(pathWithoutRootNode); } CORE_ASSERT(parent); node->SetParent(*parent); node->SetName(name); auto tcm = GetManager<ITransformComponentManager>(*ecs_); TransformComponent tc; tc.position = Math::Vec3(0.0f, 0.0f, 2.5f); tc.rotation = {}; tcm->Set(entity, tc); CameraComponent cc; cc.sceneFlags |= CameraComponent::SceneFlagBits::ACTIVE_RENDER_BIT; cc.projection = CameraComponent::Projection::PERSPECTIVE; cc.yFov = Math::DEG2RAD * 60.f; cc.zNear = 0.1f; cc.zFar = 100.f; cc.sceneFlags |= flagBits; cameraComponentManager_->Set(entity, cc); cameras_.emplace_back(CameraData::Create(entity)); return entity; } else { CORE_LOG_E("%s: Ecs not available, can not create camera %s", __func__, name.c_str()); } return CORE_NS::Entity(); } void SceneHolder::SetDefaultCamera() { if (cameraComponentManager_) { auto cameraCount = cameraComponentManager_->GetComponentCount(); while (cameraCount) { --cameraCount; // if the scene already contains main camera, respect it if (cameraComponentManager_->Get(cameraComponentManager_->GetEntity(cameraCount)).sceneFlags & CameraComponent::MAIN_CAMERA_BIT) { auto entity = cameraComponentManager_->GetEntity(cameraCount); cameras_.emplace_back(CameraData::Create(entity)); defaultCameraEntity_ = entity; SetMainCamera(entity); return; } } } } void SceneHolder::SetMainCamera(const Entity& entity) { if (cameras_.empty() || (mainCamera_ && entity == mainCamera_->entity)) { return; } if (mainCamera_ && cameraComponentManager_ && EntityUtil::IsValid(mainCamera_->entity)) { bool isAlive = ecs_->GetEntityManager().IsAlive(mainCamera_->entity); if (isAlive) { // Mark previous main camera as inactive. CameraComponent cameraComponent = cameraComponentManager_->Get(mainCamera_->entity); cameraComponent.sceneFlags &= ~CameraComponent::MAIN_CAMERA_BIT; cameraComponentManager_->Set(mainCamera_->entity, cameraComponent); } } mainCamera_ = {}; for (auto& camera : cameras_) { if (camera->entity == entity) { mainCamera_ = camera; break; } } if (!mainCamera_) { if (EntityUtil::IsValid(entity)) { CORE_LOG_W("%s: was not able to set camera for: %" PRIx64 " ", __func__, entity.id); } return; } if (cameraComponentManager_ && EntityUtil::IsValid(mainCamera_->entity)) { // Mark new camera as active. CameraComponent cameraComponent = cameraComponentManager_->Get(mainCamera_->entity); cameraComponent.sceneFlags |= CameraComponent::MAIN_CAMERA_BIT; cameraComponentManager_->Set(mainCamera_->entity, cameraComponent); UpdateViewportSize(mainCamera_->width, mainCamera_->height, mainCamera_->entity.id); } } void SceneHolder::AddCamera(const CORE_NS::Entity& entity) { for (auto c : cameras_) { if (c->entity == entity) { return; } } cameras_.emplace_back(CameraData::Create(entity)); } void SceneHolder::RemoveCamera(const CORE_NS::Entity& entity) { auto it = std::find_if(cameras_.begin(), cameras_.end(), [entity](const auto& item) { // Check if camera entities are the same. return item->entity == entity; }); if (it != cameras_.end()) { cameras_.erase(it); } } void SceneHolder::UpdateViewportSize(uint32_t width, uint32_t height, uint64_t cameraHandle) { CORE_LOG_D("%s: w: %u h: %u", __func__, width, height); // these would be never valid as render target so use some default values to make sure that buffers are prepared if (width == 0) { width = 128; // 128 default width } if (height == 0) { height = 128; // // 128 default height } if (ecs_) { if (cameraHandle == 0 && !cameras_.empty()) { // short circuit defaut camera cameraHandle = cameras_.front()->entity.id; } // Make sure that render target will be updated // ToDo: Clean this up to select the correct camera instance CameraData::Ptr camera; for (auto& c : cameras_) { if (c->entity.id == cameraHandle) { camera = c; break; } } if (!camera) { return; } if (!camera->ownsColorImage) { // we don't own the bitmap so. return; } camera->width = width; camera->height = height; RecreateOutputTexture(camera); if (!EntityUtil::IsValid(camera->entity)) { return; } camera->updateTargets = true; ecs_->RequestRender(); } } void SceneHolder::RecreateOutputTexture(CameraData::Ptr camera) { if ((renderContext_) && (camera)) { if (camera->ownsColorImage) { camera->colorImage = renderContext_->GetDevice().GetGpuResourceManager().Create( GetColorImageDesc(camera->width, camera->height)); #ifdef SCENE_PLUGIN_CREATE_IMPLICIT_DEPTH_TARGET camera->depthImage = renderContext_->GetDevice().GetGpuResourceManager().Create( GetDepthImageDesc(camera->width, camera->height)); #endif } } } bool SceneHolder::UpdateCameraRenderTarget(CameraData::Ptr camera) { if (!EntityUtil::IsValid(camera->entity)) return false; if (auto cc = cameraComponentManager_->Write(camera->entity)) { auto& em = ecs_->GetEntityManager(); cc->renderResolution[0] = static_cast<float>(camera->width); cc->renderResolution[1] = static_cast<float>(camera->height); cc->customColorTargets = { GetOrCreateEntityReference(em, *rhComponentManager_, camera->colorImage) }; if (camera->depthImage) { cc->customDepthTarget = GetOrCreateEntityReference(em, *rhComponentManager_, camera->depthImage); } if (cc->sceneFlags & CameraComponent::SceneFlagBits::ACTIVE_RENDER_BIT) { // please render also. return true; } } // no render. return false; } void SceneHolder::ResetScene(bool initialize) { bool wasScenePrepared = scenePrepared_; scenePrepared_ = false; if (wasScenePrepared && initialize) { return; } SetMainCamera({}); cameras_.clear(); animations_.clear(); // Release resource data. gltfResourceData_ = {}; sceneEntity_ = {}; // Release default camera. defaultCameraEntity_ = {}; // Release scene scene_ = {}; assetManager_ = {}; // Release scene root. rootNode_ = {}; // fully recreate ECS if requested if (ecs_) { cameraComponentManager_ = {}; meshComponentManager_ = {}; nameComponentManager_ = {}; nodeComponentManager_ = {}; meshQuery_ = {}; materialQuery_ = {}; animationQuery_ = {}; ecsListener_->Reset(); ecs_->Uninitialize(); ecs_.reset(); } if (sceneUninitializedCallback_) { QueueApplicationTask(MakeTask( [](auto callback) { callback->Invoke(); return false; }, sceneUninitializedCallback_), false); } if (initialize && CreateDefaultEcs()) { // Create new root node. CORE3D_NS::INodeSystem& nodeSystem = *GetSystem<CORE3D_NS::INodeSystem>(*ecs_); rootNode_ = nodeSystem.CreateNode(); rootNode_->SetName(ROOTNODE_NAME); if (nodeComponentManager_) { nodeComponentManager_->Create(rootNode_->GetEntity()); } scene_ = SCENE_NS::IEntityCollection::Ptr { new SCENE_NS::EntityCollection(*ecs_, "scene", {}) }; // it seems that having our root entity as a part of loaded entify has no significant effect on // anything Presumably it is ok to keep it out of the serialization auto entityRef = rootNode_->SetEnabled(true); assetManager_ = SCENE_NS::IAssetManager::Ptr { new SCENE_NS::AssetManager(*renderContext_, *graphicsContext3D_) }; } } void SceneHolder::SetSceneSystemGraph(const BASE_NS::string& uri) { if (uri != sceneSystemGraphUri_) { sceneSystemGraphUri_ = uri; scenePrepared_ = false; if (scene_) { // reload with new graph LoadScene(); } else if (sceneLoadedCallback_) { QueueApplicationTask(MakeTask( [](auto sceneLoadedCallback) { if (sceneLoadedCallback) { sceneLoadedCallback->Invoke(SCENE_NS::IScene::SCENE_STATUS_UNINITIALIZED); } return false; }, sceneLoadedCallback_), false); } } } void SceneHolder::LoadScene(const BASE_NS::string& uri) { // ToDo: LoadScene shoud deal with this, but currently triggers a problem when we swap between instances if (sceneUri_ == uri) { if (sceneLoadedCallback_) { bool haveEcs = (ecs_ == nullptr); QueueApplicationTask(MakeTask( [haveEcs](auto sceneLoadedCallback) { if (sceneLoadedCallback) { sceneLoadedCallback->Invoke( haveEcs ? SCENE_NS::IScene::SCENE_STATUS_READY : SCENE_NS::IScene::SCENE_STATUS_LOADING_FAILED); } return false; }, sceneLoadedCallback_), false); } return; } sceneUri_ = uri; LoadScene(); } static constexpr BASE_NS::string_view KDUMMY_STRING { "!#&/dummy" }; void SceneHolder::IntrospectNodeless() { #ifndef NDEBUG Entity dummy {}; FindMesh(KDUMMY_STRING, KDUMMY_STRING, dummy); FindMaterial(KDUMMY_STRING, KDUMMY_STRING, dummy); #endif // !NDEBUG RemoveUriComponentsFromMeshes(); ResolveAnimations(); } bool GetEntityId(const CORE_NS::Entity& entity, BASE_NS::string_view& entityName, SCENE_NS::IEntityCollection& collection, BASE_NS::string_view backupCollection, CORE3D_NS::INameComponentManager& nameComponentManager) { if (EntityUtil::IsValid(entity)) { entityName = collection.GetUniqueIdentifierRecursive(entity); if (!entityName.empty() && entityName != "/") { return true; } else { if (auto readHandle = nameComponentManager.Read(entity)) { entityName = readHandle->name; collection.AddEntityToSubcollection(backupCollection, entityName, entity, true); auto gltfCollectionIx = collection.GetSubCollectionIndex(backupCollection); if (auto gltfCollection = collection.GetSubCollection(gltfCollectionIx)) { entityName = gltfCollection->GetUniqueIdentifier(entity); return true; } } else { // ToDo: we could store entity id even it did not have name component CORE_LOG_W("%s: entity missing from collections", __func__); } } } return false; } void ExtractQueryResults(CORE_NS::ComponentQuery& query, SCENE_NS::IEntityCollection& collection, BASE_NS::string_view backupCollection, BASE_NS::shared_ptr<BASE_NS::vector<BASE_NS::string_view>>& result, CORE3D_NS::INameComponentManager& nameComponentManager) { query.Execute(); auto results = query.GetResults(); for (auto& row : results) { BASE_NS::string_view id; if (GetEntityId(row.entity, id, collection, backupCollection, nameComponentManager)) { result->push_back(id); } } } BASE_NS::shared_ptr<BASE_NS::vector<BASE_NS::string_view>> SceneHolder::ListMaterialNames() { BASE_NS::shared_ptr<BASE_NS::vector<BASE_NS::string_view>> ret { new BASE_NS::vector<BASE_NS::string_view> }; if (materialQuery_ && scene_) { ExtractQueryResults(*materialQuery_, *scene_.get(), "GLTF_Materials", ret, *nameComponentManager_); } return ret; } BASE_NS::shared_ptr<BASE_NS::vector<BASE_NS::string_view>> SceneHolder::ListMeshNames() { BASE_NS::shared_ptr<BASE_NS::vector<BASE_NS::string_view>> ret { new BASE_NS::vector<BASE_NS::string_view> }; if (meshQuery_ && scene_) { ExtractQueryResults(*meshQuery_, *scene_.get(), "GLTF_Meshes", ret, *nameComponentManager_); } return ret; } void SceneHolder::RemoveUriComponentsFromMeshes() { meshQuery_->Execute(); auto meshes = meshQuery_->GetResults(); SCENE_PLUGIN_VERBOSE_LOG("found %zu meshes", meshes.size()); // Remove uri component from meshes, since it causes issues with prefab loading. // The gltf importer shares the meshes between collections, if they have // similar uri. // However, this is not desired behavior, because scene-overridden property // values will propagate to future prefab instances (e.g. material). for (auto& mesh : meshes) { if (uriComponentManager_->HasComponent(mesh.entity)) { uriComponentManager_->Destroy(mesh.entity); } } } bool SceneHolder::FindMesh(const BASE_NS::string_view name, const BASE_NS::string_view fullPath, Entity& entity) { if (ecs_) { entity = scene_->GetEntityRecursive(fullPath); if (CORE_NS::EntityUtil::IsValid(entity)) { return true; } meshQuery_->Execute(); auto meshes = meshQuery_->GetResults(); SCENE_PLUGIN_VERBOSE_LOG("found %zu meshes", meshes.size()); #ifdef NDEBUG if (name == KDUMMY_STRING) { return false; } #endif for (auto&& mesh : meshes) { bool effectivelyEnabled = false; if (auto readHandle = nodeComponentManager_->Read(mesh.components[1])) { effectivelyEnabled = readHandle->effectivelyEnabled; } BASE_NS::string entityName; if (auto readHandle = nameComponentManager_->Read(mesh.components[2])) { entityName = readHandle->name; } SCENE_PLUGIN_VERBOSE_LOG("name: %s enabled: %i", entityName.c_str(), effectivelyEnabled); if (name == entityName || fullPath == entityName) { entity = mesh.entity; return true; } } // Could not find the mesh with given the name, try if it has a node const auto& root = nodeSystem_->GetRootNode(); if (auto ecsNode = root.LookupNodeByPath(fullPath)) { entity = ecsNode->GetEntity(); return true; } } return false; } BASE_NS::string SceneHolder::GetResourceId(CORE_NS::Entity entity) { BASE_NS::string result; if (scene_) { result = scene_->GetUniqueIdentifierRecursive(entity); } return result; } bool SceneHolder::FindMaterial(const BASE_NS::string_view name, const BASE_NS::string_view fullPath, Entity& entity) { if (ecs_) { entity = scene_->GetEntityRecursive(fullPath); if (CORE_NS::EntityUtil::IsValid(entity)) { return true; } materialQuery_->Execute(); auto materials = materialQuery_->GetResults(); SCENE_PLUGIN_VERBOSE_LOG("found %zu materials", materials.size()); #ifdef NDEBUG if (name == KDUMMY_STRING) { return false; } #endif for (auto&& material : materials) { bool effectivelyEnabled = false; if (auto readHandle = nodeComponentManager_->Read(material.components[1])) { effectivelyEnabled = readHandle->effectivelyEnabled; } BASE_NS::string entityName; if (auto readHandle = nameComponentManager_->Read(material.components[2])) { entityName = readHandle->name; } SCENE_PLUGIN_VERBOSE_LOG("name: %s enabled: %i", entityName.c_str(), effectivelyEnabled); if (name == entityName) { entity = material.entity; return true; } if (auto readHandle = uriComponentManager_->Read(material.components[3])) { entityName = readHandle->uri; } if (!entityName.empty()) { if (name == entityName || fullPath == entityName) { entity = material.entity; return true; } } } // Could not find the material with the given name, try if it has a node const auto& root = nodeSystem_->GetRootNode(); if (auto ecsNode = root.LookupNodeByPath(fullPath)) { entity = ecsNode->GetEntity(); return true; } // Desperate measures, to be rectified if (auto created = rootNode_->LookupNodeByPath(fullPath)) { entity = created->GetEntity(); return true; } } return false; } bool SceneHolder::FindAnimation(const BASE_NS::string_view name, const BASE_NS::string_view fullPath, Entity& entity) { if (ecs_) { if (!animations_.empty()) { size_t ix = name.rfind(":"); if (ix != BASE_NS::string_view::npos && ix != name.size() - 1) { char* dummy = nullptr; auto entityId = strtoll(name.substr(ix + 1).data(), &dummy, 16); if (animations_.find(entityId) != animations_.cend()) { entity.id = entityId; return true; } } for (auto&& animation : animations_) { if (META_NS::GetValue(interface_pointer_cast<META_NS::INamed>(animation.second)->Name()) == name) { entity.id = animation.first; return true; } } } else { animationQuery_->Execute(); auto animations = animationQuery_->GetResults(); SCENE_PLUGIN_VERBOSE_LOG("found %zu animations", animations.size()); #ifdef NDEBUG if (name == KDUMMY_STRING) { return false; } #endif for (auto&& animation : animations) { bool effectivelyEnabled = false; if (auto readHandle = nodeComponentManager_->Read(animation.components[1])) { effectivelyEnabled = readHandle->effectivelyEnabled; } BASE_NS::string entityName; if (auto readHandle = nameComponentManager_->Read(animation.components[2])) { entityName = readHandle->name; } SCENE_PLUGIN_VERBOSE_LOG("name: %s enabled: %i", entityName.c_str(), effectivelyEnabled); if (name == entityName || fullPath == entityName) { entity = animation.entity; return true; } } // Could not find the animation with given the name, try if it has a node const auto& root = nodeSystem_->GetRootNode(); if (auto ecsNode = root.LookupNodeByPath(fullPath)) { entity = ecsNode->GetEntity(); return true; } } } return false; } void SceneHolder::ResolveAnimations() { if (ecs_) { animationQuery_->Execute(); auto animations = animationQuery_->GetResults(); SCENE_PLUGIN_VERBOSE_LOG("%s, found %zu animations", __func__, animations.size()); for (auto&& animation : animations) { if (animations_.find(animation.entity.id) == animations_.cend()) { animations_[animation.entity.id] = GetObjectRegistry().Create<SCENE_NS::IEcsAnimation>(SCENE_NS::ClassId::EcsAnimation); if (auto ecsProxyIf = interface_pointer_cast<SCENE_NS::IEcsProxyObject>(animations_[animation.entity.id])) { ecsProxyIf->SetCommonListener(GetCommonEcsListener()); } animations_[animation.entity.id]->SetEntity(*ecs_, animation.entity); } } } } void SceneHolder::UpdateAttachments(SCENE_NS::IEcsObject::Ptr& ecsObject) { if (ecs_) { auto entity = ecsObject->GetEntity(); if (EntityUtil::IsValid(entity)) { for (auto&& animation : animations_) { if (animation.second->GetRootEntity() == entity) { ecsObject->AddAttachment(animation.second->GetEntity()); } } } } } // This could be highly inefficient, in most cases we should have a clue of resource type bool SceneHolder::FindResource(const BASE_NS::string_view name, const BASE_NS::string_view fullPath, Entity& entity) { if (ecs_ && scene_) { if (FindAnimation(name, fullPath, entity) || FindMaterial(name, fullPath, entity) || FindMesh(name, fullPath, entity)) { return true; } // traverse through scene collection for (size_t ix = 0; ix < scene_->GetSubCollectionCount(); ix++) { if (const auto& subcollection = scene_->GetSubCollection(ix); subcollection->GetUri() == fullPath) { // the assumption is that we do not have to dig deeper if (subcollection->GetEntityCount() > 0) { entity = subcollection->GetEntity(0); return true; } } } // Try finding an existing resource with an uri. This is needed e.g. for the images loaded from an glTF // file where the uri does not point to an actual image file but the resource is just a entity with uri in // ECS. if (renderMeshComponentManager_ && uriComponentManager_) { for (size_t i = 0; i < uriComponentManager_->GetComponentCount(); ++i) { const Entity currentEntity = uriComponentManager_->GetEntity(static_cast<IComponentManager::ComponentId>(i)); if (ecs_->GetEntityManager().IsAlive(currentEntity)) { if (auto uriComponent = uriComponentManager_->Read(currentEntity); uriComponent && uriComponent->uri == fullPath) { if (renderMeshComponentManager_->HasComponent(currentEntity)) { entity = currentEntity; return true; } } } } } } return false; } bool SceneHolder::GetImageEntity(CORE_NS::Entity material, size_t index, CORE_NS::Entity& entity) { bool ret { false }; if (ecs_ && scene_ && CORE_NS::EntityUtil::IsValid(material)) { if (auto handle = materialComponentManager_->Read(material)) { entity = handle->textures[index].image; ret = true; } } return ret; } bool SceneHolder::GetRenderHandleUri(const RENDER_NS::RenderHandle& handle, BASE_NS::string& uriString) { if (ecs_) { auto& renderUtil = graphicsContext3D_->GetRenderContext().GetRenderUtil(); size_t index { 0 }; while (auto handleReference = rhComponentManager_->GetRenderHandleReference(index)) { if (handleReference.GetHandle() == handle) { auto& renderUtil = graphicsContext3D_->GetRenderContext().GetRenderUtil(); const auto desc = renderUtil.GetRenderHandleDesc(handleReference); if (desc.type == RenderHandleType::GPU_IMAGE && !desc.name.empty()) { // Note: assuming that the name is the image uri. Ignore the name if not in uri format. if (desc.name.find(":/") != BASE_NS::string::npos) { uriString = desc.name; return true; } } } index++; } } return false; } CORE_NS::Entity SceneHolder::CreatePostProcess() { CORE_NS::Entity entity; if (ecs_) { auto pm = CORE_NS::GetManager<CORE3D_NS::IPostProcessComponentManager>(*ecs_); entity = ecs_->GetEntityManager().Create(); // createComponent pm->Create(entity); } return entity; } CORE_NS::Entity SceneHolder::CreateRenderConfiguration() { CORE_NS::Entity entity; if (ecs_) { entity = ecs_->GetEntityManager().Create(); auto rcm = CORE_NS::GetManager<CORE3D_NS::IRenderConfigurationComponentManager>(*ecs_); rcm->Create(entity); } return entity; } void SceneHolder::SetRenderHandle(const CORE_NS::Entity& target, const CORE_NS::Entity& source) { if (ecs_) { if (auto sourceHandle = rhComponentManager_->GetRenderHandleReference(source)) { if (!rhComponentManager_->HasComponent(target)) { rhComponentManager_->Create(target); } if (auto handle = rhComponentManager_->Write(target)) { handle->reference = sourceHandle; } } } } bool SceneHolder::GetEntityUri(const CORE_NS::Entity& entity, BASE_NS::string& uriString) { bool ret { false }; if (ecs_ && scene_ && CORE_NS::EntityUtil::IsValid(entity)) { if (auto handle = uriComponentManager_->Read(entity)) { uriString = BASE_NS::string(handle->uri.data(), handle->uri.size()); ret = true; } } if (!ret) { // Special handling for render handles (automatically loaded images in the ecs are now just render handles // with a name containing the uri). auto handle = rhComponentManager_->GetRenderHandleReference(entity); if (handle) { auto& renderUtil = renderContext_->GetRenderUtil(); const auto desc = renderUtil.GetRenderHandleDesc(handle); if (desc.type == RenderHandleType::GPU_IMAGE && !desc.name.empty()) { // Note: assuming that the name is the image uri. Ignore the name if not in uri format. if (desc.name.find(":/") != BASE_NS::string::npos) { uriString = desc.name; ret = true; } } } } return ret; } bool SceneHolder::GetImageHandle( const CORE_NS::Entity& entity, RENDER_NS::RenderHandleReference& handle, RENDER_NS::GpuImageDesc& desc) { if (rhComponentManager_->HasComponent(entity)) { handle = rhComponentManager_->GetRenderHandleReference(entity); desc = renderContext_->GetDevice().GetGpuResourceManager().GetImageDescriptor(handle); return true; } return false; } bool SceneHolder::SetEntityUri(const CORE_NS::Entity& entity, const BASE_NS::string& uriString) { bool ret { false }; if (ecs_ && scene_ && CORE_NS::EntityUtil::IsValid(entity)) { if (!uriComponentManager_->HasComponent(entity)) { uriComponentManager_->Create(entity); } if (auto handle = uriComponentManager_->Write(entity)) { handle->uri = uriString; ret = true; } } return ret; } bool SceneHolder::GetEntityName(const CORE_NS::Entity& entity, BASE_NS::string& nameString) { bool ret { false }; if (ecs_ && scene_ && CORE_NS::EntityUtil::IsValid(entity)) { if (auto handle = nameComponentManager_->Read(entity)) { nameString = BASE_NS::string(handle->name.data(), handle->name.size()); ret = true; } } return ret; } CORE_NS::Entity SceneHolder::GetEntityByUri(BASE_NS::string_view uriString) { for (size_t i = 0; i < uriComponentManager_->GetComponentCount(); ++i) { const Entity entity = uriComponentManager_->GetEntity(static_cast<IComponentManager::ComponentId>(i)); if (ecs_->GetEntityManager().IsAlive(entity)) { if (auto uriComponent = uriComponentManager_->Read(entity); uriComponent && uriComponent->uri == uriString) { return entity; } } } return {}; } bool ResolveNodeFullPath(CORE3D_NS::ISceneNode* node, BASE_NS::string& path) { if (node) { path = node->GetName(); while ((node = node->GetParent())) { path.insert(0, "/"); auto name = node->GetName(); path.insert(0, name.data(), name.size()); } } return (!path.empty()); } BASE_NS::string ResolveNodeFullPath(CORE_NS::IEcs& ecs, const CORE_NS::Entity& entity) { CORE3D_NS::INodeSystem& nodeSystem = *CORE_NS::GetSystem<CORE3D_NS::INodeSystem>(ecs); BASE_NS::string ret; ResolveNodeFullPath(nodeSystem.GetNode(entity), ret); return ret; } void SceneHolder::LoadScene() { // Reset scene to default. CORE_LOG_I("Loading scene: '%s'", sceneUri_.c_str()); uint32_t loadingStatus = SCENE_NS::IScene::SCENE_STATUS_LOADING_FAILED; bool replacedRoot { false }; while (true) { if (sceneUri_.empty()) { ResetScene(); loadingStatus = SCENE_NS::IScene::SCENE_STATUS_UNINITIALIZED; break; } auto params = SCENE_NS::PathUtil::GetUriParameters(sceneUri_); auto ite = params.find("target"); if (!scene_ || ite == params.end() || ite->second.empty()) { ResetScene(true); replacedRoot = true; } else { CORE_LOG_I("Load into scene: %s", ite->second.c_str()); } // If ecs initializing failed, report it back and return if (!ecs_) { break; } if (assetManager_) { if ((sceneUri_ != "scene://empty") && !assetManager_->LoadAsset(*scene_, sceneUri_, "project://")) { CORE_LOG_E("Loading scene %s failed", sceneUri_.c_str()); ResetScene(); break; } else { // ToDo: This is currently done also when a node is added to container, so no need to do it // here // Rearrange scene to have a concrete common root node, this will be more important for prefabs CORE3D_NS::INodeSystem& nodeSystem = *CORE_NS::GetSystem<CORE3D_NS::INodeSystem>(*ecs_); auto& root = nodeSystem.GetRootNode(); // ToDo: should this fetch the named root node instead auto ourRoot = root.GetChildren().at(0); ourRoot->SetEnabled(true); auto ite = root.GetChildren().begin() + 1; while (ite != root.GetChildren().end()) { (*ite)->SetParent(*ourRoot); ite = root.GetChildren().begin() + 1; } } // Loading might have cached some assets (in case there are multiple // instances). Scene widget will not need them after loading so clear the // cache. if (replacedRoot) { assetManager_->ClearCache(); } } if (sceneUri_ != "scene://empty") { // Set default camera. SetDefaultCamera(); } // ask synchronous update RenderCameras(); loadingStatus = SCENE_NS::IScene::SCENE_STATUS_READY; break; } // If ecs initialization fails, all we can do is to retry loading later if (!ecs_) { loadSceneFailed_ = true; if (sceneLoadedCallback_) { QueueApplicationTask(MakeTask( [](auto sceneLoadedCallback, auto loadingStatus) { if (sceneLoadedCallback) sceneLoadedCallback->Invoke(loadingStatus); return false; }, sceneLoadedCallback_, loadingStatus), false); } return; } // make sure that components get updated ProcessEvents(); IntrospectNodeless(); // Call back the app thread, notify that the graphics and ecs are ready. This // is not quite true, though if (ecs_ && replacedRoot && sceneInitializedCallback_) { BASE_NS::string id { rootNode_->GetName() }; BASE_NS::string cameraId = ResolveNodeFullPath(*ecs_, defaultCameraEntity_); QueueApplicationTask(MakeTask( [](auto sceneInitializedCallback, auto id, auto cameraId) { if (sceneInitializedCallback) { sceneInitializedCallback->Invoke(id, cameraId); } return false; }, sceneInitializedCallback_, id, cameraId), false); } else if (sceneLoadedCallback_) { // Call back the app thread, notify that new scene is loaded QueueApplicationTask(MakeTask( [](auto sceneLoadedCallback, auto loadingStatus) { if (sceneLoadedCallback) { sceneLoadedCallback->Invoke(loadingStatus); } return false; }, sceneLoadedCallback_, loadingStatus), false); } } void SceneHolder::ChangeCamera(SCENE_NS::ICamera::Ptr camera) { if (auto cameraObject = interface_pointer_cast<SCENE_NS::IEcsObject>(camera)) { auto entity = cameraObject->GetEntity(); if (EntityUtil::IsValid(entity)) { SetMainCamera(entity); } } } void logNodes(const CORE3D_NS::ISceneNode& node, BASE_NS::string path) { if (!path.empty() && path.back() != '/') { path.append("/"); } if (!node.GetName().empty()) { path.append(node.GetName()); } else { path.append("["); path.append(BASE_NS::to_string(node.GetEntity().id)); path.append("]"); } CORE_LOG_I("%s", path.c_str()); for (const auto child : node.GetChildren()) logNodes(*child, path); } void SceneHolder::SaveScene(const BASE_NS::string& fileName) { assetManager_->SaveJsonEntityCollection(*scene_.get(), fileName.empty() ? sceneUri_ : fileName, "file://"); // Verbose logs, to be suppressed at some point CORE3D_NS::INodeSystem& nodeSystem = *GetSystem<CORE3D_NS::INodeSystem>(*ecs_); logNodes(nodeSystem.GetRootNode(), "/"); IntrospectNodeless(); } CORE3D_NS::ISceneNode* SceneHolder::CreateNode(const BASE_NS::string& name) { return CreateNode({}, name); } CORE3D_NS::ISceneNode* SceneHolder::CreateNode(const BASE_NS::string& path, const BASE_NS::string& name) { if (ecs_) { auto pathWithoutRootNode = RemoveRootNodeFromPath(path); CORE3D_NS::INodeSystem& nodeSystem = *GetSystem<CORE3D_NS::INodeSystem>(*ecs_); CORE3D_NS::ISceneNode* parent = nullptr; if (pathWithoutRootNode.empty()) { parent = rootNode_; } else { parent = rootNode_->LookupNodeByPath(pathWithoutRootNode); } CORE_ASSERT(parent); auto instanceRoot = nodeSystem.CreateNode(); instanceRoot->SetName(name); instanceRoot->SetParent(*parent); return instanceRoot; } else { CORE_LOG_E("%s: Ecs not available, can not create node %s", __func__, name.c_str()); } return nullptr; } CORE_NS::Entity SceneHolder::CreateMaterial(const BASE_NS::string& name) { if (ecs_) { auto entity = ecs_->GetEntityManager().Create(); RenameEntity(entity, name); materialComponentManager_->Create(entity); scene_->AddEntityToSubcollection("created_materials", name, entity); ProcessEvents(); return entity; } else { CORE_LOG_E("%s: Ecs not available, can not create material %s", __func__, name.c_str()); } return {}; } void SceneHolder::RenameEntity(CORE_NS::Entity entity, const BASE_NS::string& name) { if (ecs_) { // Name Component if (!nameComponentManager_->HasComponent(entity)) { nameComponentManager_->Create(entity); } if (auto nameHandle = nameComponentManager_->Write(entity)) { nameHandle->name = name; } // Root level entity collection item if (BASE_NS::string_view prevName = scene_->GetUniqueIdentifier(entity); !prevName.empty()) { scene_->SetUniqueIdentifier(name, scene_->GetEntity(prevName)); } else if (nodeComponentManager_->HasComponent(entity)) { if (auto ecsNode = nodeSystem_->GetNode(entity)) { if (const auto& children = ecsNode->GetChildren(); children.size()) { if (auto ix = scene_->GetSubCollectionIndexByRoot(children[0]->GetEntity()); ix != -1) { scene_->GetSubCollection(ix)->SetUri(name); } } } } // Experimentals: Subcollection root (will not traverse further, the rest should go through normal property // serialization) } } // Clone the entity, reference will be stored CORE_NS::Entity SceneHolder::CloneEntity(CORE_NS::Entity entity, const BASE_NS::string& name, bool storeWithUniqueId) { CORE_NS::Entity ret {}; if (ecs_ && scene_) { ret = ecs_->GetEntityManager().Create(); CORE_NS::CloneComponents(*ecs_, entity, *ecs_, ret); RenameEntity(ret, name); // when cloning, we trust that implementation uses unique name so no padding required scene_->AddEntityToSubcollection("cloned_entities", name, ret, storeWithUniqueId); } return ret; } void SceneHolder::DestroyEntity(CORE_NS::Entity entity) { // Destroy created materials & meshes here. auto meshCollectionIndex = scene_->GetSubCollectionIndex("GLTF_Meshes"); if (meshCollectionIndex != -1) { auto meshCollection = scene_->GetSubCollection(meshCollectionIndex); if (meshCollection->Contains(entity)) { auto ref = meshCollection->GetReference(entity); meshCollection->RemoveEntity(ref); } } auto materialCollectionIndex = scene_->GetSubCollectionIndex("GLTF_Materials"); if (materialCollectionIndex != -1) { auto materialCollection = scene_->GetSubCollection(materialCollectionIndex); if (materialCollection->Contains(entity)) { auto ref = materialCollection->GetReference(entity); materialCollection->RemoveEntity(ref); } } scene_->RemoveEntityRecursive(entity); } void SceneHolder::SetEntityActive(CORE_NS::Entity entity, bool active) { ecs_->GetEntityManager().SetActive(entity, active); } bool SceneHolder::ReparentEntity(CORE_NS::Entity entity, const BASE_NS::string& parentPath, size_t index) { auto& root = nodeSystem_->GetRootNode(); auto node = nodeSystem_->GetNode(entity); if (!node) { CORE_LOG_W("SceneHolder::ReparentEntity, Failed to find node from entity."); return false; } auto parentNode = root.LookupNodeByPath(parentPath); if (!parentNode) { if (parentPath == "/" || parentPath.empty()) { parentNode = &root; } else { CORE_LOG_W("SceneHolder::ReparentEntity, Failed to find parent node '%s'", parentPath.c_str()); return false; } } if (parentNode != node->GetParent()) { if (index != SIZE_MAX) { parentNode->InsertChild(index, *node); } else { parentNode->AddChild(*node); } } return true; } const CORE3D_NS::ISceneNode* SceneHolder::ReparentEntity(const BASE_NS::string& parentPath, const BASE_NS::string& name) { if (ecs_) { const auto& root = nodeSystem_->GetRootNode(); auto ecsNode = root.LookupNodeByPath(name); auto parentNode = root.LookupNodeByPath(parentPath); if (!ecsNode && parentNode) { if (ecsNode = parentNode->LookupNodeByPath(name); !ecsNode) { CORE_LOG_W("Could not find: '%s', tried root and parent '%s'", name.c_str(), parentPath.c_str()); } } else if (parentNode && parentNode != ecsNode) { // prefabs may get have their path pointing them self // before they are positioned into scene const_cast<CORE3D_NS::ISceneNode*>(ecsNode)->SetParent(*parentNode); SCENE_PLUGIN_VERBOSE_LOG("reparenting '%s' to '%s'", name.c_str(), parentPath.c_str()); } else if (!parentPath.empty() && parentPath != "/") { CORE_LOG_W("Could not find parent '%s'", parentPath.c_str()); // Even parent is not found, we can still activate the bindings, still we could // reschedule retry here } return ecsNode; } return {}; } void SceneHolder::SetMesh(CORE_NS::Entity targetEntity, CORE_NS::Entity mesh) { if (ecs_) { if (EntityUtil::IsValid(targetEntity) && EntityUtil::IsValid(mesh)) { // ToDo: should perhaps generate render mesh component for entity if it does not exist yet? if (!renderMeshComponentManager_->HasComponent(targetEntity)) { renderMeshComponentManager_->Create(targetEntity); } if (auto handle = renderMeshComponentManager_->Write(targetEntity)) { handle->mesh = mesh; } } } } CORE_NS::Entity SceneHolder::GetMaterial(CORE_NS::Entity meshEntity, int64_t submeshIndex) { CORE_NS::Entity material; if (ecs_) { if (EntityUtil::IsValid(meshEntity)) { if (auto handle = meshComponentManager_->Read(meshEntity)) { if (submeshIndex >= 0 && submeshIndex < handle->submeshes.size()) { material = handle->submeshes[submeshIndex].material; } } } } return material; } BASE_NS::string_view SceneHolder::GetMaterialName(CORE_NS::Entity meshEntity, int64_t submeshIndex) { BASE_NS::string_view entityName; auto entity = GetMaterial(meshEntity, submeshIndex); if (!GetEntityId(entity, entityName, *scene_.get(), "GLTF_Materials", *nameComponentManager_)) { CORE_LOG_W("%s: could not find material", __func__); } return entityName; } BASE_NS::string_view SceneHolder::GetMeshName(CORE_NS::Entity referringEntity) { BASE_NS::string_view entityName; if (EntityUtil::IsValid(referringEntity)) { CORE_NS::Entity meshEntity; if (renderMeshComponentManager_->HasComponent(referringEntity)) { if (auto handle = renderMeshComponentManager_->Read(referringEntity)) { meshEntity = handle->mesh; } } else { CORE_LOG_W("%s: could not find mesh from entity", __func__); } if (!GetEntityId(meshEntity, entityName, *scene_.get(), "GLTF_Meshes", *nameComponentManager_)) { CORE_LOG_W("%s: could not find valid mesh", __func__); } } return entityName; } void SceneHolder::SetMaterial(CORE_NS::Entity targetEntity, CORE_NS::Entity material, int64_t submeshIndex) { if (ecs_) { if (EntityUtil::IsValid(targetEntity)) { // ToDo: should perhaps generate mesh component for entity if it does not exist yet? if (auto handle = meshComponentManager_->Write(targetEntity)) { if (submeshIndex == -1) { for (auto&& submesh : handle->submeshes) { submesh.material = material; } } else if (submeshIndex >= 0 && submeshIndex < handle->submeshes.size()) { handle->submeshes[submeshIndex].material = material; } } } } } CORE_NS::EntityReference SceneHolder::BindUIBitmap(SCENE_NS::IBitmap::Ptr bitmap, bool createNew) { if (ecs_) { BASE_NS::string uri; RENDER_NS::RenderHandleReference imageHandle {}; // Need two things, uri if (auto uriBmp = interface_pointer_cast<SCENE_NS::IBitmap>(bitmap)) { uri = uriBmp->Uri()->GetValue(); } else { uri = interface_pointer_cast<META_NS::IObjectInstance>(bitmap)->GetInstanceId().ToString(); } // And concrete handle to data if (auto lumeBmp = interface_pointer_cast<SCENE_NS::IBitmap>(bitmap)) { imageHandle = lumeBmp->GetRenderHandle(); } if (imageHandle) { // check if we have existing resource CORE_NS::Entity existing; if (FindResource(uri, uri, existing)) { auto hande = rhComponentManager_->Get(existing); hande.reference = imageHandle; rhComponentManager_->Set(existing, hande); return ecs_->GetEntityManager().GetReferenceCounted(existing); } if (!createNew) { return {}; } // if not, create new one EntityReference entity = ecs_->GetEntityManager().CreateReferenceCounted(); nameComponentManager_->Create(entity); auto component = nameComponentManager_->Get(entity); component.name = uri; nameComponentManager_->Set(entity, component); // Not sure if we should do this, somehow we need to keep the entity alive // with proper uri it could be found afterwards auto& subcollection = scene_->AddSubCollection(uri, "bitmap://"); subcollection.AddEntity(entity); // Or this. We should perhaps use URI component more rhComponentManager_->Create(entity); auto hande = rhComponentManager_->Get(entity); hande.reference = imageHandle; rhComponentManager_->Set(entity, hande); return entity; } } return {}; } void SceneHolder::SetTexture(size_t index, CORE_NS::Entity targetEntity, CORE_NS::EntityReference imageEntity) { if (ecs_) { if (auto handle = materialComponentManager_->Write(targetEntity)) { handle->textures[index].image = imageEntity; } } } BASE_NS::string_view ResolveSamplerUri(SCENE_NS::ITextureInfo::SamplerId samplerId) { switch (samplerId) { case SCENE_NS::ITextureInfo::CORE_DEFAULT_SAMPLER_NEAREST_REPEAT: return "engine://CORE_DEFAULT_SAMPLER_NEAREST_REPEAT"; case SCENE_NS::ITextureInfo::CORE_DEFAULT_SAMPLER_NEAREST_CLAMP: return "engine://CORE_DEFAULT_SAMPLER_NEAREST_CLAMP"; case SCENE_NS::ITextureInfo::CORE_DEFAULT_SAMPLER_LINEAR_REPEAT: return "engine://CORE_DEFAULT_SAMPLER_LINEAR_REPEAT"; case SCENE_NS::ITextureInfo::CORE_DEFAULT_SAMPLER_LINEAR_CLAMP: return "engine://CORE_DEFAULT_SAMPLER_LINEAR_CLAMP"; case SCENE_NS::ITextureInfo::CORE_DEFAULT_SAMPLER_LINEAR_MIPMAP_REPEAT: return "engine://CORE_DEFAULT_SAMPLER_LINEAR_MIPMAP_REPEAT"; case SCENE_NS::ITextureInfo::CORE_DEFAULT_SAMPLER_LINEAR_MIPMAP_CLAMP: return "engine://CORE_DEFAULT_SAMPLER_LINEAR_MIPMAP_CLAMP"; default: CORE_LOG_W("%s: unable to find sampler uri for: %d", __func__, samplerId); } return ""; } static constexpr size_t ENGINE_URI_PREFIX_LEN = BASE_NS::string_view("engine://").size(); SCENE_NS::IShader::Ptr SceneHolder::GetShader(CORE_NS::Entity materialEntity, ShaderType type) { // Resolve render handle from material. if (!ecs_) { return {}; } if (!EntityUtil::IsValid(materialEntity)) { return {}; } RENDER_NS::RenderHandleReference shaderHandleRef; EntityReference shaderEntityRef {}; if (auto readHandle = materialComponentManager_->Read(materialEntity)) { if (type == ShaderType::MATERIAL_SHADER) { shaderEntityRef = readHandle->materialShader.shader; } else if (type == ShaderType::DEPTH_SHADER) { shaderEntityRef = readHandle->depthShader.shader; } } if (shaderEntityRef) { auto rh = rhComponentManager_->GetRenderHandleReference(shaderEntityRef); if (RENDER_NS::RenderHandleUtil::IsValid(rh.GetHandle())) { auto uri = GetHandleUri(rh, HANDLE_TYPE_SHADER); if (!uri.empty()) { auto shader = SCENE_NS::Shader(); auto shaderInterface = interface_pointer_cast<SCENE_NS::IShader>(shader); SetValue(shaderInterface->Uri(), uri); return shader; } } } return {}; } SCENE_NS::IGraphicsState::Ptr SceneHolder::GetGraphicsState(CORE_NS::Entity materialEntity, ShaderType type) { // Resolve render handle from material. if (!ecs_) { return {}; } if (!EntityUtil::IsValid(materialEntity)) { return {}; } RENDER_NS::RenderHandleReference shaderHandleRef; EntityReference stateEntityRef {}; if (auto readHandle = materialComponentManager_->Read(materialEntity)) { if (type == ShaderType::MATERIAL_SHADER) { stateEntityRef = readHandle->materialShader.graphicsState; } else if (type == ShaderType::DEPTH_SHADER) { stateEntityRef = readHandle->depthShader.graphicsState; } } if (stateEntityRef) { auto rh = rhComponentManager_->GetRenderHandleReference(stateEntityRef); if (RENDER_NS::RenderHandleUtil::IsValid(rh.GetHandle())) { auto uri = GetHandleUri(rh); if (!uri.empty()) { auto state = SCENE_NS::GraphicsState(); auto interface = interface_pointer_cast<SCENE_NS::IGraphicsState>(state); // The uri contains either .shader or .shadergs. BASE_NS::string extension = ".shadergs"; if (uri.find(extension) == BASE_NS::string::npos) { extension = ".shader"; } // Split the uri to uri and variant. auto ix = uri.find_last_of(extension); if (ix != BASE_NS::string_view::npos && ix < uri.size() - 1) { SetValue(interface->Uri(), BASE_NS::string(uri.substr(0, ix + 1))); SetValue(interface->Variant(), BASE_NS::string(uri.substr(ix + 1))); SCENE_PLUGIN_VERBOSE_LOG("Got graphics state: %s variant: %s", META_NS::GetValue(interface->Uri()).c_str(), META_NS::GetValue(interface->Variant()).c_str()); } else { SetValue(interface->Uri(), uri); SetValue(interface->Variant(), ""); } if (auto shRef = interface_cast<ISceneHolderRef>(state)) { shRef->SetSceneHolder(me_); shRef->SetIndex(type); } return state; } } } return {}; } void SceneHolder::SetGraphicsState( CORE_NS::Entity materialEntity, ShaderType type, const RENDER_NS::GraphicsState& state) { if (!ecs_) { return; } if (!EntityUtil::IsValid(materialEntity)) { return; } CORE_NS::EntityReference entityRef; if (auto readHandle = materialComponentManager_->Read(materialEntity)) { if (type == ShaderType::MATERIAL_SHADER) { entityRef = readHandle->materialShader.graphicsState; } else if (type == ShaderType::DEPTH_SHADER) { entityRef = readHandle->depthShader.graphicsState; } } auto rh = rhComponentManager_->GetRenderHandleReference(entityRef); if (RENDER_NS::RenderHandleUtil::IsValid(rh.GetHandle())) { auto& engineShaderManager = renderContext_->GetDevice().GetShaderManager(); auto hash = engineShaderManager.HashGraphicsState(state); auto currentState = engineShaderManager.GetGraphicsState(rh); if (hash != engineShaderManager.HashGraphicsState(currentState)) { auto desc = engineShaderManager.GetIdDesc(rh); engineShaderManager.CreateGraphicsState({ desc.path, state }); } } } bool SceneHolder::GetGraphicsState( CORE_NS::Entity materialEntity, ShaderType type, const SCENE_NS::IShaderGraphicsState::Ptr& ret) { if (!ecs_) { return false; } if (!EntityUtil::IsValid(materialEntity)) { return false; } CORE_NS::EntityReference entityRef; if (auto readHandle = materialComponentManager_->Read(materialEntity)) { if (type == ShaderType::MATERIAL_SHADER) { entityRef = readHandle->materialShader.graphicsState; } else if (type == ShaderType::DEPTH_SHADER) { entityRef = readHandle->depthShader.graphicsState; } } auto rh = rhComponentManager_->GetRenderHandleReference(entityRef); if (RENDER_NS::RenderHandleUtil::IsValid(rh.GetHandle())) { auto& engineShaderManager = renderContext_->GetDevice().GetShaderManager(); auto state = engineShaderManager.GetGraphicsState(rh); if (auto typed = interface_cast<SCENE_NS::IPendingRequestData<RENDER_NS::GraphicsState>>(ret)) { typed->Add(state); return true; } } return false; } void SceneHolder::SetShader(CORE_NS::Entity materialEntity, ShaderType type, SCENE_NS::IShader::Ptr shader) { if (!ecs_) { return; } if (!EntityUtil::IsValid(materialEntity)) { return; } EntityReference shaderEntityRef {}; RenderHandleReference rh {}; auto& engineShaderManager = renderContext_->GetDevice().GetShaderManager(); if (shader) { rh = shader->GetRenderHandleReference(engineShaderManager); auto uri = META_NS::GetValue(shader->Uri()); if (RenderHandleUtil::IsValid(rh.GetHandle())) { shaderEntityRef = GetOrCreateEntityReference(ecs_->GetEntityManager(), *rhComponentManager_, rh); // Studio used to update also uri component, maybe someone else should do this // if needed to begin with if (!uriComponentManager_->HasComponent(shaderEntityRef)) { uriComponentManager_->Create(shaderEntityRef); } if (auto handle = uriComponentManager_->Write(shaderEntityRef)) { handle->uri = uri; } } else { CORE_LOG_W("Failed to set shader, invalid render handle: %s", uri.c_str()); return; } if (auto shref = interface_cast<ISceneHolderRef>(shader)) { shref->SetSceneHolder(me_); shref->SetIndex(type); } } // Only writing the value if it has changed. bool valueChanged = false; if (auto readHandle = materialComponentManager_->Read(materialEntity)) { if (type == ShaderType::MATERIAL_SHADER) { valueChanged = readHandle->materialShader.shader != shaderEntityRef; } else if (type == ShaderType::DEPTH_SHADER) { valueChanged = readHandle->depthShader.shader != shaderEntityRef; } } if (!valueChanged) { return; } if (auto writeHandle = materialComponentManager_->Write(materialEntity)) { if (type == ShaderType::MATERIAL_SHADER) { writeHandle->materialShader.shader = shaderEntityRef; if (!writeHandle->materialShader.graphicsState) { // initialize the graphics state handle, so we can resolve it later if (auto graphicsStateHandle = engineShaderManager.GetGraphicsStateHandleByShaderHandle(rh)) { writeHandle->materialShader.graphicsState = GetOrCreateEntityReference(ecs_->GetEntityManager(), *rhComponentManager_, graphicsStateHandle); } } } else if (type == ShaderType::DEPTH_SHADER) { writeHandle->depthShader.shader = shaderEntityRef; if (!writeHandle->depthShader.graphicsState) { if (auto graphicsStateHandle = engineShaderManager.GetGraphicsStateHandleByShaderHandle(rh)) { writeHandle->depthShader.graphicsState = GetOrCreateEntityReference(ecs_->GetEntityManager(), *rhComponentManager_, graphicsStateHandle); } } } } ProcessEvents(); } void SceneHolder::SetGraphicsState(CORE_NS::Entity materialEntity, ShaderType type, SCENE_NS::IGraphicsState::Ptr state) { if (!ecs_) { return; } if (!EntityUtil::IsValid(materialEntity)) { return; } EntityReference stateEntityRef {}; if (state) { auto& engineShaderManager = renderContext_->GetDevice().GetShaderManager(); auto rh = state->GetRenderHandleReference(engineShaderManager); if (RenderHandleUtil::IsValid(rh.GetHandle())) { auto uri = META_NS::GetValue(state->Uri()); auto variant = META_NS::GetValue(state->Variant()); stateEntityRef = GetOrCreateEntityReference(ecs_->GetEntityManager(), *rhComponentManager_, rh); // Studio used to update also uri component, maybe someone else should do this // if needed to begin with if (!uriComponentManager_->HasComponent(stateEntityRef)) { uriComponentManager_->Create(stateEntityRef); } if (auto handle = uriComponentManager_->Write(stateEntityRef)) { handle->uri = uri.append(variant); } } else { CORE_LOG_W("Failed to set shader, invalid render handle: %s", META_NS::GetValue(state->Uri()).c_str()); return; } if (auto shref = interface_cast<ISceneHolderRef>(state)) { shref->SetSceneHolder(me_); shref->SetIndex(type); } } // Only writing the value if it has changed. bool valueChanged = false; if (auto readHandle = materialComponentManager_->Read(materialEntity)) { if (type == ShaderType::MATERIAL_SHADER) { valueChanged = readHandle->materialShader.graphicsState != stateEntityRef; } else if (type == ShaderType::DEPTH_SHADER) { valueChanged = readHandle->depthShader.graphicsState != stateEntityRef; } } if (!valueChanged) { return; } if (auto writeHandle = materialComponentManager_->Write(materialEntity)) { if (type == ShaderType::MATERIAL_SHADER) { writeHandle->materialShader.graphicsState = stateEntityRef; } else if (type == ShaderType::DEPTH_SHADER) { writeHandle->depthShader.graphicsState = stateEntityRef; } } ProcessEvents(); } BASE_NS::string SceneHolder::GetHandleUri(RENDER_NS::RenderHandleReference renderHandleReference, UriHandleType type) { if (ecs_ && renderContext_) { auto& device = renderContext_->GetDevice(); auto& shaderManager = device.GetShaderManager(); if ((type == HANDLE_TYPE_SHADER && shaderManager.IsShader(renderHandleReference)) || (type == HANDLE_TYPE_DO_NOT_CARE)) { auto desc = shaderManager.GetIdDesc(renderHandleReference); return desc.path; } } return BASE_NS::string(); } CORE_NS::Entity SceneHolder::LoadSampler(BASE_NS::string_view uri) { CORE_NS::Entity ret; if (ecs_ && renderContext_) { const auto name = uri.substr(ENGINE_URI_PREFIX_LEN); auto linearHandle = renderContext_->GetDevice().GetGpuResourceManager().GetSamplerHandle(name); auto entity = rhComponentManager_->GetEntityWithReference(linearHandle); if (!EntityUtil::IsValid(entity)) { // No existing sampler found, prepare one auto uriManager = GetManager<CORE3D_NS::IUriComponentManager>(*ecs_); entity = ecs_->GetEntityManager().Create(); uriManager->Create(entity); uriManager->Write(entity)->uri = uri; rhComponentManager_->Create(entity); rhComponentManager_->Write(entity)->reference = linearHandle; RenameEntity(entity, BASE_NS::string(name.data(), name.size())); scene_->AddEntityToSubcollection("samplers", name, entity, false); } ret = entity; } return ret; } CORE_NS::EntityReference SceneHolder::LoadImage(BASE_NS::string_view uri, RENDER_NS::RenderHandleReference rh) { CORE_NS::Entity ret; if (FindResource(uri, uri, ret)) { return ecs_->GetEntityManager().GetReferenceCounted(ret); } if (scene_ && assetManager_) { RENDER_NS::RenderHandleReference imageHandle; if (rh) { imageHandle = rh; } else { imageHandle = assetManager_->GetEcsSerializer().LoadImageResource(uri); } if (imageHandle) { return GetOrCreateEntityReference(ecs_->GetEntityManager(), *rhComponentManager_, imageHandle); } } return {}; } void SceneHolder::SetSampler(size_t index, CORE_NS::Entity targetEntity, SCENE_NS::ITextureInfo::SamplerId samplerId) { if (ecs_) { if (EntityUtil::IsValid(targetEntity)) { bool wrote = false; if (auto handle = materialComponentManager_->Write(targetEntity)) { auto entity = LoadSampler(ResolveSamplerUri(samplerId)); if (EntityUtil::IsValid(entity)) { // both scene and texture array will hold a reference to entity handle->textures[index].sampler = ecs_->GetEntityManager().GetReferenceCounted(entity); wrote = true; } } if (wrote) { ProcessEvents(); } } } } void SceneHolder::SetSubmeshRenderSortOrder(CORE_NS::Entity targetEntity, int64_t submeshIndex, uint8_t value) { if (ecs_) { if (EntityUtil::IsValid(targetEntity)) { bool wrote = false; if (auto handle = meshComponentManager_->Write(targetEntity)) { if (submeshIndex >= 0 && submeshIndex < handle->submeshes.size()) { handle->submeshes[submeshIndex].renderSortLayerOrder = value; wrote = true; } } if (wrote) { ProcessEvents(); } } } } void SceneHolder::SetSubmeshAABBMin(CORE_NS::Entity targetEntity, int64_t submeshIndex, const BASE_NS::Math::Vec3& vec) { if (ecs_) { if (EntityUtil::IsValid(targetEntity)) { bool wrote = false; if (auto handle = meshComponentManager_->Write(targetEntity)) { if (submeshIndex >= 0 && submeshIndex < handle->submeshes.size()) { handle->submeshes[submeshIndex].aabbMin = vec; handle->aabbMin = min(handle->aabbMin, vec); wrote = true; } } if (wrote) { ProcessEvents(); } } } } void SceneHolder::RemoveSubmesh(CORE_NS::Entity targetEntity, int64_t submeshIndex) { if (ecs_) { if (EntityUtil::IsValid(targetEntity)) { bool wrote = false; if (auto handle = meshComponentManager_->Write(targetEntity)) { if (submeshIndex < 0) { handle->submeshes.clear(); handle->aabbMin = { 0.f, 0.f, 0.f }; handle->aabbMax = { 0.f, 0.f, 0.f }; wrote = true; } else if (submeshIndex < handle->submeshes.size()) { handle->submeshes.erase(handle->submeshes.begin() + submeshIndex); for (const auto& submesh : handle->submeshes) { handle->aabbMin = BASE_NS::Math::min(handle->aabbMin, submesh.aabbMin); handle->aabbMax = BASE_NS::Math::max(handle->aabbMax, submesh.aabbMax); } wrote = true; } } if (wrote) { ProcessEvents(); } } } } void SceneHolder::SetSubmeshAABBMax(CORE_NS::Entity targetEntity, int64_t submeshIndex, const BASE_NS::Math::Vec3& vec) { if (ecs_) { if (EntityUtil::IsValid(targetEntity)) { bool wrote = false; if (auto handle = meshComponentManager_->Write(targetEntity)) { if (submeshIndex >= 0 && submeshIndex < handle->submeshes.size()) { handle->submeshes[submeshIndex].aabbMax = vec; handle->aabbMax = max(handle->aabbMax, vec); wrote = true; } } if (wrote) { ProcessEvents(); } } } } void SceneHolder::EnableEnvironmentComponent(CORE_NS::Entity entity) { if (ecs_) { if (EntityUtil::IsValid(entity)) { if (!envComponentManager_->HasComponent(entity)) { envComponentManager_->Create(entity); } } } } void SceneHolder::EnableLayerComponent(CORE_NS::Entity entity) { if (ecs_) { if (EntityUtil::IsValid(entity)) { if (!layerComponentManager_->HasComponent(entity)) { layerComponentManager_->Create(entity); } } } } void SceneHolder::EnableLightComponent(CORE_NS::Entity entity) { if (ecs_) { if (EntityUtil::IsValid(entity)) { if (!lightComponentManager_->HasComponent(entity)) { lightComponentManager_->Create(entity); } } } } template<typename T> constexpr inline CORE3D_NS::IMeshBuilder::DataBuffer FillData(array_view<const T> c) noexcept { Format format = BASE_FORMAT_UNDEFINED; if constexpr (is_same_v<T, Math::Vec2>) { format = BASE_FORMAT_R32G32_SFLOAT; } else if constexpr (is_same_v<T, Math::Vec3>) { format = BASE_FORMAT_R32G32B32_SFLOAT; } else if constexpr (is_same_v<T, Math::Vec4>) { format = BASE_FORMAT_R32G32B32A32_SFLOAT; } else if constexpr (is_same_v<T, uint16_t>) { format = BASE_FORMAT_R16_UINT; } else if constexpr (is_same_v<T, uint32_t>) { format = BASE_FORMAT_R32_UINT; } return CORE3D_NS::IMeshBuilder::DataBuffer { format, sizeof(T), { reinterpret_cast<const uint8_t*>(c.data()), c.size() * sizeof(T) } }; } template<typename IndicesType> CORE_NS::Entity SceneHolder::CreateMeshFromArrays(const BASE_NS::string& name, SCENE_NS::MeshGeometryArrayPtr<IndicesType> arrays, RENDER_NS::IndexType indexType, Entity existingEntity, bool append) { if (!ecs_) { CORE_LOG_W("%s: no ecs, cannot create mesh", __func__); return {}; } if (!arrays || arrays->size() == 0) { if (EntityUtil::IsValid(existingEntity)) { RemoveSubmesh(existingEntity, -1); return existingEntity; } else { // create empty, submeshes may follow auto entity = ecs_->GetEntityManager().Create(); RenameEntity(entity, name); meshComponentManager_->Create(entity); scene_->AddEntityToSubcollection("created_meshes", name, entity); return entity; } } if (EntityUtil::IsValid(existingEntity) && !append) { RemoveSubmesh(existingEntity, -1); } size_t subMeshIndex = 0; auto meshBuilder = CORE_NS::CreateInstance<CORE3D_NS::IMeshBuilder>(*renderContext_, CORE3D_NS::UID_MESH_BUILDER); RENDER_NS::IShaderManager& shaderManager = renderContext_->GetDevice().GetShaderManager(); const RENDER_NS::VertexInputDeclarationView vertexInputDeclaration = shaderManager.GetVertexInputDeclarationView(shaderManager.GetVertexInputDeclarationHandle( CORE3D_NS::DefaultMaterialShaderConstants::VERTEX_INPUT_DECLARATION_FORWARD)); meshBuilder->Initialize(vertexInputDeclaration, 1); for (auto&& geometry : *arrays.get()) { CORE3D_NS::IMeshBuilder::Submesh submesh; submesh.indexType = indexType; submesh.vertexCount = static_cast<uint32_t>(geometry->vertices.size()); submesh.indexCount = static_cast<uint32_t>(geometry->indices.size()); submesh.tangents = geometry->generateTangents; meshBuilder->AddSubmesh(submesh); } meshBuilder->Allocate(); for (auto&& geometry : *arrays.get()) { meshBuilder->SetVertexData(subMeshIndex, FillData<BASE_NS::Math::Vec3>(geometry->vertices), FillData<BASE_NS::Math::Vec3>(geometry->normals), FillData<BASE_NS::Math::Vec2>(geometry->uvs), FillData<BASE_NS::Math::Vec2>(geometry->uv2s), FillData<BASE_NS::Math::Vec4>(geometry->tangents), FillData<BASE_NS::Math::Vec4>(geometry->colors)); meshBuilder->CalculateAABB(subMeshIndex, FillData<BASE_NS::Math::Vec3>(geometry->vertices)); meshBuilder->SetIndexData(subMeshIndex, FillData<IndicesType>(geometry->indices)); } auto entity = meshBuilder->CreateMesh(*ecs_); if (EntityUtil::IsValid(existingEntity)) { for (size_t ii = 0; ii < arrays->size(); ii++) { CopySubMesh(existingEntity, entity, ii); } ecs_->GetEntityManager().Destroy(entity); return existingEntity; } RenameEntity(entity, name); scene_->AddEntityToSubcollection("created_meshes", name, entity); ProcessEvents(); return entity; } // we rely that someone else will ret up correct flags to components void SceneHolder::SetMultiviewCamera(CORE_NS::Entity target, CORE_NS::Entity source) { if (!ecs_ || !EntityUtil::IsValid(target) || !EntityUtil::IsValid(source) || !cameraComponentManager_->HasComponent(source) || !cameraComponentManager_->HasComponent(target)) { CORE_LOG_W("%s: camera component not valid", __func__); return; } if (auto handle = cameraComponentManager_->Write(target)) { for (auto& ref : handle->multiViewCameras) { if (ref.id == target.id) { return; } } handle->multiViewCameras.push_back(source); } } // we rely that someone else will ret up correct flags to components void SceneHolder::RemoveMultiviewCamera(CORE_NS::Entity target, CORE_NS::Entity source) { if (!ecs_ || !EntityUtil::IsValid(target) || !EntityUtil::IsValid(source) || !cameraComponentManager_->HasComponent(source) || !cameraComponentManager_->HasComponent(target)) { CORE_LOG_W("%s: camera component not valid", __func__); return; } if (auto handle = cameraComponentManager_->Write(target)) { for (size_t ii = 0; ii < handle->multiViewCameras.size(); ii++) { if (handle->multiViewCameras[ii] == target) { handle->multiViewCameras.erase(handle->multiViewCameras.cbegin() + ii); return; } } } } void SceneHolder::CopySubMesh(CORE_NS::Entity target, CORE_NS::Entity source, size_t index) { if (!ecs_ || !EntityUtil::IsValid(target) || !EntityUtil::IsValid(source) || !meshComponentManager_->HasComponent(source)) { CORE_LOG_W("%s: cannot copy submesh", __func__); return; } bool wrote = false; if (auto sourceComponent = meshComponentManager_->Read(source)) { if (sourceComponent->submeshes.size() > index) { if (!meshComponentManager_->HasComponent(target)) { meshComponentManager_->Create(target); } if (auto targetComponent = meshComponentManager_->Write(target)) { targetComponent->submeshes.push_back(CORE3D_NS::MeshComponent::Submesh()); // Todo: verify if copy by assignment is enough, or do we need deep copy // and explicit reference modifications targetComponent->submeshes.back() = sourceComponent->submeshes.at(index); for (const auto& submesh : targetComponent->submeshes) { targetComponent->aabbMin = BASE_NS::Math::min(targetComponent->aabbMin, submesh.aabbMin); targetComponent->aabbMax = BASE_NS::Math::max(targetComponent->aabbMax, submesh.aabbMax); } wrote = true; } } } if (wrote) { ProcessEvents(); } } void SceneHolder::ReleaseOwnership(CORE_NS::Entity entity) { if (ecs_ && scene_) { if (EntityUtil::IsValid(entity)) { if (const auto cachedEntity = FindCachedRelatedEntity(entity); EntityUtil::IsValid(cachedEntity) && entity != cachedEntity) { SCENE_PLUGIN_VERBOSE_LOG("%s: Cached entity does not match: entity: %I64u, cachedEntity: %I64u", __func__, entity.id, cachedEntity.id); if (auto cachedIdx = scene_->GetSubCollectionIndexByRoot(cachedEntity); cachedIdx != -1) { scene_->GetSubCollection(cachedIdx)->SetActive(false); scene_->RemoveEntityRecursive(cachedEntity); } } scene_->RemoveEntityRecursive(entity); } } } // Reposition a node within its parent void SceneHolder::ReindexEntity(CORE_NS::Entity target, size_t index) { if (ecs_ && scene_ && EntityUtil::IsValid(target)) { // Get the node if (auto node = nodeSystem_->GetNode(target)) { // Get the parent if (auto parent = node->GetParent()) { parent->RemoveChild(*node); parent->InsertChild(index, *node); } } // do we need to adjust also collection manually (it should be reference only) scene_->MarkModified(true, true); } } template CORE_NS::Entity SceneHolder::CreateMeshFromArrays<uint16_t>(const BASE_NS::string& name, SCENE_NS::MeshGeometryArrayPtr<uint16_t> arrays, RENDER_NS::IndexType indexType, Entity existingEntity, bool append); template CORE_NS::Entity SceneHolder::CreateMeshFromArrays<uint32_t>(const BASE_NS::string& name, SCENE_NS::MeshGeometryArrayPtr<uint32_t> arrays, RENDER_NS::IndexType indexType, Entity existingEntity, bool append); string SceneHolder::GetUniqueName() { return instanceId_.ToString().append(to_string(++instanceNumber_)); } // Enable new multi-mesh batch CORE_NS::Entity SceneHolder::CreateMultiMeshInstance(CORE_NS::Entity baseComponent) { if (ecs_) { if (!nodeSystem_->GetNode(baseComponent)) { CORE_LOG_I("%s: selected base entity does not have node on ecs", __func__); // Now, the assumption is that entity should have either RenderMeshComponent or MeshComponent // It is bit open if it has render mesh component, shoud we copy its mesh data implicitly CORE3D_NS::RenderMeshComponent renderMeshData; if (auto data = renderMeshComponentManager_->Read(baseComponent)) { renderMeshData = *data; } auto node = nodeSystem_->CreateNode(); auto nodeEntity = node->GetEntity(); renderMeshComponentManager_->Create(nodeEntity); if (auto data = renderMeshComponentManager_->Write(nodeEntity)) { *data = renderMeshData; if (!EntityUtil::IsValid(data->mesh)) { // base component may have mesh component, or then it does not data->mesh = baseComponent; } } baseComponent = nodeEntity; } if (!renderMeshComponentManager_->HasComponent(baseComponent)) { renderMeshComponentManager_->Create(baseComponent); } } return baseComponent; } static constexpr size_t MULTI_MESH_CHILD_PREFIX_LEN = MULTI_MESH_CHILD_PREFIX.size(); bool SceneHolder::IsMultiMeshChild(const CORE3D_NS::ISceneNode* child) { auto entity = child->GetEntity(); if (auto nameHandle = nameComponentManager_->Read(entity)) { auto name = BASE_NS::string_view(nameHandle->name.data(), nameHandle->name.size()); if (name.compare(0, MULTI_MESH_CHILD_PREFIX_LEN, MULTI_MESH_CHILD_PREFIX) == 0) { return true; } } return false; } CORE_NS::Entity SceneHolder::FindCachedRelatedEntity(const CORE_NS::Entity& entity) { if (!(scene_ && nodeSystem_ && CORE_NS::EntityUtil::IsValid(entity))) { return {}; } CORE_NS::Entity cachedEntity {}; if (const auto node = nodeSystem_->GetNode(entity)) { if (const auto idx = scene_->GetSubCollectionIndex(node->GetName()); idx != -1) { cachedEntity = scene_->GetSubCollection(idx)->GetEntity("/"); } } return cachedEntity; } // Set mesh to multi-mesh void SceneHolder::SetMeshMultimeshArray(CORE_NS::Entity target, CORE_NS::Entity mesh) { if (ecs_) { if (auto data = renderMeshComponentManager_->Write(target)) { data->mesh = mesh; } if (const CORE3D_NS::ISceneNode* parent = nodeSystem_->GetNode(target)) { MeshComponent givenData; if (auto givenMeshData = meshComponentManager_->Read(mesh)) { givenData = *givenMeshData; } for (auto& child : parent->GetChildren()) { auto entity = child->GetEntity(); if (!IsMultiMeshChild(child)) { continue; } if (auto data = renderMeshComponentManager_->Read(entity)) { auto mesh = data->mesh; if (auto meshData = meshComponentManager_->Write(mesh)) { *meshData = givenData; } } if (auto data = renderMeshComponentManager_->Write(entity)) { data->renderMeshBatch = target; } } } } } void SetAll(CORE_NS::Entity target, CORE_NS::Entity material, BASE_NS::vector<CORE_NS::Entity>& ret, CORE3D_NS::IMeshComponentManager* meshComponentManager) { if (EntityUtil::IsValid(target)) { if (auto handle = meshComponentManager->Write(target)) { for (auto&& submesh : handle->submeshes) { ret.push_back(submesh.material); submesh.material = material; } } } } void ResetAll(CORE_NS::Entity target, BASE_NS::vector<CORE_NS::Entity>& in, CORE3D_NS::IMeshComponentManager* meshComponentManager) { if (EntityUtil::IsValid(target)) { if (auto handle = meshComponentManager->Write(target)) { for (auto&& submesh : handle->submeshes) { if (in.size() > 0) { submesh.material = in.front(); in.erase(in.begin()); } } } } } // Set override material to multi-mesh BASE_NS::vector<CORE_NS::Entity> SceneHolder::SetOverrideMaterialMultimeshArray( CORE_NS::Entity target, CORE_NS::Entity material) { BASE_NS::vector<CORE_NS::Entity> ret; if (ecs_) { // if we contain render mesh handle, use it to set material if (auto data = renderMeshComponentManager_->Read(target)) { auto mesh = data->mesh; SetAll(mesh, material, ret, meshComponentManager_); } // if our child contain meshes, update material on those if (const CORE3D_NS::ISceneNode* parent = nodeSystem_->GetNode(target)) { for (auto& child : parent->GetChildren()) { if (IsMultiMeshChild(child)) { auto entity = child->GetEntity(); if (auto data = renderMeshComponentManager_->Read(entity)) { auto mesh = data->mesh; SetAll(mesh, material, ret, meshComponentManager_); } } } } } return ret; } // reset override material from multi-mesh void SceneHolder::ResetOverrideMaterialMultimeshArray(CORE_NS::Entity target, BASE_NS::vector<CORE_NS::Entity>& in) { if (ecs_) { BASE_NS::vector<CORE_NS::Entity> ret; // if we contain render mesh handle, use it to set material if (auto data = renderMeshComponentManager_->Read(target)) { auto mesh = data->mesh; ResetAll(mesh, in, meshComponentManager_); } // if our child contain meshes, update material on those if (const CORE3D_NS::ISceneNode* parent = nodeSystem_->GetNode(target)) { for (auto& child : parent->GetChildren()) { if (IsMultiMeshChild(child)) { auto entity = child->GetEntity(); if (auto data = renderMeshComponentManager_->Read(entity)) { auto mesh = data->mesh; ResetAll(mesh, in, meshComponentManager_); } } } } } in.clear(); } // Set instance count to multi-mesh void SceneHolder::SetInstanceCountMultimeshArray(CORE_NS::Entity target, size_t count) { if (ecs_) { if (auto parent = nodeSystem_->GetNode(target)) { // this is kind of sub-optimization. If we have instances available, the mesh component is stored only // in instances CORE_NS::Entity firstBorn; auto children = parent->GetChildren(); for (auto& child : children) { if (IsMultiMeshChild(child)) { firstBorn = child->GetEntity(); break; } } CORE3D_NS::MeshComponent meshData; // when the instance count goes back to zero, we need to restore the data on parent if (count == 0) { if (CORE_NS::EntityUtil::IsValid(firstBorn)) { renderMeshComponentManager_->Create(target); if (auto renderMeshData = renderMeshComponentManager_->Write(target)) { if (auto instancingData = renderMeshComponentManager_->Read(firstBorn)) { *renderMeshData = *instancingData; } } } // now, when we move the templated item back to root, it will become visible by default } else { // if we have mesh, set things up using mesh if (auto data = meshComponentManager_->Read(target)) { meshData = *data; } else if (auto instanceData = renderMeshComponentManager_->Read(firstBorn)) { // otherwise we prefer the instanced data if (auto data = meshComponentManager_->Read(instanceData->mesh)) { // use instance data meshData = *data; // and switch target target = firstBorn; } } else if (auto renderMeshData = renderMeshComponentManager_->Read(target)) { // and fallback to existing render mesh data if it exists if (auto data = meshComponentManager_->Read(renderMeshData->mesh)) { meshData = *data; } } } size_t mmcount = 0; for (auto& child : children) { if (IsMultiMeshChild(child)) { if (mmcount == count) { parent->RemoveChild(*child); } else { mmcount++; } } } BASE_NS::vector<CORE_NS::Entity> clones; while (mmcount < count) { clones.push_back(ecs_->CloneEntity(target)); mmcount++; } for (auto clone : clones) { // set up node system auto node = nodeSystem_->GetNode(clone); node->SetParent(*parent); node->SetEnabled(true); nameComponentManager_->Create(clone); // set up name so we can identify cloned instances afterwards if (auto nameHandle = nameComponentManager_->Write(clone)) { BASE_NS::string postFixed(MULTI_MESH_CHILD_PREFIX.data(), MULTI_MESH_CHILD_PREFIX.size()); postFixed.append(BASE_NS::to_string(mmcount)); nameHandle->name = postFixed; } // and finally mesh specifics if (auto rmcHandle = renderMeshComponentManager_->Write(clone)) { // during the first clone, switch from template to first instance if (mmcount == 0) { renderMeshComponentManager_->Destroy(target); target = clone; } // Set batch rmcHandle->renderMeshBatch = target; // create new entity for mesh const CORE_NS::Entity entity = ecs_->GetEntityManager().Create(); meshComponentManager_->Create(entity); // copy the data of the original *meshComponentManager_->Write(entity) = meshData; // and set the mesh to a new instance rmcHandle->mesh = entity; } mmcount++; } // Set up a batch auto renderMeshBatchComponentManager = CORE_NS::GetManager<CORE3D_NS::IRenderMeshBatchComponentManager>(*ecs_); if (!renderMeshBatchComponentManager->HasComponent(firstBorn)) { renderMeshBatchComponentManager->Create(firstBorn); } if (auto batchHandle = renderMeshBatchComponentManager->Write(firstBorn)) { batchHandle->batchType = CORE3D_NS::RenderMeshBatchComponent::BatchType::GPU_INSTANCING; } } } } // Set visible count to multi-mesh void SceneHolder::SetVisibleCountMultimeshArray(CORE_NS::Entity target, size_t count) { if (ecs_) { if (const CORE3D_NS::ISceneNode* parent = nodeSystem_->GetNode(target)) { size_t ix = 0; for (auto& child : parent->GetChildren()) { if (IsMultiMeshChild(child)) { child->SetEnabled(ix < count); ix++; } } } } } // Set custom data to multi-mesh index void SceneHolder::SetCustomData(CORE_NS::Entity target, size_t index, const BASE_NS::Math::Vec4& data) { if (ecs_) { if (const CORE3D_NS::ISceneNode* parent = nodeSystem_->GetNode(target)) { size_t ix = 0; for (auto& child : parent->GetChildren()) { if (IsMultiMeshChild(child)) { if (ix == index) { auto entity = child->GetEntity(); if (auto handle = renderMeshComponentManager_->Write(entity)) { *static_cast<float*>(static_cast<void*>(&handle->customData[0].x)) = data.x; *static_cast<float*>(static_cast<void*>(&handle->customData[0].y)) = data.y; *static_cast<float*>(static_cast<void*>(&handle->customData[0].z)) = data.z; *static_cast<float*>(static_cast<void*>(&handle->customData[0].w)) = data.w; } return; } ix++; } } } } } // Set transformation to multi-mesh index void SceneHolder::SetTransformation(CORE_NS::Entity target, size_t index, const BASE_NS::Math::Mat4X4& transform) { if (ecs_) { if (const CORE3D_NS::ISceneNode* parent = nodeSystem_->GetNode(target)) { size_t ix = 0; for (auto& child : parent->GetChildren()) { if (IsMultiMeshChild(child)) { if (ix == index) { auto entity = child->GetEntity(); if (auto handle = transformComponentManager_->Write(entity)) { BASE_NS::Math::Quat rotation; BASE_NS::Math::Vec3 scale; BASE_NS::Math::Vec3 position; BASE_NS::Math::Vec3 skew; BASE_NS::Math::Vec4 persp; if (BASE_NS::Math::Decompose(transform, scale, rotation, position, skew, persp)) { handle->scale = scale; handle->rotation = rotation; handle->position = position; } return; } } ix++; } } } } } bool SceneHolder::GetWorldMatrixComponentAABB( SCENE_NS::IPickingResult::Ptr ret, CORE_NS::Entity entity, bool isRecursive) { if (picking_ && ecs_) { auto result = picking_->GetWorldMatrixComponentAABB(entity, isRecursive, *ecs_); if (auto writable = interface_cast<SCENE_NS::IPendingRequestData<BASE_NS::Math::Vec3>>(ret)) { writable->Add(result.minAABB); writable->Add(result.maxAABB); return true; } } return false; } bool SceneHolder::GetTransformComponentAABB(SCENE_NS::IPickingResult::Ptr ret, CORE_NS::Entity entity, bool isRecursive) { if (picking_ && ecs_) { auto result = picking_->GetTransformComponentAABB(entity, isRecursive, *ecs_); if (auto writable = interface_cast<SCENE_NS::IPendingRequestData<BASE_NS::Math::Vec3>>(ret)) { writable->Add(result.minAABB); writable->Add(result.maxAABB); return true; } } return false; } bool SceneHolder::GetWorldAABB(SCENE_NS::IPickingResult::Ptr ret, const BASE_NS::Math::Mat4X4& world, const BASE_NS::Math::Vec3& aabbMin, const BASE_NS::Math::Vec3& aabbMax) { if (picking_ && ecs_) { auto result = picking_->GetWorldAABB(world, aabbMin, aabbMax); if (auto writable = interface_cast<SCENE_NS::IPendingRequestData<BASE_NS::Math::Vec3>>(ret)) { writable->Add(result.minAABB); writable->Add(result.maxAABB); return true; } } return false; } bool ConvertToToolkit(const BASE_NS::vector<CORE3D_NS::RayCastResult>& result, SCENE_NS::IRayCastResult::Ptr ret) { if (auto writable = interface_cast<SCENE_NS::IPendingRequestData<SCENE_NS::NodeDistance>>(ret)) { for (auto& value : result) { BASE_NS::string path; if (ResolveNodeFullPath(value.node, path)) { writable->Add({ {}, value.distance }); writable->MetaData().push_back(path); } } return true; } return false; } bool SceneHolder::RayCast( SCENE_NS::IRayCastResult::Ptr ret, const BASE_NS::Math::Vec3& start, const BASE_NS::Math::Vec3& direction) { if (picking_ && ecs_) { auto result = picking_->RayCast(*ecs_, start, direction); return ConvertToToolkit(result, ret); } return false; } bool SceneHolder::RayCast(SCENE_NS::IRayCastResult::Ptr ret, const BASE_NS::Math::Vec3& start, const BASE_NS::Math::Vec3& direction, uint64_t layerMask) { if (picking_ && ecs_) { auto result = picking_->RayCast(*ecs_, start, direction, layerMask); return ConvertToToolkit(result, ret); } return false; } bool SceneHolder::ScreenToWorld( SCENE_NS::IPickingResult::Ptr ret, CORE_NS::Entity camera, BASE_NS::Math::Vec3 screenCoordinate) { if (picking_ && ecs_) { auto result = picking_->ScreenToWorld(*ecs_, camera, screenCoordinate); if (auto writable = interface_cast<SCENE_NS::IPendingRequestData<BASE_NS::Math::Vec3>>(ret)) { writable->Add(result); return true; } } return false; } bool SceneHolder::WorldToScreen( SCENE_NS::IPickingResult::Ptr ret, CORE_NS::Entity camera, BASE_NS::Math::Vec3 worldCoordinate) { if (picking_ && ecs_) { auto result = picking_->WorldToScreen(*ecs_, camera, worldCoordinate); if (auto writable = interface_cast<SCENE_NS::IPendingRequestData<BASE_NS::Math::Vec3>>(ret)) { writable->Add(result); return true; } } return false; } bool SceneHolder::RayCastFromCamera( SCENE_NS::IRayCastResult::Ptr ret, CORE_NS::Entity camera, const BASE_NS::Math::Vec2& screenPos) { if (picking_ && ecs_) { auto result = picking_->RayCastFromCamera(*ecs_, camera, screenPos); return ConvertToToolkit(result, ret); } return false; } bool SceneHolder::RayCastFromCamera( SCENE_NS::IRayCastResult::Ptr ret, CORE_NS::Entity camera, const BASE_NS::Math::Vec2& screenPos, uint64_t layerMask) { if (picking_ && ecs_) { auto result = picking_->RayCastFromCamera(*ecs_, camera, screenPos, layerMask); return ConvertToToolkit(result, ret); } return false; } bool SceneHolder::RayFromCamera( SCENE_NS::IPickingResult::Ptr ret, CORE_NS::Entity camera, BASE_NS::Math::Vec2 screenCoordinate) { if (auto cameraComponent = cameraComponentManager_->Read(camera)) { if (auto worldMatrixManager = GetManager<CORE3D_NS::IWorldMatrixComponentManager>(*ecs_)) { if (auto cameraWorldMatrixComponent = worldMatrixManager->Read(camera)) { if (cameraComponent->projection == CORE3D_NS::CameraComponent::Projection::ORTHOGRAPHIC) { const auto worldPos = picking_->ScreenToWorld( *ecs_, camera, BASE_NS::Math::Vec3(screenCoordinate.x, screenCoordinate.y, 0.0f)); const auto direction = cameraWorldMatrixComponent->matrix * BASE_NS::Math::Vec4(0.0f, 0.0f, -1.0f, 0.0f); if (auto writable = interface_cast<SCENE_NS::IPendingRequestData<BASE_NS::Math::Vec3>>(ret)) { writable->Add(worldPos); writable->Add(direction); return true; } } else { // Ray origin is the camera world position. const auto rayOrigin = BASE_NS::Math::Vec3(cameraWorldMatrixComponent->matrix.w); const auto targetPos = picking_->ScreenToWorld( *ecs_, camera, BASE_NS::Math::Vec3(screenCoordinate.x, screenCoordinate.y, 1.0f)); const auto direction = BASE_NS::Math::Normalize(targetPos - rayOrigin); if (auto writable = interface_cast<SCENE_NS::IPendingRequestData<BASE_NS::Math::Vec3>>(ret)) { writable->Add(rayOrigin); writable->Add(direction); return true; } } } } } return false; }