From 0e0f5b7996bc2fa6ee9339b67344d5dc6d47a378 Mon Sep 17 00:00:00 2001 From: seungho baek Date: Wed, 24 May 2023 18:35:28 +0900 Subject: [PATCH] [Tizen] Add Light for Scene3D Change-Id: If21ef89a2254dd835c80449da575d301124f8fcc Signed-off-by: seungho baek --- automated-tests/src/dali-scene3d/CMakeLists.txt | 1 + .../src/dali-scene3d/utc-Dali-Light.cpp | 591 +++++++++++++++++++++ ...age-based-light-observer.h => light-observer.h} | 30 +- .../internal/controls/model/model-impl.cpp | 84 ++- dali-scene3d/internal/controls/model/model-impl.h | 24 +- .../controls/scene-view/scene-view-impl.cpp | 147 ++++- .../internal/controls/scene-view/scene-view-impl.h | 53 +- dali-scene3d/internal/file.list | 1 + .../shaders/default-physically-based-shader.frag | 43 ++ dali-scene3d/internal/light/light-impl.cpp | 241 +++++++++ dali-scene3d/internal/light/light-impl.h | 239 +++++++++ .../internal/model-components/material-impl.cpp | 16 + .../internal/model-components/model-node-impl.cpp | 39 ++ .../internal/model-components/model-node-impl.h | 7 + .../model-components/model-primitive-impl.cpp | 61 +++ .../model-components/model-primitive-impl.h | 26 +- .../public-api/controls/scene-view/scene-view.cpp | 5 + .../public-api/controls/scene-view/scene-view.h | 8 + dali-scene3d/public-api/file.list | 1 + dali-scene3d/public-api/light/light.cpp | 102 ++++ dali-scene3d/public-api/light/light.h | 187 +++++++ dali-scene3d/public-api/loader/node-definition.cpp | 21 +- 22 files changed, 1904 insertions(+), 23 deletions(-) create mode 100644 automated-tests/src/dali-scene3d/utc-Dali-Light.cpp rename dali-scene3d/internal/common/{image-based-light-observer.h => light-observer.h} (72%) create mode 100644 dali-scene3d/internal/light/light-impl.cpp create mode 100644 dali-scene3d/internal/light/light-impl.h create mode 100644 dali-scene3d/public-api/light/light.cpp create mode 100644 dali-scene3d/public-api/light/light.h diff --git a/automated-tests/src/dali-scene3d/CMakeLists.txt b/automated-tests/src/dali-scene3d/CMakeLists.txt index 41a55c0..d892b4d 100755 --- a/automated-tests/src/dali-scene3d/CMakeLists.txt +++ b/automated-tests/src/dali-scene3d/CMakeLists.txt @@ -32,6 +32,7 @@ SET(TC_SOURCES utc-Dali-StringCallback.cpp utc-Dali-Utils.cpp utc-Dali-ViewProjection.cpp + utc-Dali-Light.cpp ) # List of test harness files (Won't get parsed for test cases) diff --git a/automated-tests/src/dali-scene3d/utc-Dali-Light.cpp b/automated-tests/src/dali-scene3d/utc-Dali-Light.cpp new file mode 100644 index 0000000..2ac5bcb --- /dev/null +++ b/automated-tests/src/dali-scene3d/utc-Dali-Light.cpp @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2023 Samsung Electronics 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace Dali; +using namespace Dali::Toolkit; + +void light_startup(void) +{ + test_return_value = TET_UNDEF; +} + +void light_cleanup(void) +{ + test_return_value = TET_PASS; +} + +namespace +{ +const char* TEST_GLTF_FILE_NAME = TEST_RESOURCE_DIR "/BoxAnimated.gltf"; +} // namespace + +// Negative test case for a method +int UtcDaliLightUninitialized(void) +{ + ToolkitTestApplication application; + tet_infoline("UtcDaliLightUninitialized"); + + Scene3D::Light light; + + try + { + // New() must be called to create a Model or it wont be valid. + Actor a = Actor::New(); + light.Add(a); + DALI_TEST_CHECK(false); + } + catch(Dali::DaliException& e) + { + // Tests that a negative test of an assertion succeeds + DALI_TEST_PRINT_ASSERT(e); + DALI_TEST_CHECK(!light); + } + END_TEST; +} + +// Positive test case for a method +int UtcDaliLightNew(void) +{ + ToolkitTestApplication application; + tet_infoline("UtcDaliLightNew"); + + Scene3D::Light light = Scene3D::Light::New(); + DALI_TEST_CHECK(light); + END_TEST; +} + +// Positive test case for a method +int UtcDaliLightDownCast(void) +{ + ToolkitTestApplication application; + tet_infoline("UtcDaliLightDownCast"); + + Scene3D::Light light = Scene3D::Light::New(); + BaseHandle handle(light); + + Scene3D::Light light2 = Scene3D::Light::DownCast(handle); + DALI_TEST_CHECK(light); + DALI_TEST_CHECK(light2); + DALI_TEST_CHECK(light == light2); + END_TEST; +} + +// Positive test case for a method +int UtcDaliLightCopyAndAssignment(void) +{ + ToolkitTestApplication application; + + Scene3D::Light light = Scene3D::Light::New(); + DALI_TEST_CHECK(light); + + Scene3D::Light copy(light); + DALI_TEST_CHECK(light == copy); + + Scene3D::Light assign; + DALI_TEST_CHECK(!assign); + + assign = copy; + DALI_TEST_CHECK(assign == light); + + END_TEST; +} + +int UtcDaliLightMoveConstructor(void) +{ + ToolkitTestApplication application; + + Scene3D::Light light = Scene3D::Light::New(); + DALI_TEST_EQUALS(1, light.GetBaseObject().ReferenceCount(), TEST_LOCATION); + light.SetProperty(Actor::Property::SENSITIVE, false); + DALI_TEST_CHECK(false == light.GetProperty(Actor::Property::SENSITIVE)); + + Scene3D::Light moved = std::move(light); + DALI_TEST_CHECK(moved); + DALI_TEST_EQUALS(1, moved.GetBaseObject().ReferenceCount(), TEST_LOCATION); + DALI_TEST_CHECK(false == moved.GetProperty(Actor::Property::SENSITIVE)); + DALI_TEST_CHECK(!light); + + END_TEST; +} + +int UtcDaliLightMoveAssignment(void) +{ + ToolkitTestApplication application; + + Scene3D::Light light = Scene3D::Light::New(); + DALI_TEST_EQUALS(1, light.GetBaseObject().ReferenceCount(), TEST_LOCATION); + light.SetProperty(Actor::Property::SENSITIVE, false); + DALI_TEST_CHECK(false == light.GetProperty(Actor::Property::SENSITIVE)); + + Scene3D::Light moved; + moved = std::move(light); + DALI_TEST_CHECK(moved); + DALI_TEST_EQUALS(1, moved.GetBaseObject().ReferenceCount(), TEST_LOCATION); + DALI_TEST_CHECK(false == moved.GetProperty(Actor::Property::SENSITIVE)); + DALI_TEST_CHECK(!light); + + END_TEST; +} + +// For TC coverage +int UtcDaliLightSize(void) +{ + ToolkitTestApplication application; + + Scene3D::Light light = Scene3D::Light::New(); + application.GetScene().Add(light); + + application.SendNotification(); + application.Render(); + + light.SetProperty(Dali::Actor::Property::SIZE, Vector3::ONE); + DALI_TEST_EQUALS(Vector3::ONE, light.GetProperty(Dali::Actor::Property::SIZE), 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(Vector3::ZERO, light.GetNaturalSize(), 0.01f, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + light.SetProperty(Dali::Actor::Property::WIDTH_RESIZE_POLICY, ResizePolicy::FIXED); + DALI_TEST_EQUALS(ResizePolicy::FIXED, light.GetProperty(Dali::Actor::Property::WIDTH_RESIZE_POLICY), TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + float widthForHeight = light.GetWidthForHeight(light.GetProperty(Dali::Actor::Property::SIZE_HEIGHT)); + float heightForWidth = light.GetHeightForWidth(light.GetProperty(Dali::Actor::Property::SIZE_WIDTH)); + DALI_TEST_EQUALS(0.0f, widthForHeight, 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(0.0f, heightForWidth, 0.01f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliLightOnScene01(void) +{ + ToolkitTestApplication application; + + Scene3D::Light light = Scene3D::Light::New(); + application.GetScene().Add(light); + + application.SendNotification(); + application.Render(); + + // Light is added on layer when on scene + DALI_TEST_EQUALS(true, light.GetProperty(Dali::Actor::Property::CONNECTED_TO_SCENE), TEST_LOCATION); + + application.GetScene().Remove(light); + + application.SendNotification(); + application.Render(); + + // Light is removed from layer when on scene + DALI_TEST_EQUALS(false, light.GetProperty(Dali::Actor::Property::CONNECTED_TO_SCENE), TEST_LOCATION); + + END_TEST; +} + +// Add a light on SceneView directly +int UtcDaliLightAdd01(void) +{ + ToolkitTestApplication application; + Scene3D::SceneView sceneView = Scene3D::SceneView::New(); + sceneView.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + sceneView.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + sceneView.SetProperty(Dali::Actor::Property::WIDTH_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + sceneView.SetProperty(Dali::Actor::Property::HEIGHT_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + application.GetScene().Add(sceneView); + + Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME); + sceneView.Add(model); + + application.SendNotification(); + application.Render(); + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + application.SendNotification(); + application.Render(); + + Scene3D::Light light = Scene3D::Light::New(); + light.SetProperty(Dali::Actor::Property::COLOR, Color::BLUE); + Dali::DevelActor::LookAt(light, Vector3(1.0f, 0.0f, 0.0f)); + sceneView.Add(light); + + application.SendNotification(); + application.Render(); + + Renderer renderer = model.FindChildByName("node2").GetRendererAt(0u); + DALI_TEST_CHECK(renderer); + + DALI_TEST_EQUALS(1u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + auto countPropertyIndex = renderer.GetPropertyIndex("uLightCount"); + DALI_TEST_CHECK(countPropertyIndex != DALI_KEY_INVALID); + DALI_TEST_EQUALS(1, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + auto colorPropertyIndex = renderer.GetPropertyIndex("uLightColor[0]"); + DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, 1.0f), renderer.GetCurrentProperty(colorPropertyIndex), 0.01f, TEST_LOCATION); + auto directionPropertyIndex = renderer.GetPropertyIndex("uLightDirection[0]"); + DALI_TEST_EQUALS(Vector3(1.0f, 0.0f, 0.0f), renderer.GetCurrentProperty(directionPropertyIndex), 0.01f, TEST_LOCATION); + + light.Enable(false); + + DALI_TEST_EQUALS(0u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + DALI_TEST_EQUALS(0, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + + END_TEST; +} + +// Add a light on an Actor that is child of SceneView. +int UtcDaliLightAdd02(void) +{ + ToolkitTestApplication application; + + Scene3D::SceneView sceneView = Scene3D::SceneView::New(); + sceneView.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + sceneView.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + sceneView.SetProperty(Dali::Actor::Property::WIDTH_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + sceneView.SetProperty(Dali::Actor::Property::HEIGHT_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + application.GetScene().Add(sceneView); + + Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME); + sceneView.Add(model); + + application.SendNotification(); + application.Render(); + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + application.SendNotification(); + application.Render(); + + Actor actor = Actor::New(); + sceneView.Add(actor); + + Scene3D::Light light = Scene3D::Light::New(); + light.SetProperty(Dali::Actor::Property::COLOR, Color::BLUE); + Dali::DevelActor::LookAt(light, Vector3(1.0f, 0.0f, 0.0f)); + actor.Add(light); + + application.SendNotification(); + application.Render(); + + Renderer renderer = model.FindChildByName("node2").GetRendererAt(0u); + DALI_TEST_CHECK(renderer); + + DALI_TEST_EQUALS(1u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + auto countPropertyIndex = renderer.GetPropertyIndex("uLightCount"); + DALI_TEST_CHECK(countPropertyIndex != DALI_KEY_INVALID); + DALI_TEST_EQUALS(1, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + auto colorPropertyIndex = renderer.GetPropertyIndex("uLightColor[0]"); + DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, 1.0f), renderer.GetCurrentProperty(colorPropertyIndex), 0.01f, TEST_LOCATION); + auto directionPropertyIndex = renderer.GetPropertyIndex("uLightDirection[0]"); + DALI_TEST_EQUALS(Vector3(1.0f, 0.0f, 0.0f), renderer.GetCurrentProperty(directionPropertyIndex), 0.01f, TEST_LOCATION); + + light.Enable(false); + + DALI_TEST_EQUALS(0u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + DALI_TEST_EQUALS(0, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + + END_TEST; +} + +// Enable a light after it is added on SceneView. +int UtcDaliLightAdd03(void) +{ + ToolkitTestApplication application; + + Scene3D::SceneView sceneView = Scene3D::SceneView::New(); + sceneView.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + sceneView.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + sceneView.SetProperty(Dali::Actor::Property::WIDTH_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + sceneView.SetProperty(Dali::Actor::Property::HEIGHT_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + application.GetScene().Add(sceneView); + + Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME); + sceneView.Add(model); + + application.SendNotification(); + application.Render(); + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + application.SendNotification(); + application.Render(); + + Scene3D::Light light = Scene3D::Light::New(); + light.SetProperty(Dali::Actor::Property::COLOR, Color::BLUE); + Dali::DevelActor::LookAt(light, Vector3(1.0f, 0.0f, 0.0f)); + light.Enable(false); + sceneView.Add(light); + + application.SendNotification(); + application.Render(); + + Renderer renderer = model.FindChildByName("node2").GetRendererAt(0u); + DALI_TEST_CHECK(renderer); + + DALI_TEST_EQUALS(0u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + auto countPropertyIndex = renderer.GetPropertyIndex("uLightCount"); + DALI_TEST_CHECK(countPropertyIndex != DALI_KEY_INVALID); + DALI_TEST_EQUALS(0, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + + light.Enable(true); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(1u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + DALI_TEST_EQUALS(1, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + auto colorPropertyIndex = renderer.GetPropertyIndex("uLightColor[0]"); + DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, 1.0f), renderer.GetCurrentProperty(colorPropertyIndex), 0.01f, TEST_LOCATION); + auto directionPropertyIndex = renderer.GetPropertyIndex("uLightDirection[0]"); + DALI_TEST_EQUALS(Vector3(1.0f, 0.0f, 0.0f), renderer.GetCurrentProperty(directionPropertyIndex), 0.01f, TEST_LOCATION); + + light.Enable(false); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(0u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + DALI_TEST_EQUALS(0, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + + END_TEST; +} + +int UtcDaliLightAdd04(void) +{ + ToolkitTestApplication application; + + Scene3D::SceneView sceneView = Scene3D::SceneView::New(); + sceneView.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + sceneView.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + sceneView.SetProperty(Dali::Actor::Property::WIDTH_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + sceneView.SetProperty(Dali::Actor::Property::HEIGHT_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + application.GetScene().Add(sceneView); + + Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME); + sceneView.Add(model); + + application.SendNotification(); + application.Render(); + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + application.SendNotification(); + application.Render(); + + Scene3D::Light light1 = Scene3D::Light::New(); + light1.SetProperty(Dali::Actor::Property::COLOR, Color::BLUE); + Dali::DevelActor::LookAt(light1, Vector3(1.0f, 0.0f, 0.0f)); + sceneView.Add(light1); + + Scene3D::Light light2 = Scene3D::Light::New(); + light2.SetProperty(Dali::Actor::Property::COLOR, Color::RED); + Dali::DevelActor::LookAt(light2, Vector3(0.0f, 0.0f, -1.0f)); + sceneView.Add(light2); + + application.SendNotification(); + application.Render(); + + Renderer renderer = model.FindChildByName("node2").GetRendererAt(0u); + DALI_TEST_CHECK(renderer); + + DALI_TEST_EQUALS(2u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + auto countPropertyIndex = renderer.GetPropertyIndex("uLightCount"); + DALI_TEST_CHECK(countPropertyIndex != DALI_KEY_INVALID); + DALI_TEST_EQUALS(2, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + auto colorPropertyIndex1 = renderer.GetPropertyIndex("uLightColor[0]"); + DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, 1.0f), renderer.GetCurrentProperty(colorPropertyIndex1), 0.01f, TEST_LOCATION); + auto directionPropertyIndex1 = renderer.GetPropertyIndex("uLightDirection[0]"); + DALI_TEST_EQUALS(Vector3(1.0f, 0.0f, 0.0f), renderer.GetCurrentProperty(directionPropertyIndex1), 0.01f, TEST_LOCATION); + auto colorPropertyIndex2 = renderer.GetPropertyIndex("uLightColor[1]"); + DALI_TEST_EQUALS(Vector3(1.0f, 0.0f, 0.0f), renderer.GetCurrentProperty(colorPropertyIndex2), 0.01f, TEST_LOCATION); + auto directionPropertyIndex2 = renderer.GetPropertyIndex("uLightDirection[1]"); + DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, -1.0f), renderer.GetCurrentProperty(directionPropertyIndex2), 0.01f, TEST_LOCATION); + + light1.Enable(false); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(1u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + DALI_TEST_EQUALS(1, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + + // After light1 is disable, shader uniforms of lights are reordered. + DALI_TEST_EQUALS(Vector3(1.0f, 0.0f, 0.0f), renderer.GetCurrentProperty(colorPropertyIndex1), 0.01f, TEST_LOCATION); + DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, -1.0f), renderer.GetCurrentProperty(directionPropertyIndex1), 0.01f, TEST_LOCATION); + + END_TEST; +} + +// Check unactivated light in SceneView becomes activated when a light become disabled. +int UtcDaliLightAdd05(void) +{ + ToolkitTestApplication application; + + Scene3D::SceneView sceneView = Scene3D::SceneView::New(); + sceneView.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + sceneView.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + sceneView.SetProperty(Dali::Actor::Property::WIDTH_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + sceneView.SetProperty(Dali::Actor::Property::HEIGHT_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + application.GetScene().Add(sceneView); + + Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME); + sceneView.Add(model); + + application.SendNotification(); + application.Render(); + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + application.SendNotification(); + application.Render(); + + uint32_t maxLightCount = Scene3D::Light::GetMaximumEnabledLightCount(); + std::vector lightList; + for(uint32_t i = 0; i < maxLightCount; ++i) + { + Scene3D::Light light = Scene3D::Light::New(); + light.SetProperty(Dali::Actor::Property::COLOR, Color::BLUE); + Dali::DevelActor::LookAt(light, Vector3(1.0f, 0.0f, 0.0f)); + sceneView.Add(light); + lightList.push_back(light); + } + + Scene3D::Light light2 = Scene3D::Light::New(); + light2.SetProperty(Dali::Actor::Property::COLOR, Color::RED); + Dali::DevelActor::LookAt(light2, Vector3(0.0f, 0.0f, -1.0f)); + sceneView.Add(light2); + lightList.push_back(light2); + + application.SendNotification(); + application.Render(); + + Renderer renderer = model.FindChildByName("node2").GetRendererAt(0u); + DALI_TEST_CHECK(renderer); + + std::vector enableAnswers; + enableAnswers.assign(maxLightCount, 1); + + DALI_TEST_EQUALS(maxLightCount, sceneView.GetActivatedLightCount(), TEST_LOCATION); + auto countPropertyIndex = renderer.GetPropertyIndex("uLightCount"); + DALI_TEST_CHECK(countPropertyIndex != DALI_KEY_INVALID); + DALI_TEST_EQUALS(static_cast(maxLightCount), renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + std::string colorStringKey = std::string("uLightColor[") + std::to_string(i) + "]"; + auto colorPropertyIndex = renderer.GetPropertyIndex(colorStringKey); + DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, 1.0f), renderer.GetCurrentProperty(colorPropertyIndex), 0.01f, TEST_LOCATION); + + std::string directionStringKey = std::string("uLightDirection[") + std::to_string(i) + "]"; + auto directionPropertyIndex = renderer.GetPropertyIndex(directionStringKey); + DALI_TEST_EQUALS(Vector3(1.0f, 0.0f, 0.0f), renderer.GetCurrentProperty(directionPropertyIndex), 0.01f, TEST_LOCATION); + } + + lightList[2].Enable(false); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(maxLightCount, sceneView.GetActivatedLightCount(), TEST_LOCATION); + DALI_TEST_EQUALS(static_cast(maxLightCount), renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + Vector3 color, direction; + if(i == 2) + { + color = Vector3(1.0f, 0.0f, 0.0f); + direction = Vector3(0.0f, 0.0f, -1.0f); + } + else + { + color = Vector3(0.0f, 0.0f, 1.0f); + direction = Vector3(1.0f, 0.0f, 0.0f); + } + std::string colorStringKey = std::string("uLightColor[") + std::to_string(i) + "]"; + auto colorPropertyIndex = renderer.GetPropertyIndex(colorStringKey); + DALI_TEST_EQUALS(color, renderer.GetCurrentProperty(colorPropertyIndex), 0.01f, TEST_LOCATION); + + std::string directionStringKey = std::string("uLightDirection[") + std::to_string(i) + "]"; + auto directionPropertyIndex = renderer.GetPropertyIndex(directionStringKey); + DALI_TEST_EQUALS(direction, renderer.GetCurrentProperty(directionPropertyIndex), 0.01f, TEST_LOCATION); + } + + for(uint32_t i = 0; i < lightList.size(); ++i) + { + if(i == 2) + { + DALI_TEST_EQUALS(false, lightList[i].IsEnabled(), TEST_LOCATION); + } + else + { + DALI_TEST_EQUALS(true, lightList[i].IsEnabled(), TEST_LOCATION); + } + } + + END_TEST; +} + +int UtcDaliLightModelAddAndRemove(void) +{ + ToolkitTestApplication application; + + Scene3D::SceneView sceneView = Scene3D::SceneView::New(); + sceneView.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER); + sceneView.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + sceneView.SetProperty(Dali::Actor::Property::WIDTH_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + sceneView.SetProperty(Dali::Actor::Property::HEIGHT_RESIZE_POLICY, ResizePolicy::FILL_TO_PARENT); + application.GetScene().Add(sceneView); + + Scene3D::Light light = Scene3D::Light::New(); + light.SetProperty(Dali::Actor::Property::COLOR, Color::BLUE); + Dali::DevelActor::LookAt(light, Vector3(1.0f, 0.0f, 0.0f)); + sceneView.Add(light); + + application.SendNotification(); + application.Render(); + + Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME); + sceneView.Add(model); + + application.SendNotification(); + application.Render(); + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + application.SendNotification(); + application.Render(); + + Renderer renderer = model.FindChildByName("node2").GetRendererAt(0u); + DALI_TEST_CHECK(renderer); + + DALI_TEST_EQUALS(1u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + auto countPropertyIndex = renderer.GetPropertyIndex("uLightCount"); + DALI_TEST_CHECK(countPropertyIndex != DALI_KEY_INVALID); + DALI_TEST_EQUALS(1, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + + auto colorPropertyIndex = renderer.GetPropertyIndex("uLightColor[0]"); + DALI_TEST_EQUALS(Vector3(0.0f, 0.0f, 1.0f), renderer.GetCurrentProperty(colorPropertyIndex), 0.01f, TEST_LOCATION); + auto directionPropertyIndex = renderer.GetPropertyIndex("uLightDirection[0]"); + DALI_TEST_EQUALS(Vector3(1.0f, 0.0f, 0.0f), renderer.GetCurrentProperty(directionPropertyIndex), 0.01f, TEST_LOCATION); + + DALI_TEST_EQUALS(1u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + + model.Unparent(); + + DALI_TEST_EQUALS(1u, sceneView.GetActivatedLightCount(), TEST_LOCATION); + DALI_TEST_EQUALS(0, renderer.GetProperty(countPropertyIndex), TEST_LOCATION); + + END_TEST; +} diff --git a/dali-scene3d/internal/common/image-based-light-observer.h b/dali-scene3d/internal/common/light-observer.h similarity index 72% rename from dali-scene3d/internal/common/image-based-light-observer.h rename to dali-scene3d/internal/common/light-observer.h index f9f1e06..c54c73b 100644 --- a/dali-scene3d/internal/common/image-based-light-observer.h +++ b/dali-scene3d/internal/common/light-observer.h @@ -1,5 +1,5 @@ -#ifndef DALI_SCENE3D_INTERNAL_IMAGE_BASED_LIGHT_OBSERVER_H -#define DALI_SCENE3D_INTERNAL_IMAGE_BASED_LIGHT_OBSERVER_H +#ifndef DALI_SCENE3D_INTERNAL_LIGHT_OBSERVER_H +#define DALI_SCENE3D_INTERNAL_LIGHT_OBSERVER_H /* * Copyright (c) 2022 Samsung Electronics Co., Ltd. @@ -22,24 +22,27 @@ #include #include +// INTERNAL INCLUDES +#include + namespace Dali { namespace Scene3D { namespace Internal { -class ImageBasedLightObserver +class LightObserver { public: /** * @brief Constructor. */ - ImageBasedLightObserver() = default; + LightObserver() = default; /** * @brief Virtual destructor. */ - virtual ~ImageBasedLightObserver() = default; + virtual ~LightObserver() = default; /** * @brief Notifies Image Based Light Textures are changed by parent SceneView. @@ -57,6 +60,21 @@ public: * @param[in] scaleFactor scale factor that controls light source intensity in [0.0f, 1.0f]. */ virtual void NotifyImageBasedLightScaleFactor(float scaleFactor) = 0; + + /** + * @brief Notifies new light is added on SceneView + * + * @param[in] lightIndex Index of added light. + * @param[in] light Added light. + */ + virtual void NotifyLightAdded(uint32_t lightIndex, Scene3D::Light light) = 0; + + /** + * @brief Notifies a light is removed from SceneView + * + * @param[in] lightIndex Index of light to be removed. + */ + virtual void NotifyLightRemoved(uint32_t lightIndex) = 0; }; } // namespace Internal @@ -65,4 +83,4 @@ public: } // namespace Dali -#endif // DALI_SCENE3D_INTERNAL_IMAGE_BASED_LIGHT_OBSERVER_H +#endif // DALI_SCENE3D_INTERNAL_LIGHT_OBSERVER_H diff --git a/dali-scene3d/internal/controls/model/model-impl.cpp b/dali-scene3d/internal/controls/model/model-impl.cpp index 1dbd218..b16d5cd 100644 --- a/dali-scene3d/internal/controls/model/model-impl.cpp +++ b/dali-scene3d/internal/controls/model/model-impl.cpp @@ -33,6 +33,7 @@ // INTERNAL INCLUDES #include #include +#include #include #include #include @@ -171,6 +172,46 @@ void AddModelTreeToAABB(BoundingVolume& AABB, const Dali::Scene3D::Loader::Scene } } +void AddLightRecursively(Scene3D::ModelNode node, Scene3D::Light light, uint32_t lightIndex) +{ + if(!node) + { + return; + } + + GetImplementation(node).AddLight(light, lightIndex); + + uint32_t childrenCount = node.GetChildCount(); + for(uint32_t i = 0; i < childrenCount; ++i) + { + Scene3D::ModelNode childNode = Scene3D::ModelNode::DownCast(node.GetChildAt(i)); + if(childNode) + { + AddLightRecursively(childNode, light, lightIndex); + } + } +} + +void RemoveLightRecursively(Scene3D::ModelNode node, uint32_t lightIndex) +{ + if(!node) + { + return; + } + + GetImplementation(node).RemoveLight(lightIndex); + + uint32_t childrenCount = node.GetChildCount(); + for(uint32_t i = 0; i < childrenCount; ++i) + { + Scene3D::ModelNode childNode = Scene3D::ModelNode::DownCast(node.GetChildAt(i)); + if(childNode) + { + RemoveLightRecursively(childNode, lightIndex); + } + } +} + } // anonymous namespace Model::Model(const std::string& modelUrl, const std::string& resourceDirectoryUrl) @@ -241,6 +282,15 @@ void Model::AddModelNode(Scene3D::ModelNode modelNode) UpdateImageBasedLightScaleFactor(); } + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + if(mLights[i]) + { + AddLightRecursively(modelNode, mLights[i], i); + } + } + if(Self().GetProperty(Dali::Actor::Property::CONNECTED_TO_SCENE)) { NotifyResourceReady(); @@ -251,6 +301,14 @@ void Model::RemoveModelNode(Scene3D::ModelNode modelNode) { if(mModelRoot) { + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + if(mLights[i]) + { + RemoveLightRecursively(modelNode, i); + } + } mModelRoot.Remove(modelNode); } } @@ -467,6 +525,7 @@ void Model::OnInitialize() { // Make ParentOrigin as Center. Self().SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + mLights.resize(Scene3D::Internal::Light::GetMaximumEnabledLightCount()); } void Model::OnSceneConnection(int depth) @@ -634,7 +693,6 @@ void Model::UpdateImageBasedLightScaleFactorRecursively(Scene3D::ModelNode node, return; } - node.RegisterProperty(Dali::Scene3D::Loader::NodeDefinition::GetIblScaleFactorUniformName().data(), iblScaleFactor); GetImplementation(node).SetImageBasedLightScaleFactor(iblScaleFactor); uint32_t childrenCount = node.GetChildCount(); @@ -743,6 +801,21 @@ void Model::NotifyImageBasedLightScaleFactor(float scaleFactor) } } +void Model::NotifyLightAdded(uint32_t lightIndex, Scene3D::Light light) +{ + mLights[lightIndex] = light; + AddLightRecursively(mModelRoot, light, lightIndex); +} + +void Model::NotifyLightRemoved(uint32_t lightIndex) +{ + if(mLights[lightIndex]) + { + RemoveLightRecursively(mModelRoot, lightIndex); + mLights[lightIndex].Reset(); + } +} + void Model::OnModelLoadComplete() { if(!mModelLoadTask->HasSucceeded()) @@ -773,6 +846,15 @@ void Model::OnModelLoadComplete() mDefaultSpecularTexture = resources.mEnvironmentMaps.front().second.mSpecular; } + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + if(mLights[i]) + { + AddLightRecursively(mModelRoot, mLights[i], i); + } + } + UpdateImageBasedLightTexture(); UpdateImageBasedLightScaleFactor(); Self().SetProperty(Dali::Actor::Property::ANCHOR_POINT, Vector3(mModelPivot.x, 1.0f - mModelPivot.y, mModelPivot.z)); diff --git a/dali-scene3d/internal/controls/model/model-impl.h b/dali-scene3d/internal/controls/model/model-impl.h index d7924bf..52aed7d5 100644 --- a/dali-scene3d/internal/controls/model/model-impl.h +++ b/dali-scene3d/internal/controls/model/model-impl.h @@ -29,10 +29,11 @@ // INTERNAL INCLUDES #include -#include +#include #include #include #include +#include #include #include @@ -47,7 +48,7 @@ namespace Internal /** * @brief Impl class for Model. */ -class Model : public Dali::Toolkit::Internal::Control, public ImageBasedLightObserver +class Model : public Dali::Toolkit::Internal::Control, public LightObserver { public: using AnimationData = std::pair; @@ -247,17 +248,27 @@ private: */ void ApplyCameraTransform(Dali::CameraActor camera) const; -public: // Overrides ImageBasedLightObserver Methods. +public: // Overrides LightObserver Methods. /** - * @copydoc Dali::Scene3D::Internal::ImageBasedLightObserver::NotifyImageBasedLightTexture() + * @copydoc Dali::Scene3D::Internal::LightObserver::NotifyImageBasedLightTexture() */ void NotifyImageBasedLightTexture(Dali::Texture diffuseTexture, Dali::Texture specularTexture, float scaleFactor, uint32_t specularMipmapLevels) override; /** - * @copydoc Dali::Scene3D::Internal::ImageBasedLightObserver::NotifyImageBasedLightScaleFactor() + * @copydoc Dali::Scene3D::Internal::LightObserver::NotifyImageBasedLightScaleFactor() */ void NotifyImageBasedLightScaleFactor(float scaleFactor) override; + /** + * @copydoc Dali::Scene3D::Internal::LightObserver::NotifyLightAdded() + */ + void NotifyLightAdded(uint32_t lightIndex, Scene3D::Light light) override; + + /** + * @copydoc Dali::Scene3D::Internal::LightObserver::NotifyLightRemoved() + */ + void NotifyLightRemoved(uint32_t lightIndex) override; + private: /** * @brief Asynchronously model loading finished. @@ -323,6 +334,9 @@ private: WeakHandle mParentSceneView; Dali::PropertyNotification mSizeNotification; + // Light + std::vector mLights; + // Asynchronous loading variable ModelLoadTaskPtr mModelLoadTask; EnvironmentMapLoadTaskPtr mIblDiffuseLoadTask; diff --git a/dali-scene3d/internal/controls/scene-view/scene-view-impl.cpp b/dali-scene3d/internal/controls/scene-view/scene-view-impl.cpp index 17c9465..6f8e800 100644 --- a/dali-scene3d/internal/controls/scene-view/scene-view-impl.cpp +++ b/dali-scene3d/internal/controls/scene-view/scene-view-impl.cpp @@ -39,6 +39,7 @@ // INTERNAL INCLUDES #include #include +#include using namespace Dali; @@ -61,6 +62,7 @@ DALI_TYPE_REGISTRATION_END() Property::Index RENDERING_BUFFER = Dali::Toolkit::Control::CONTROL_PROPERTY_END_INDEX + 1; constexpr int32_t DEFAULT_ORIENTATION = 0; +constexpr int32_t INVALID_INDEX = -1; static constexpr std::string_view SKYBOX_INTENSITY_STRING = "uIntensity"; @@ -273,16 +275,28 @@ void SceneView::SelectCamera(const std::string& name) UpdateCamera(GetCamera(name)); } -void SceneView::RegisterSceneItem(Scene3D::Internal::ImageBasedLightObserver* item) +void SceneView::RegisterSceneItem(Scene3D::Internal::LightObserver* item) { if(item) { item->NotifyImageBasedLightTexture(mDiffuseTexture, mSpecularTexture, mIblScaleFactor, mSpecularMipmapLevels); + + if(mActivatedLightCount > 0) + { + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + if(mActivatedLights[i]) + { + item->NotifyLightAdded(i, mActivatedLights[i]); + } + } + } mItems.push_back(item); } } -void SceneView::UnregisterSceneItem(Scene3D::Internal::ImageBasedLightObserver* item) +void SceneView::UnregisterSceneItem(Scene3D::Internal::LightObserver* item) { if(item) { @@ -290,6 +304,17 @@ void SceneView::UnregisterSceneItem(Scene3D::Internal::ImageBasedLightObserver* { if(mItems[i] == item) { + if(mActivatedLightCount > 0) + { + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + if(mActivatedLights[i]) + { + item->NotifyLightRemoved(i); + } + } + } mItems.erase(mItems.begin() + i); break; } @@ -412,6 +437,64 @@ float SceneView::GetImageBasedLightScaleFactor() const return mIblScaleFactor; } +void SceneView::AddLight(Scene3D::Light light) +{ + bool enabled = AddLightInternal(light); + mLights.push_back(std::make_pair(light, enabled)); +} + +void SceneView::RemoveLight(Scene3D::Light light) +{ + if(mActivatedLights.empty()) + { + return; + } + + bool needToDisable = false; + for(uint32_t i = 0; i < mLights.size(); ++i) + { + if(mLights[i].first == light) + { + // If mLights[i].second is true, it means the light is currently activated in Scene. + // Then it should be removed from mActivatedLights list too. + needToDisable = mLights[i].second; + mLights.erase(mLights.begin() + i); + break; + } + } + + uint32_t maxNumberOfLight = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + if(needToDisable) + { + uint32_t removedIndex = RemoveLightInternal(light); + if(mActivatedLightCount < maxNumberOfLight && mLights.size() >= maxNumberOfLight) + { + for(auto && lightItem : mLights) + { + if(lightItem.second == false) + { + lightItem.second = AddLightInternal(lightItem.first); + break; + } + } + } + + // To remove empty entity of mActivatedLights, moves last object to empty position. + // Because one Light is removed, mActivatedLights[mActivatedLightCount] is current last object of the list. + if(!mActivatedLights[removedIndex] && mActivatedLightCount > 0 && removedIndex < mActivatedLightCount) + { + Scene3D::Light reorderingLight = mActivatedLights[mActivatedLightCount]; + RemoveLightInternal(reorderingLight); + AddLightInternal(reorderingLight); + } + } +} + +uint32_t SceneView::GetActivatedLightCount() const +{ + return mActivatedLightCount; +} + void SceneView::UseFramebuffer(bool useFramebuffer) { if(mUseFrameBuffer != useFramebuffer) @@ -848,6 +931,66 @@ void SceneView::NotifyImageBasedLightTextureChange() } } +bool SceneView::AddLightInternal(Scene3D::Light light) +{ + uint32_t maxNumberOfLight = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + if(mActivatedLightCount == 0) + { + mActivatedLights.resize(maxNumberOfLight); + } + + bool enabled = false; + if(mActivatedLightCount < maxNumberOfLight) + { + uint32_t newLightIndex = 0u; + for(; newLightIndex < maxNumberOfLight; ++newLightIndex) + { + if(!mActivatedLights[newLightIndex]) + { + mActivatedLights[newLightIndex] = light; + break; + } + } + + for(auto&& item : mItems) + { + if(item) + { + item->NotifyLightAdded(newLightIndex, light); + } + } + + mActivatedLightCount++; + enabled = true; + } + return enabled; +} + +int32_t SceneView::RemoveLightInternal(Scene3D::Light light) +{ + int32_t removedIndex = INVALID_INDEX; + uint32_t maxNumberOfLight = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxNumberOfLight; ++i) + { + if(mActivatedLights[i] == light) + { + for(auto&& item : mItems) + { + if(item) + { + item->NotifyLightRemoved(i); + } + } + mActivatedLights[i].Reset(); + mActivatedLightCount--; + removedIndex = i; + break; + } + } + + return removedIndex; +} + } // namespace Internal } // namespace Scene3D } // namespace Dali diff --git a/dali-scene3d/internal/controls/scene-view/scene-view-impl.h b/dali-scene3d/internal/controls/scene-view/scene-view-impl.h index 343db45..02e0a74 100644 --- a/dali-scene3d/internal/controls/scene-view/scene-view-impl.h +++ b/dali-scene3d/internal/controls/scene-view/scene-view-impl.h @@ -32,7 +32,7 @@ // INTERNAL INCLUDES #include -#include +#include #include namespace Dali @@ -104,14 +104,14 @@ public: * * @param[in] item scene observer to be registered. */ - void RegisterSceneItem(Scene3D::Internal::ImageBasedLightObserver* item); + void RegisterSceneItem(Scene3D::Internal::LightObserver* item); /** * @brief Unregister an item * * @param[in] item scene observer to be unregistered. */ - void UnregisterSceneItem(Scene3D::Internal::ImageBasedLightObserver* item); + void UnregisterSceneItem(Scene3D::Internal::LightObserver* item); /** * @copydoc SceneView::SetImageBasedLightSource() @@ -129,6 +129,30 @@ public: float GetImageBasedLightScaleFactor() const; /** + * @brief Adds a Light object to this SceneView. + * Multiple light object can be added on this SceneView + * But the number of lights those actually turned on has limitation. + * Scene3D::Light::GetMaximumEnabledLightCount() method can be used to know the maximum namber. + * + * @param[in] light Light object to be added. + */ + void AddLight(Scene3D::Light light); + + /** + * @brief Removes a Light object to this SceneView. + * If the light is currently turned on, and SceneView has lights more than maximum number of enabled light. + * One of light in queue is turned on after this light is removed. + * + * @param[in] light Light object to be removed. + */ + void RemoveLight(Scene3D::Light light); + + /** + * @copydoc SceneView::GetActivatedLightCount() + */ + uint32_t GetActivatedLightCount() const; + + /** * @copydoc SceneView::UseFramebuffer() */ void UseFramebuffer(bool useFramebuffer); @@ -290,6 +314,22 @@ private: */ void NotifyImageBasedLightTextureChange(); + /** + * @brief Internal method to add an Light object to this SceneView. + * + * @param[in] light Light object to be added. + * @return True if successed to enable. + */ + bool AddLightInternal(Scene3D::Light light); + + /** + * @brief Internal method to remove an Light object to this SceneView. + * + * @param[in] light Light object to be added. + * @return Index of removed light in enabled light list. If failed to remove it returns negative value; + */ + int32_t RemoveLightInternal(Scene3D::Light light); + private: Toolkit::Visual::Base mVisual; @@ -299,7 +339,7 @@ private: CameraActor mDefaultCamera; CameraActor mSelectedCamera; std::vector mCameras; - std::vector mItems; + std::vector mItems; Dali::FrameBuffer mFrameBuffer; Dali::Texture mTexture; Dali::RenderTask mRenderTask; @@ -310,6 +350,11 @@ private: float mSkyboxIntensity{1.0f}; uint8_t mFrameBufferMultiSamplingLevel{4u}; + // Light + std::vector> mLights; // Pair of Light object and flag that denotes the light is currently activated or not. + std::vector mActivatedLights; + uint32_t mActivatedLightCount{0u}; + // Asynchronous Loading. EnvironmentMapLoadTaskPtr mSkyboxLoadTask; EnvironmentMapLoadTaskPtr mIblDiffuseLoadTask; diff --git a/dali-scene3d/internal/file.list b/dali-scene3d/internal/file.list index 064afbf..661041f 100644 --- a/dali-scene3d/internal/file.list +++ b/dali-scene3d/internal/file.list @@ -6,6 +6,7 @@ set(scene3d_src_files ${scene3d_src_files} ${scene3d_internal_dir}/common/model-load-task.cpp ${scene3d_internal_dir}/controls/model/model-impl.cpp ${scene3d_internal_dir}/controls/scene-view/scene-view-impl.cpp + ${scene3d_internal_dir}/light/light-impl.cpp ${scene3d_internal_dir}/loader/dli-loader-impl.cpp ${scene3d_internal_dir}/loader/gltf2-asset.cpp ${scene3d_internal_dir}/loader/gltf2-util.cpp diff --git a/dali-scene3d/internal/graphics/shaders/default-physically-based-shader.frag b/dali-scene3d/internal/graphics/shaders/default-physically-based-shader.frag index e701162..9a436f5 100644 --- a/dali-scene3d/internal/graphics/shaders/default-physically-based-shader.frag +++ b/dali-scene3d/internal/graphics/shaders/default-physically-based-shader.frag @@ -72,6 +72,12 @@ uniform sampler2D sSpecular; uniform sampler2D sSpecularColor; #endif +// For Light (Currently Directional Only) +#define MAX_LIGHTS 5 +uniform mediump int uLightCount; +uniform mediump vec3 uLightDirection[MAX_LIGHTS]; +uniform mediump vec3 uLightColor[MAX_LIGHTS]; + //// For IBL uniform sampler2D sbrdfLUT; uniform samplerCube sDiffuseEnvSampler; @@ -94,6 +100,7 @@ in highp vec3 vPositionToCamera; out vec4 FragColor; const float c_MinRoughness = 0.04; +const float M_PI = 3.141592653589793; vec3 linear(vec3 color) { @@ -201,6 +208,42 @@ void main() lowp vec3 color = (diffuse + specular) * uIblIntensity; + // Punctual Light + if(uLightCount > 0) + { + // Compute reflectance. + lowp float reflectance = max(max(f0.r, f0.g), f0.b); + lowp float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0); + lowp float r = perceptualRoughness * perceptualRoughness; + lowp float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV))); + mediump float roughnessSq = r * r; + lowp vec3 diffuseColorPunctual = baseColor.rgb * (vec3(1.0) - f0); + diffuseColorPunctual *= ( 1.0 - metallic ); + + for(int i = 0; i < uLightCount; ++i) + { + mediump vec3 l = normalize(-uLightDirection[i]); // Vector from surface point to light + mediump vec3 h = normalize(l+v); // Half vector between both l and v + mediump float VdotH = dot(v, h); + lowp vec3 specularReflection = f0 + (reflectance90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0); + + mediump float NdotL = clamp(dot(n, l), 0.001, 1.0); + lowp float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL))); + lowp float geometricOcclusion = attenuationL * attenuationV; + + mediump float NdotH = dot(n, h); + lowp float f = (NdotH * roughnessSq - NdotH) * NdotH + 1.0; + lowp float microfacetDistribution = roughnessSq / (M_PI * f * f);; + + // Calculation of analytical lighting contribution + lowp vec3 diffuseContrib = ( 1.0 - specularReflection ) * ( diffuseColorPunctual / M_PI ); + lowp vec3 specContrib = specularReflection * geometricOcclusion * microfacetDistribution / ( 4.0 * NdotL * NdotV ); + + // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law) + color += NdotL * uLightColor[i] * (diffuseContrib + specContrib); + } + } + #ifdef OCCLUSION lowp float ao = texture(sOcclusion, vUV).r; color = mix(color, color * ao, uOcclusionStrength); diff --git a/dali-scene3d/internal/light/light-impl.cpp b/dali-scene3d/internal/light/light-impl.cpp new file mode 100644 index 0000000..00eb0f4 --- /dev/null +++ b/dali-scene3d/internal/light/light-impl.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2023 Samsung Electronics 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. + * + */ + +// CLASS HEADER +#include + +// EXTERNAL INCLUDES +#include +#include +#include + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +namespace Scene3D +{ +namespace Internal +{ +namespace +{ +static constexpr uint32_t MAX_NUMBER_OF_LIGHT = 5; +static constexpr std::string_view LIGHT_COUNT_STRING("uLightCount"); +static constexpr std::string_view LIGHT_DIRECTION_STRING("uLightDirection"); +static constexpr std::string_view LIGHT_COLOR_STRING("uLightColor"); + +/** + * Creates control through type registry + */ +BaseHandle Create() +{ + return Scene3D::Light::New(); +} + +// Setup properties, signals and actions using the type-registry. +DALI_TYPE_REGISTRATION_BEGIN(Scene3D::Light, Dali::CustomActor, Create); +DALI_TYPE_REGISTRATION_END() +} // unnamed namespace + +Dali::Scene3D::Light Light::New() +{ + // Create the implementation, temporarily owned on stack + IntrusivePtr nodeImpl = new Light(); + + // Pass ownership to handle + Scene3D::Light handle(*nodeImpl); + + // Second-phase init of the implementation + // This can only be done after the CustomActor connection has been made... + nodeImpl->Initialize(); + + return handle; +} + +Light::Light() +: CustomActorImpl(ActorFlags::DISABLE_SIZE_NEGOTIATION) +{ +} + +Light::~Light() +{ +} + +void Light::Initialize() +{ + Self().SetProperty(Dali::Actor::Property::COLOR, Color::WHITE); +} + +void Light::Enable(bool enable) +{ + if(enable == mIsEnabled) + { + return; + } + mIsEnabled = enable; + + Scene3D::SceneView sceneView = mParentSceneView.GetHandle(); + if(!sceneView) + { + return; + } + + if(mIsEnabled) + { + GetImpl(sceneView).AddLight(Scene3D::Light::DownCast(Self())); + } + else + { + GetImpl(sceneView).RemoveLight(Scene3D::Light::DownCast(Self())); + } +} + +bool Light::IsEnabled() const +{ + return mIsEnabled; +} + +void Light::OnSceneConnection(int depth) +{ + Actor parent = Self().GetParent(); + while(parent) + { + Scene3D::SceneView sceneView = Scene3D::SceneView::DownCast(parent); + if(sceneView) + { + mParentSceneView = sceneView; + if(mIsEnabled) + { + GetImpl(sceneView).AddLight(Scene3D::Light::DownCast(Self())); + } + break; + } + parent = parent.GetParent(); + } +} + +void Light::OnSceneDisconnection() +{ + Scene3D::SceneView sceneView = mParentSceneView.GetHandle(); + if(sceneView) + { + mParentSceneView.Reset(); + GetImpl(sceneView).RemoveLight(Scene3D::Light::DownCast(Self())); + } +} + +void Light::OnChildAdd(Actor& child) +{ +} + +void Light::OnChildRemove(Actor& child) +{ +} + +void Light::OnSizeSet(const Vector3& targetSize) +{ +} + +void Light::OnSizeAnimation(Animation& animation, const Vector3& targetSize) +{ + // @todo size negotiate background to new size, animate as well? +} + +void Light::OnRelayout(const Vector2& size, RelayoutContainer& container) +{ +} + +void Light::OnSetResizePolicy(ResizePolicy::Type policy, Dimension::Type dimension) +{ +} + +Vector3 Light::GetNaturalSize() +{ + return Vector3::ZERO; +} + +float Light::CalculateChildSize(const Dali::Actor& child, Dimension::Type dimension) +{ + return 0.0f; +} + +float Light::GetHeightForWidth(float width) +{ + return 0.0f; +} + +float Light::GetWidthForHeight(float height) +{ + return 0.0f; +} + +bool Light::RelayoutDependentOnChildren(Dimension::Type dimension) +{ + return false; +} + +void Light::OnCalculateRelayoutSize(Dimension::Type dimension) +{ +} + +void Light::OnLayoutNegotiated(float size, Dimension::Type dimension) +{ +} + +Light& GetImplementation(Dali::Scene3D::Light& handle) +{ + CustomActorImpl& customInterface = handle.GetImplementation(); + Light& impl = dynamic_cast(customInterface); + return impl; +} + +const Light& GetImplementation(const Dali::Scene3D::Light& handle) +{ + const CustomActorImpl& customInterface = handle.GetImplementation(); + // downcast to control + const Light& impl = dynamic_cast(customInterface); + return impl; +} + +// Public Method + +uint32_t Light::GetMaximumEnabledLightCount() +{ + return MAX_NUMBER_OF_LIGHT; +} + +std::string_view Light::GetLightCountUniformName() +{ + return LIGHT_COUNT_STRING; +} + +std::string_view Light::GetLightDirectionUniformName() +{ + return LIGHT_DIRECTION_STRING; +} + +std::string_view Light::GetLightColorUniformName() +{ + return LIGHT_COLOR_STRING; +} + +} // namespace Internal + +} // namespace Scene3D + +} // namespace Dali diff --git a/dali-scene3d/internal/light/light-impl.h b/dali-scene3d/internal/light/light-impl.h new file mode 100644 index 0000000..70b51f5 --- /dev/null +++ b/dali-scene3d/internal/light/light-impl.h @@ -0,0 +1,239 @@ +#ifndef DALI_SCENE3D_LIGHT_IMPL_H +#define DALI_SCENE3D_LIGHT_IMPL_H + +/* + * Copyright (c) 2023 Samsung Electronics 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. + * + */ + +// EXTERNAL INCLUDES +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include + +namespace Dali +{ +namespace Scene3D +{ +/** + * @addtogroup dali_scene3d_light + * @{ + */ + +namespace Internal +{ +/** + * @brief This is the internal base class for custom node of Light. + * + * @SINCE_2_2.32 + */ +class DALI_SCENE3D_API Light : public CustomActorImpl +{ +public: + // Creation & Destruction + /** + * @brief Creates a new LightImpl instance that does not require touch by default. + * + * If touch is required, then the user can connect to this class' touch signal. + * @SINCE_2_2.32 + * @return A handle to the Light instance + */ + static Scene3D::Light New(); + + /** + * @copydoc Scene3D::Light::Enable() + */ + void Enable(bool enable); + + /** + * @copydoc Scene3D::Light::IsEnabled() + */ + bool IsEnabled() const; + +protected: + /** + * @brief Virtual destructor. + * @SINCE_2_2.32 + */ + virtual ~Light(); + +protected: // From CustomActorImpl + /** + * @copydoc CustomActorImpl::OnSceneConnection() + * @note If overridden, then an up-call to Light::OnSceneConnection MUST be made at the end. + */ + void OnSceneConnection(int depth) override; + + /** + * @copydoc CustomActorImpl::OnSceneDisconnection() + * @note If overridden, then an up-call to Light::OnSceneDisconnection MUST be made at the end. + */ + void OnSceneDisconnection() override; + + /** + * @copydoc CustomActorImpl::OnChildAdd() + * @note If overridden, then an up-call to Light::OnChildAdd MUST be made at the end. + */ + void OnChildAdd(Actor& child) override; + + /** + * @copydoc CustomActorImpl::OnChildRemove() + * @note If overridden, then an up-call to Light::OnChildRemove MUST be made at the end. + */ + void OnChildRemove(Actor& child) override; + + /** + * @copydoc CustomActorImpl::OnSizeSet() + * @note If overridden, then an up-call to Light::OnSizeSet MUST be made at the end. + */ + void OnSizeSet(const Vector3& targetSize) override; + + /** + * @copydoc CustomActorImpl::OnSizeAnimation() + * @note If overridden, then an up-call to Light::OnSizeAnimation MUST be made at the end. + */ + void OnSizeAnimation(Animation& animation, const Vector3& targetSize) override; + + /** + * @copydoc CustomActorImpl::OnRelayout() + */ + void OnRelayout(const Vector2& size, RelayoutContainer& container) override; + + /** + * @copydoc CustomActorImpl::OnSetResizePolicy() + */ + void OnSetResizePolicy(ResizePolicy::Type policy, Dimension::Type dimension) override; + + /** + * @copydoc CustomActorImpl::GetNaturalSize() + */ + Vector3 GetNaturalSize() override; + + /** + * @copydoc CustomActorImpl::CalculateChildSize() + */ + float CalculateChildSize(const Dali::Actor& child, Dimension::Type dimension) override; + + /** + * @copydoc CustomActorImpl::GetHeightForWidth() + */ + float GetHeightForWidth(float width) override; + + /** + * @copydoc CustomActorImpl::GetWidthForHeight() + */ + float GetWidthForHeight(float height) override; + + /** + * @copydoc CustomActorImpl::RelayoutDependentOnChildren() + */ + bool RelayoutDependentOnChildren(Dimension::Type dimension = Dimension::ALL_DIMENSIONS) override; + + /** + * @copydoc CustomActorImpl::OnCalculateRelayoutSize() + */ + void OnCalculateRelayoutSize(Dimension::Type dimension) override; + + /** + * @copydoc CustomActorImpl::OnLayoutNegotiated() + */ + void OnLayoutNegotiated(float size, Dimension::Type dimension) override; + +protected: + // Construction + + /** + * @brief Light constructor. + */ + Light(); + + /** + * @brief Second phase initialization. + */ + void Initialize(); + +public: // Public Method + + /** + * @copydoc Scene3D::Light::GetMaximumEnabledLightCount() + */ + static uint32_t GetMaximumEnabledLightCount(); + + /** + * @brief Retrieves Light Enabled Uniform Name for shader() + * @return string_view for LightEnabledUniformName + */ + static std::string_view GetLightCountUniformName(); + + /** + * @brief Retrieves Light Direction Uniform Name for shader() + * @return string_view for LightDirectionUniformName + */ + static std::string_view GetLightDirectionUniformName(); + + /** + * @brief Retrieves Light Color Uniform Name for shader() + * @return string_view for LightColorUniformName + */ + static std::string_view GetLightColorUniformName(); + +private: + /// @cond internal + + // Not copyable or movable + DALI_INTERNAL Light(const Light&) = delete; ///< Deleted copy constructor. + DALI_INTERNAL Light(Light&&) = delete; ///< Deleted move constructor. + DALI_INTERNAL Light& operator=(const Light&) = delete; ///< Deleted copy assignment operator. + DALI_INTERNAL Light& operator=(Light&&) = delete; ///< Deleted move assignment operator. + +private: + WeakHandle mParentSceneView; + bool mIsEnabled{true}; + /// @endcond +}; + +/** + * @brief Gets implementation from the handle. + * + * @param handle + * @return Implementation + * @pre handle is initialized and points to a node + */ +DALI_SCENE3D_API Internal::Light& GetImplementation(Dali::Scene3D::Light& handle); + +/** + * @brief Gets implementation from the handle. + * + * @param handle + * @return Implementation + * @pre Handle is initialized and points to a node. + */ +DALI_SCENE3D_API const Internal::Light& GetImplementation(const Dali::Scene3D::Light& handle); + +} // namespace Internal + +/** + * @} + */ +} // namespace Scene3D + +} // namespace Dali + +#endif // DALI_SCENE3D_LIGHT_IMPL_H diff --git a/dali-scene3d/internal/model-components/material-impl.cpp b/dali-scene3d/internal/model-components/material-impl.cpp index 7cfb847..a117d48 100644 --- a/dali-scene3d/internal/model-components/material-impl.cpp +++ b/dali-scene3d/internal/model-components/material-impl.cpp @@ -26,6 +26,7 @@ // INTERNAL INCLUDES #include +#include #include #include #include @@ -696,6 +697,21 @@ void Material::SetRendererUniform(Dali::Renderer renderer) renderer.RegisterProperty(Scene3D::Loader::NodeDefinition::GetIblScaleFactorUniformName().data(), 1.0f); renderer.RegisterProperty(Scene3D::Loader::NodeDefinition::GetIblYDirectionUniformName().data(), Vector3(1.0f, -1.0, 1.0)); + std::string lightCountPropertyName(Scene3D::Internal::Light::GetLightCountUniformName()); + renderer.RegisterProperty(lightCountPropertyName, 0); + + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + std::string lightDirectionPropertyName(Scene3D::Internal::Light::GetLightDirectionUniformName()); + lightDirectionPropertyName += "[" + std::to_string(i) + "]"; + renderer.RegisterProperty(lightDirectionPropertyName, Vector3::ZAXIS); + + std::string lightColorPropertyName(Scene3D::Internal::Light::GetLightColorUniformName()); + lightColorPropertyName += "[" + std::to_string(i) + "]"; + renderer.RegisterProperty(lightColorPropertyName, Vector3(Color::WHITE)); + } + Scene3D::Loader::RendererState::Apply(mRendererState, renderer); } diff --git a/dali-scene3d/internal/model-components/model-node-impl.cpp b/dali-scene3d/internal/model-components/model-node-impl.cpp index 54db4bc..64b3ec2 100644 --- a/dali-scene3d/internal/model-components/model-node-impl.cpp +++ b/dali-scene3d/internal/model-components/model-node-impl.cpp @@ -25,6 +25,7 @@ // INTERNAL INCLUDES #include +#include namespace Dali { @@ -74,6 +75,7 @@ ModelNode::~ModelNode() void ModelNode::Initialize() { OnInitialize(); + mLights.resize(Scene3D::Internal::Light::GetMaximumEnabledLightCount()); } void ModelNode::OnInitialize() @@ -191,6 +193,15 @@ void ModelNode::AddModelPrimitive(Dali::Scene3D::ModelPrimitive modelPrimitive) GetImplementation(modelPrimitive).SetImageBasedLightTexture(mDiffuseTexture, mSpecularTexture, mIblScaleFactor, mSpecularMipmapLevels); } + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + if(mLights[i]) + { + GetImplementation(modelPrimitive).AddLight(mLights[i], i); + } + } + Dali::Renderer renderer = GetImplementation(modelPrimitive).GetRenderer(); if(renderer) { @@ -220,6 +231,7 @@ void ModelNode::RemoveModelPrimitive(Dali::Scene3D::ModelPrimitive modelPrimitiv { continue; } + RemoveModelPrimitive(i); break; } @@ -232,6 +244,15 @@ void ModelNode::RemoveModelPrimitive(uint32_t index) return; } + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + if(mLights[i]) + { + GetImplementation(mModelPrimitiveContainer[index]).RemoveLight(i); + } + } + Actor self = Self(); GetImplementation(mModelPrimitiveContainer[index]).RemovePrimitiveObserver(this); @@ -280,6 +301,24 @@ void ModelNode::SetImageBasedLightScaleFactor(float iblScaleFactor) } } +void ModelNode::AddLight(Scene3D::Light light, uint32_t lightIndex) +{ + mLights[lightIndex] = light; + for(auto&& primitive : mModelPrimitiveContainer) + { + GetImplementation(primitive).AddLight(light, lightIndex); + } +} + +void ModelNode::RemoveLight(uint32_t lightIndex) +{ + for(auto&& primitive : mModelPrimitiveContainer) + { + GetImplementation(primitive).RemoveLight(lightIndex); + } + mLights[lightIndex].Reset(); +} + void ModelNode::SetBlendShapeData(Scene3D::Loader::BlendShapes::BlendShapeData& data, Scene3D::ModelPrimitive primitive) { GetImplementation(primitive).SetBlendShapeData(data); diff --git a/dali-scene3d/internal/model-components/model-node-impl.h b/dali-scene3d/internal/model-components/model-node-impl.h index e3c718c..4b9a66d 100644 --- a/dali-scene3d/internal/model-components/model-node-impl.h +++ b/dali-scene3d/internal/model-components/model-node-impl.h @@ -25,6 +25,7 @@ // INTERNAL INCLUDES #include +#include #include #include #include @@ -232,6 +233,9 @@ public: // Public Method */ void SetImageBasedLightScaleFactor(float iblScaleFactor); + void AddLight(Scene3D::Light light, uint32_t lightIndex); + void RemoveLight(uint32_t lightIndex); + /** * @brief Sets the blend shape data for a ModelPrimitive. * @@ -280,6 +284,9 @@ private: Dali::Texture mDiffuseTexture; float mIblScaleFactor{1.0f}; uint32_t mSpecularMipmapLevels{1u}; + + // Light + std::vector mLights; /// @endcond }; diff --git a/dali-scene3d/internal/model-components/model-primitive-impl.cpp b/dali-scene3d/internal/model-components/model-primitive-impl.cpp index adb78aa..569be53 100644 --- a/dali-scene3d/internal/model-components/model-primitive-impl.cpp +++ b/dali-scene3d/internal/model-components/model-primitive-impl.cpp @@ -20,10 +20,12 @@ // EXTERNAL INCLUDES #include +#include #include #include // INTERNAL INCLUDES +#include #include #include @@ -52,6 +54,8 @@ constexpr std::string_view MORPH_POSITION_KEYWORD = "MORPH_POSITION"; constexpr std::string_view MORPH_NORMAL_KEYWORD = "MORPH_NORMAL"; constexpr std::string_view MORPH_TANGENT_KEYWORD = "MORPH_TANGENT"; constexpr std::string_view MORPH_VERSION_2_0_KEYWORD = "MORPH_VERSION_2_0"; + +static constexpr uint32_t INDEX_FOR_LIGHT_CONSTRAINT_TAG = 10; } // unnamed namespace ModelPrimitivePtr ModelPrimitive::New() @@ -78,6 +82,7 @@ ModelPrimitive::~ModelPrimitive() void ModelPrimitive::Initialize() { + mLights.resize(Scene3D::Internal::Light::GetMaximumEnabledLightCount()); } void ModelPrimitive::SetRenderer(Dali::Renderer renderer) @@ -175,6 +180,52 @@ void ModelPrimitive::SetImageBasedLightScaleFactor(float iblScaleFactor) } } +void ModelPrimitive::AddLight(Scene3D::Light light, uint32_t lightIndex) +{ + if(mLights[lightIndex]) + { + RemoveLight(lightIndex); + } + + mLights[lightIndex] = light; + // TODO Remove light at lightIndex if it is already set. + if(mRenderer && mMaterial) + { + mLightCount++; + std::string lightCountPropertyName(Scene3D::Internal::Light::GetLightCountUniformName()); + mRenderer.RegisterProperty(lightCountPropertyName, mLightCount); + + std::string lightDirectionPropertyName(Scene3D::Internal::Light::GetLightDirectionUniformName()); + lightDirectionPropertyName += "[" + std::to_string(lightIndex) + "]"; + auto lightDirectionPropertyIndex = mRenderer.RegisterProperty(lightDirectionPropertyName, Vector3::ZAXIS); + Dali::Constraint lightDirectionConstraint = Dali::Constraint::New(mRenderer, lightDirectionPropertyIndex, [](Vector3& output, const PropertyInputContainer& inputs) + { output = inputs[0]->GetQuaternion().Rotate(Vector3::ZAXIS); }); + lightDirectionConstraint.AddSource(Source{light, Dali::Actor::Property::WORLD_ORIENTATION}); + lightDirectionConstraint.ApplyPost(); + lightDirectionConstraint.SetTag(INDEX_FOR_LIGHT_CONSTRAINT_TAG + lightIndex); + + std::string lightColorPropertyName(Scene3D::Internal::Light::GetLightColorUniformName()); + lightColorPropertyName += "[" + std::to_string(lightIndex) + "]"; + auto lightColorPropertyIndex = mRenderer.RegisterProperty(lightColorPropertyName, Vector3(Color::WHITE)); + Dali::Constraint lightColorConstraint = Dali::Constraint::New(mRenderer, lightColorPropertyIndex, [](Vector3& output, const PropertyInputContainer& inputs) + { output = Vector3(inputs[0]->GetVector4()); }); + lightColorConstraint.AddSource(Source{light, Dali::Actor::Property::COLOR}); + lightColorConstraint.ApplyPost(); + lightColorConstraint.SetTag(INDEX_FOR_LIGHT_CONSTRAINT_TAG + lightIndex); + } +} + +void ModelPrimitive::RemoveLight(uint32_t lightIndex) +{ + mLightCount--; + std::string lightCountPropertyName(Scene3D::Internal::Light::GetLightCountUniformName()); + mRenderer.RegisterProperty(lightCountPropertyName, mLightCount); + + mRenderer.RemoveConstraints(INDEX_FOR_LIGHT_CONSTRAINT_TAG + lightIndex); + + mLights[lightIndex].Reset(); +} + void ModelPrimitive::SetBlendShapeData(Scene3D::Loader::BlendShapes::BlendShapeData& data) { mBlendShapeData = std::move(data); @@ -338,6 +389,16 @@ void ModelPrimitive::CreateRenderer() mRenderer = Renderer::New(mGeometry, mShader); mRenderer.SetTextures(mTextureSet); UpdateRendererUniform(); + + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + if(mLights[i]) + { + AddLight(mLights[i], i); + } + } + for(auto* observer : mObservers) { observer->OnRendererCreated(mRenderer); diff --git a/dali-scene3d/internal/model-components/model-primitive-impl.h b/dali-scene3d/internal/model-components/model-primitive-impl.h index b7d26b4..027ecac 100644 --- a/dali-scene3d/internal/model-components/model-primitive-impl.h +++ b/dali-scene3d/internal/model-components/model-primitive-impl.h @@ -29,6 +29,7 @@ // INTERNAL INCLUDES #include #include +#include #include #include #include @@ -139,6 +140,21 @@ public: void SetImageBasedLightScaleFactor(float iblScaleFactor); /** + * @brief Adds a Light on this medel primitive + * + * @param[in] light Added light. + * @param[in] lightIndex Index of added light. + */ + void AddLight(Scene3D::Light light, uint32_t lightIndex); + + /** + * @brief Removes a light at the lightIndex of this model primitive + * + * @param[in] lightIndex Index of added light. + */ + void RemoveLight(uint32_t lightIndex); + + /** * @brief Sets the blend shape data for this model primitive. * * @param[in] data The blend shape data to set. @@ -197,9 +213,9 @@ private: private: // Delete copy & move operator - ModelPrimitive(const ModelPrimitive&) = delete; - ModelPrimitive(ModelPrimitive&&) = delete; - ModelPrimitive& operator=(const ModelPrimitive& rhs) = delete; + ModelPrimitive(const ModelPrimitive&) = delete; + ModelPrimitive(ModelPrimitive&&) = delete; + ModelPrimitive& operator=(const ModelPrimitive& rhs) = delete; ModelPrimitive& operator=(ModelPrimitive&& rhs) noexcept = delete; private: @@ -212,6 +228,10 @@ private: Dali::TextureSet mTextureSet; Dali::Scene3D::Material mMaterial; + // Light + std::vector mLights; + int32_t mLightCount{0}; + // For IBL Dali::Texture mSpecularTexture; Dali::Texture mDiffuseTexture; diff --git a/dali-scene3d/public-api/controls/scene-view/scene-view.cpp b/dali-scene3d/public-api/controls/scene-view/scene-view.cpp index 68bb529..0e756d0 100644 --- a/dali-scene3d/public-api/controls/scene-view/scene-view.cpp +++ b/dali-scene3d/public-api/controls/scene-view/scene-view.cpp @@ -117,6 +117,11 @@ float SceneView::GetImageBasedLightScaleFactor() const return GetImpl(*this).GetImageBasedLightScaleFactor(); } +uint32_t SceneView::GetActivatedLightCount() const +{ + return GetImpl(*this).GetActivatedLightCount(); +} + void SceneView::UseFramebuffer(bool useFramebuffer) { GetImpl(*this).UseFramebuffer(useFramebuffer); diff --git a/dali-scene3d/public-api/controls/scene-view/scene-view.h b/dali-scene3d/public-api/controls/scene-view/scene-view.h index 3866879..4d5857a 100644 --- a/dali-scene3d/public-api/controls/scene-view/scene-view.h +++ b/dali-scene3d/public-api/controls/scene-view/scene-view.h @@ -295,6 +295,14 @@ public: float GetImageBasedLightScaleFactor() const; /** + * @brief Gets Enabled Light Count of this SceneView + * + * @SINCE_2_2.32 + * @return The number of enabled light count; + */ + uint32_t GetActivatedLightCount() const; + + /** * @brief Sets whether to use FBO or not for the SceneView. * If useFramebuffer is true, rendering result of SceneView is drawn on FBO and it is mapping on this SceneView plane. * If useFramebuffer is false, each item in SceneView is rendered on window directly. diff --git a/dali-scene3d/public-api/file.list b/dali-scene3d/public-api/file.list index a15ea78..1729012 100644 --- a/dali-scene3d/public-api/file.list +++ b/dali-scene3d/public-api/file.list @@ -3,6 +3,7 @@ set(scene3d_public_api_dir "${scene3d_dir}/public-api") set(scene3d_src_files ${scene3d_src_files} ${scene3d_public_api_dir}/controls/model/model.cpp ${scene3d_public_api_dir}/controls/scene-view/scene-view.cpp + ${scene3d_public_api_dir}/light/light.cpp ${scene3d_public_api_dir}/loader/alpha-function-helper.cpp ${scene3d_public_api_dir}/loader/animated-property.cpp ${scene3d_public_api_dir}/loader/animation-definition.cpp diff --git a/dali-scene3d/public-api/light/light.cpp b/dali-scene3d/public-api/light/light.cpp new file mode 100644 index 0000000..91d87c1 --- /dev/null +++ b/dali-scene3d/public-api/light/light.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 Samsung Electronics 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. + * + */ + +// CLASS HEADER +#include + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +namespace Scene3D +{ +Light::Light() +{ +} + +Light::Light(const Light& light) = default; + +Light::Light(Light&& rhs) = default; + +Light& Light::operator=(const Light& light) = default; + +Light& Light::operator=(Light&& rhs) = default; + +Light::~Light() +{ +} + +Light Light::New() +{ + return Internal::Light::New(); +} + +Light Light::DownCast(BaseHandle handle) +{ + Light result; + + CustomActor custom = Dali::CustomActor::DownCast(handle); + if(custom) + { + CustomActorImpl& customImpl = custom.GetImplementation(); + + Internal::Light* impl = dynamic_cast(&customImpl); + + if(impl) + { + result = Light(customImpl.GetOwner()); + } + } + + return result; +} + +void Light::Enable(bool enable) +{ + Internal::GetImplementation(*this).Enable(enable); +} + +bool Light::IsEnabled() const +{ + return Internal::GetImplementation(*this).IsEnabled(); +} + +uint32_t Light::GetMaximumEnabledLightCount() +{ + return Internal::Light::GetMaximumEnabledLightCount(); +} + +Light::Light(Internal::Light& implementation) +: CustomActor(implementation) +{ +} + +Light::Light(Dali::Internal::CustomActor* internal) +: CustomActor(internal) +{ + // Can have a NULL pointer so we only need to check if the internal implementation is our class + // when there is a value. + if(internal) + { + DALI_ASSERT_DEBUG(dynamic_cast(&CustomActor(internal).GetImplementation())); + } +} + +} // namespace Scene3D + +} // namespace Dali diff --git a/dali-scene3d/public-api/light/light.h b/dali-scene3d/public-api/light/light.h new file mode 100644 index 0000000..1db757e --- /dev/null +++ b/dali-scene3d/public-api/light/light.h @@ -0,0 +1,187 @@ +#ifndef DALI_SCENE3D_LIGHT_H +#define DALI_SCENE3D_LIGHT_H + +/* + * Copyright (c) 2023 Samsung Electronics 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. + * + */ + +// EXTERNAL INCLUDES +#include +#include + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +namespace Scene3D +{ +namespace Internal DALI_INTERNAL +{ +class Light; +} + +/** + * @addtogroup dali_toolkit_controls_light + * @{ + */ + +/** + * @brief This class is to define 3D Light source. + * Currently this Light class supports Directional Light that lits every position from the same direction. (e.g, Sun light) + * If a Light object is added on SceneView, the 3D objects in the SceneView are shined the Light. + * DALi Scene3D limits the maximum enabled light count per each SceneView. + * Currently the maximum number is set to 5, and it can be retrieved by using GetMaximumEnabledLightCount(). + * If more than 5 enabled Light objects are added on SceneView, SceneView turns on only 5 lights in the order the lights were added. + * This Light can be added to SceneView directly but also it can be added on other Actor. + * When a parent actor is added to a SceneView, its Light behaves in the SceneView the same as if it were added directly to the SceneView. + * @note Light inherits Actor, so Light color and direction can be controlled by setting Actor's COLOR and ORIENTATION Property. + * Dali::DevelActor::LookAt() method can be used to set light direction easily. + * @note Default light direction is to Z-axis + * @code + * Scene3D::SceneView sceneView = Scene3D::SceneView::New(); + * Scene3D::Lightlight = Scene3D::Light::New(); + * light.SetProperty(Dali::Actor::Property::COLOR, Color::BROWN); + * Dali::DevelActor::LookAt(light, Vector3(1.0f, 1.0f, 1.0f)); + * sceneView.Add(light); + * @endcode + * @SINCE_2_2.32 + */ +class DALI_SCENE3D_API Light : public Dali::CustomActor +{ +public: + /** + * @brief Create an initialized Light. + * + * @SINCE_2_2.32 + * @return A handle to a newly allocated Dali resource + */ + static Light New(); + + /** + * @brief Creates an uninitialized Light. + * + * Only derived versions can be instantiated. Calling member + * functions with an uninitialized Dali::Object is not allowed. + * + * @SINCE_2_2.32 + */ + Light(); + + /** + * @brief Destructor. + * + * This is non-virtual since derived Handle types must not contain data or virtual methods. + * + * @SINCE_2_2.32 + */ + ~Light(); + + /** + * @brief Copy constructor. + * + * @SINCE_2_2.32 + * @param[in] light Handle to an object + */ + Light(const Light& light); + + /** + * @brief Move constructor + * + * @SINCE_2_2.32 + * @param[in] rhs A reference to the moved handle + */ + Light(Light&& rhs); + + /** + * @brief Assignment operator. + * + * @SINCE_2_2.32 + * @param[in] light Handle to an object + * @return reference to this + */ + Light& operator=(const Light& light); + + /** + * @brief Move assignment + * + * @SINCE_2_2.32 + * @param[in] rhs A reference to the moved handle + * @return A reference to this + */ + Light& operator=(Light&& rhs); + + /** + * @brief Downcasts an Object handle to Light. + * + * If handle points to a Light, the downcast produces valid handle. + * If not, the returned handle is left uninitialized. + * + * @SINCE_2_2.32 + * @param[in] handle Handle to an object + * @return Handle to a Light or an uninitialized handle + */ + static Light DownCast(BaseHandle handle); + + /** + * @brief Enables this light. + * The Light is turned on when the Light object is added on SceneView and enabled. + * @SINCE_2_2.32 + * @note SceneView can turn on only up to maximum enabled light count that can be retrieved by GetMaximumEnabledLightCount(). + * @param[in] enable True to make this Light enable. + */ + void Enable(bool enable); + + /** + * @brief Checks whether this light is enabled or not. + * @SINCE_2_2.32 + * @return True if this light is enabled. + */ + bool IsEnabled() const; + + /** + * @brief Retrieves maximum enabled light count. + * @SINCE_2_2.32 + * @return The number of maximum enabled light count. + */ + static uint32_t GetMaximumEnabledLightCount(); + +public: // Not intended for application developers + /// @cond internal + /** + * @brief Creates a handle using the Toolkit::Internal implementation. + * + * @param[in] implementation The Control implementation + */ + DALI_INTERNAL Light(Internal::Light& implementation); + + /** + * @brief Allows the creation of this Control from an Internal::CustomActor pointer. + * + * @param[in] internal A pointer to the internal CustomActor + */ + DALI_INTERNAL Light(Dali::Internal::CustomActor* internal); + /// @endcond +}; + +/** + * @} + */ +} // namespace Scene3D + +} // namespace Dali + +#endif // DALI_SCENE3D_LIGHT_H diff --git a/dali-scene3d/public-api/loader/node-definition.cpp b/dali-scene3d/public-api/loader/node-definition.cpp index c00a87b..16d0189 100644 --- a/dali-scene3d/public-api/loader/node-definition.cpp +++ b/dali-scene3d/public-api/loader/node-definition.cpp @@ -18,13 +18,15 @@ // CLASS HEADER #include +// EXTERNAL INCLUDES +#include + // INTERNAL INCLUDES #include #include #include #include - -#include +#include namespace Dali { @@ -341,6 +343,21 @@ void ModelRenderable::OnCreate(const NodeDefinition& nodeDefinition, NodeDefinit renderer.RegisterProperty(IBL_INTENSITY_STRING.data(), resources.mEnvironmentMaps[envIndex].first.mIblIntensity); renderer.RegisterProperty(IBL_Y_DIRECTION.data(), resources.mEnvironmentMaps[envIndex].first.mYDirection); + std::string lightCountPropertyName(Scene3D::Internal::Light::GetLightCountUniformName()); + renderer.RegisterProperty(lightCountPropertyName, 0); + + uint32_t maxLightCount = Scene3D::Internal::Light::GetMaximumEnabledLightCount(); + for(uint32_t i = 0; i < maxLightCount; ++i) + { + std::string lightDirectionPropertyName(Scene3D::Internal::Light::GetLightDirectionUniformName()); + lightDirectionPropertyName += "[" + std::to_string(i) + "]"; + renderer.RegisterProperty(lightDirectionPropertyName, Vector3::ZAXIS); + + std::string lightColorPropertyName(Scene3D::Internal::Light::GetLightColorUniformName()); + lightColorPropertyName += "[" + std::to_string(i) + "]"; + renderer.RegisterProperty(lightColorPropertyName, Vector3(Color::WHITE)); + } + node.SetProperty(Actor::Property::COLOR, mColor); { -- 2.7.4