diff --git a/Editor/Qml/EButton.qml b/Editor/Qml/EButton.qml
index 0353c1a70..5303bd464 100644
--- a/Editor/Qml/EButton.qml
+++ b/Editor/Qml/EButton.qml
@@ -2,43 +2,67 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
-Button {
+Item {
enum Style {
Primary,
- Secondary,
- Disabled,
- Outline,
- Dashed
+ Secondary
}
- function getBackgroundColor(style, focus)
- {
- if (style === EButton.Style.Secondary) {
- return focus ? ETheme.secondaryFocusColor : ETheme.secondaryColor;
- } else if (style === EButton.Style.Disabled) {
- return ETheme.disabledColor;
- }
- // TODO more
- return focus ? ETheme.primaryFocusColor : ETheme.primaryColor;
+ enum Size {
+ Small,
+ Middle,
+ Large
+ }
+
+ enum Shape {
+ Rect,
+ Round
}
+ property string text: ''
+ property bool disabled: false
property int style: EButton.Style.Primary
+ property int size: EButton.Size.Middle
+ property int shape: EButton.Shape.Rect
+ signal clicked()
- // TODO disable onclick when disabled
+ id: 'root'
+ implicitWidth: btnWidget.implicitWidth
+ implicitHeight: btnWidget.implicitHeight
- contentItem: Text {
- text: parent.text
- font.pixelSize: ETheme.contentFontSize
- font.family: ETheme.fontFamily
- color: ETheme.fontColor
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- elide: Text.ElideNone
- }
+ Button {
+ id: 'btnWidget'
+ enabled: !root.disabled
+ onClicked: root.clicked()
+ leftPadding: 10
+ rightPadding: 10
+ topPadding: 5 + 2 * (root.size - 1)
+ bottomPadding: 5 + 2 * (root.size - 1)
+ width: root.width
- background: Rectangle {
- implicitWidth: 50
- color: getBackgroundColor(style, parent.down)
- radius: 10
+ contentItem: Text {
+ text: root.text
+ font.pixelSize: ETheme.contentFontSize
+ font.family: ETheme.fontFamily
+ color: ETheme.fontColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideNone
+ }
+
+ background: Rectangle {
+ function getBackgroundColor(style, focus, disabled)
+ {
+ if (disabled) {
+ return ETheme.disabledColor;
+ } else if (style === EButton.Style.Secondary) {
+ return focus ? ETheme.secondaryFocusColor : ETheme.secondaryColor;
+ }
+ return focus ? ETheme.primaryFocusColor : ETheme.primaryColor;
+ }
+
+ color: getBackgroundColor(root.style, parent.down, root.disabled)
+ radius: 5 + root.shape * 8
+ }
}
}
diff --git a/Editor/Qml/EText.qml b/Editor/Qml/EText.qml
index 61c244d8b..289e0fbbd 100644
--- a/Editor/Qml/EText.qml
+++ b/Editor/Qml/EText.qml
@@ -1,36 +1,53 @@
import QtQuick
import QtQuick.Controls
-Text {
+Item {
enum Style {
Title1,
Title2,
Title3,
- Content
+ Content,
+ Link,
+ Italic
}
- function getFontBold(style)
- {
- return style !== EText.Style.Content
- }
+ property string text: ''
+ property string href: ''
+ property int style: EText.Style.Content
- function getFontPixelSize(style)
- {
- if (style === EText.Style.Title1) {
- return ETheme.tiele1FontSize;
- } else if (style === EText.Style.Title2) {
- return ETheme.title2FontSize;
- } else if (style === EText.Style.Title3) {
- return ETheme.title3FontSize;
- } else {
- return ETheme.contentFontSize;
+ id: 'root'
+ implicitWidth: textWidget.implicitWidth
+ implicitHeight: textWidget.implicitHeight
+
+ Text {
+ function getText(text, href, style)
+ {
+ return style === EText.Style.Link
+ ? '%3'.arg(href).arg(ETheme.linkFontColor).arg(text)
+ : text;
}
- }
- property int style: EText.Style.Content
+ function getFontPixelSize(style)
+ {
+ if (style === EText.Style.Title1) {
+ return ETheme.tiele1FontSize;
+ } else if (style === EText.Style.Title2) {
+ return ETheme.title2FontSize;
+ } else if (style === EText.Style.Title3) {
+ return ETheme.title3FontSize;
+ } else {
+ return ETheme.contentFontSize;
+ }
+ }
- font.bold: getFontBold(style)
- font.pixelSize: getFontPixelSize(style)
- font.family: ETheme.fontFamily
- color: ETheme.fontColor
+ id: 'textWidget'
+ text: getText(root.text, root.href, root.style)
+ textFormat: root.style === EText.Style.Link ? Text.RichText : Text.PlainText;
+ font.italic: root.style === EText.Style.Italic
+ font.bold: root.style < EText.Style.Content;
+ font.pixelSize: getFontPixelSize(root.style)
+ font.family: ETheme.fontFamily
+ color: ETheme.fontColor
+ onLinkActivated: Qt.openUrlExternally(root.href)
+ }
}
diff --git a/Editor/Qml/ETheme.qml b/Editor/Qml/ETheme.qml
index f6f6f6540..250733e81 100644
--- a/Editor/Qml/ETheme.qml
+++ b/Editor/Qml/ETheme.qml
@@ -10,6 +10,7 @@ QtObject {
property color secondaryFocusColor: Qt.color('#9b6a40')
property color disabledColor: Qt.color('#676563')
property color fontColor: Qt.color('#ecf0f1')
+ property color linkFontColor: Qt.color('#91b9c4')
property FontLoader normalFont: FontLoader { source: Qt.url('Resource/Font/MiSans-Normal.ttf') }
property FontLoader boldFont: FontLoader { source: Qt.url('Resource/Font/MiSans-Bold.ttf') }
diff --git a/Editor/Qml/EWidgetSamples.qml b/Editor/Qml/EWidgetSamples.qml
index 206f7888b..cfce95713 100644
--- a/Editor/Qml/EWidgetSamples.qml
+++ b/Editor/Qml/EWidgetSamples.qml
@@ -6,11 +6,12 @@ Rectangle {
color: ETheme.bgColor
ScrollView {
+ id: 'scrollview'
anchors.fill: parent
+ anchors.margins: 20
ColumnLayout {
- anchors.fill: parent
- anchors.margins: 20
+ width: Math.max(implicitWidth, scrollview.availableWidth)
RowLayout {
Layout.leftMargin: 5
@@ -25,20 +26,87 @@ Rectangle {
Layout.margins: 5
EButton {
- text: 'Basic'
+ text: 'Basic Button'
+ onClicked: {
+ console.log('basic button clicked')
+ }
}
EButton {
style: EButton.Style.Secondary
text: 'Secondary Button'
+ onClicked: {
+ console.log('secondary button clicked')
+ }
}
EButton {
- style: EButton.Style.Disabled
text: 'Disabled Button'
+ disabled: true
+ onClicked: {
+ console.log('disabled button clicked')
+ }
}
+ }
+
+ RowLayout {
+ Layout.margins: 5
+
+ EButton {
+ text: 'Large Button'
+ size: EButton.Size.Large
+ onClicked: {
+ console.log('large button clicked')
+ }
+ }
+
+ EButton {
+ text: 'Middle Button'
+ size: EButton.Size.Middle
+ onClicked: {
+ console.log('middle button clicked')
+ }
+ }
+
+ EButton {
+ text: 'Small Button'
+ size: EButton.Size.Small
+ onClicked: {
+ console.log('small button clicked')
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.margins: 5
+
+ EButton {
+ text: 'Rect Button'
+ shape: EButton.Shape.Rect
+ onClicked: {
+ console.log('rect button clicked')
+ }
+ }
+
+ EButton {
+ text: 'Round Button'
+ shape: EButton.Shape.Round
+ onClicked: {
+ console.log('round button clicked')
+ }
+ }
+ }
- // TODO icon button
+ RowLayout {
+ Layout.margins: 5
+
+ EButton {
+ text: 'Block Button'
+ Layout.fillWidth: true
+ onClicked: {
+ console.log('block button clicked')
+ }
+ }
}
RowLayout {
@@ -68,14 +136,26 @@ Rectangle {
text: 'Title3'
style: EText.Style.Title3
}
+ }
+
+ RowLayout {
+ Layout.leftMargin: 5
EText {
- text: 'Content'
+ text: 'Basic Content'
style: EText.Style.Content
}
- // TODO superlink text
- // TODO button text
+ EText {
+ text: 'Sample Link'
+ href: 'https://github.com/ExplosionEngine/Explosion'
+ style: EText.Style.Link
+ }
+
+ EText {
+ text: 'Italic Content'
+ style: EText.Style.Italic
+ }
}
}
}
diff --git a/Engine/Source/Common/Include/Common/Container.h b/Engine/Source/Common/Include/Common/Container.h
index 2bd6e970e..d72084976 100644
--- a/Engine/Source/Common/Include/Common/Container.h
+++ b/Engine/Source/Common/Include/Common/Container.h
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
diff --git a/Engine/Source/Runtime/Include/Runtime/Component/Transform.h b/Engine/Source/Runtime/Include/Runtime/Component/Transform.h
index 6a56881cd..17312532c 100644
--- a/Engine/Source/Runtime/Include/Runtime/Component/Transform.h
+++ b/Engine/Source/Runtime/Include/Runtime/Component/Transform.h
@@ -10,31 +10,46 @@
#include
namespace Runtime {
- struct RUNTIME_API EClass() Transform final {
- EClassBody(Transform)
+ struct RUNTIME_API EClass() WorldTransform final {
+ EClassBody(WorldTransform)
- Transform();
- explicit Transform(const Common::FTransform& inLocalToWorld);
+ WorldTransform();
+ explicit WorldTransform(Common::FTransform inLocalToWorld);
EProperty() Common::FTransform localToWorld;
};
- struct RUNTIME_API EClass() ChildOfConstraint final {
- EClassBody(ChildOfConstraint)
+ // must be used with Hierarchy and WorldTransform
+ struct RUNTIME_API EClass() LocalTransform final {
+ EClassBody(LocalTransform)
- ChildOfConstraint();
- explicit ChildOfConstraint(Entity inParent, const Common::FTransform& inLocalToParent);
+ LocalTransform();
+ explicit LocalTransform(Common::FTransform inLocalToParent);
- EProperty() Entity parent;
EProperty() Common::FTransform localToParent;
};
- struct RUNTIME_API EClass() CopyConstraint final {
- EClassBody(CopyConstraint)
+ struct RUNTIME_API EClass() Hierarchy final {
+ EClassBody(Hierarchy)
+
+ Hierarchy();
- CopyConstraint();
- explicit CopyConstraint(Entity inTarget);
+ EProperty() Entity parent;
+ EProperty() Entity firstChild;
+ EProperty() Entity prevBro;
+ EProperty() Entity nextBro;
+ };
- EProperty() Entity target;
+ class HierarchyUtils {
+ public:
+ using TraverseFunc = std::function;
+
+ static bool HasParent(ECRegistry& inRegistry, Entity inTarget);
+ static bool HasBro(ECRegistry& inRegistry, Entity inTarget);
+ static bool HasChildren(ECRegistry& inRegistry, Entity inTarget);
+ static void AttachToParent(ECRegistry& inRegistry, Entity inChild, Entity inParent);
+ static void DetachFromParent(ECRegistry& inRegistry, Entity inChild);
+ static void TraverseChildren(ECRegistry& inRegistry, Entity inParent, const TraverseFunc& inFunc);
+ static void TraverseChildrenRecursively(ECRegistry& inRegistry, Entity inParent, const TraverseFunc& inFunc);
};
}
diff --git a/Engine/Source/Runtime/Include/Runtime/ECS.h b/Engine/Source/Runtime/Include/Runtime/ECS.h
index 65eeaa476..d141b134d 100644
--- a/Engine/Source/Runtime/Include/Runtime/ECS.h
+++ b/Engine/Source/Runtime/Include/Runtime/ECS.h
@@ -324,7 +324,10 @@ namespace Runtime {
Observer& ObRemoved(CompClass inClass);
size_t Size() const;
void Each(const EntityTraverseFunc& inFunc) const;
+ void EachThenClear(const EntityTraverseFunc& inFunc);
void Clear();
+ const std::vector& All() const;
+ std::vector Pop();
void UnbindAll();
ConstIter Begin() const;
ConstIter End() const;
@@ -360,6 +363,9 @@ namespace Runtime {
void ClearUpdated();
void ClearRemoved();
void Clear();
+ auto& Constructed();
+ auto& Updated();
+ auto& Removed();
const auto& Constructed() const;
const auto& Updated() const;
const auto& Removed() const;
@@ -430,6 +436,7 @@ namespace Runtime {
void ResetTransients();
// entity
+ // TODO create with hint
Entity Create();
void Destroy(Entity inEntity);
bool Valid(Entity inEntity) const;
@@ -982,6 +989,24 @@ namespace Runtime {
ClearRemoved();
}
+ template
+ auto& EventsObserver::Constructed()
+ {
+ return constructedObserver;
+ }
+
+ template
+ auto& EventsObserver::Updated()
+ {
+ return updatedObserver;
+ }
+
+ template
+ auto& EventsObserver::Removed()
+ {
+ return removedObserver;
+ }
+
template
const auto& EventsObserver::Constructed() const
{
diff --git a/Engine/Source/Runtime/Include/Runtime/System/Scene.h b/Engine/Source/Runtime/Include/Runtime/System/Scene.h
index 00e7c3e9e..834d02ec9 100644
--- a/Engine/Source/Runtime/Include/Runtime/System/Scene.h
+++ b/Engine/Source/Runtime/Include/Runtime/System/Scene.h
@@ -27,9 +27,9 @@ namespace Runtime {
private:
template using SPMap = std::unordered_map>;
- static Render::LightSceneProxy MakeLightSceneProxy(const DirectionalLight& inDirectionalLight, const Transform* inTransform);
- static Render::LightSceneProxy MakeLightSceneProxy(const PointLight& inPointLight, const Transform* inTransform);
- static Render::LightSceneProxy MakeLightSceneProxy(const SpotLight& inSpotLight, const Transform* inTransform);
+ static Render::LightSceneProxy MakeLightSceneProxy(const DirectionalLight& inDirectionalLight, const WorldTransform* inTransform);
+ static Render::LightSceneProxy MakeLightSceneProxy(const PointLight& inPointLight, const WorldTransform* inTransform);
+ static Render::LightSceneProxy MakeLightSceneProxy(const SpotLight& inSpotLight, const WorldTransform* inTransform);
template void EmplaceLightSceneProxy(Entity inEntity);
template void UpdateLightSceneProxy(Entity inEntity);
template void UpdateTransformForSceneProxy(SPMap& inSceneProxyMap, Entity inEntity, bool inWithScale = true);
@@ -49,13 +49,13 @@ namespace Runtime {
template
void SceneSystem::EmplaceLightSceneProxy(Entity inEntity)
{
- lightSceneProxies.emplace(inEntity, scene->AddLight(MakeLightSceneProxy(registry.Get(inEntity), registry.Find(inEntity))));
+ lightSceneProxies.emplace(inEntity, scene->AddLight(MakeLightSceneProxy(registry.Get(inEntity), registry.Find(inEntity))));
}
template
void SceneSystem::UpdateLightSceneProxy(Entity inEntity)
{
- *lightSceneProxies.at(inEntity) = MakeLightSceneProxy(registry.Get(inEntity), registry.Find(inEntity));
+ *lightSceneProxies.at(inEntity) = MakeLightSceneProxy(registry.Get(inEntity), registry.Find(inEntity));
}
template
@@ -66,7 +66,7 @@ namespace Runtime {
return;
}
- if (const Transform* transform = registry.Find(inEntity);
+ if (const WorldTransform* transform = registry.Find(inEntity);
transform == nullptr) {
iter->second->localToWorld = Common::FMat4x4Consts::identity;
} else {
diff --git a/Engine/Source/Runtime/Include/Runtime/System/Transform.h b/Engine/Source/Runtime/Include/Runtime/System/Transform.h
new file mode 100644
index 000000000..c20ab92ce
--- /dev/null
+++ b/Engine/Source/Runtime/Include/Runtime/System/Transform.h
@@ -0,0 +1,29 @@
+//
+// Created by johnk on 2025/1/21.
+//
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace Runtime {
+ class RUNTIME_API EClass() TransformSystem final : public System {
+ EPolyClassBody(TransformSystem)
+
+ public:
+ explicit TransformSystem(ECRegistry& inRegistry);
+ ~TransformSystem() override;
+
+ NonCopyable(TransformSystem)
+ NonMovable(TransformSystem)
+
+ void Tick(float inDeltaTimeMs) override;
+
+ private:
+ Observer worldTransformUpdatedObserver;
+ Observer localTransformUpdatedObserver;
+ };
+}
diff --git a/Engine/Source/Runtime/Src/Component/Transform.cpp b/Engine/Source/Runtime/Src/Component/Transform.cpp
index f5a6e731f..29e611655 100644
--- a/Engine/Source/Runtime/Src/Component/Transform.cpp
+++ b/Engine/Source/Runtime/Src/Component/Transform.cpp
@@ -2,28 +2,115 @@
// Created by johnk on 2024/10/14.
//
+#include
+
#include
namespace Runtime {
- Transform::Transform() = default;
+ WorldTransform::WorldTransform() = default;
+
+ WorldTransform::WorldTransform(Common::FTransform inLocalToWorld)
+ : localToWorld(std::move(inLocalToWorld))
+ {
+ }
+
+ LocalTransform::LocalTransform() = default;
+
+ LocalTransform::LocalTransform(Common::FTransform inLocalToParent)
+ : localToParent(std::move(inLocalToParent))
+ {
+ }
+
+ Hierarchy::Hierarchy()
+ : parent(entityNull)
+ , firstChild(entityNull)
+ , prevBro(entityNull)
+ , nextBro(entityNull)
+ {
+ }
- Transform::Transform(const Common::FTransform& inLocalToWorld)
- : localToWorld(inLocalToWorld)
+ bool HierarchyUtils::HasParent(ECRegistry& inRegistry, Entity inTarget)
{
+ const auto& hierarchy = inRegistry.Get(inTarget);
+ return hierarchy.parent != entityNull;
}
- ChildOfConstraint::ChildOfConstraint() = default;
+ bool HierarchyUtils::HasBro(ECRegistry& inRegistry, Entity inTarget)
+ {
+ const auto& hierarchy = inRegistry.Get(inTarget);
+ return hierarchy.prevBro != entityNull || hierarchy.nextBro != entityNull;
+ }
+
+ bool HierarchyUtils::HasChildren(ECRegistry& inRegistry, Entity inTarget)
+ {
+ const auto& hierarchy = inRegistry.Get(inTarget);
+ return hierarchy.firstChild != entityNull;
+ }
- ChildOfConstraint::ChildOfConstraint(Entity inParent, const Common::FTransform& inLocalToParent)
- : parent(inParent)
- , localToParent(inLocalToParent)
+ void HierarchyUtils::AttachToParent(ECRegistry& inRegistry, Entity inChild, Entity inParent)
{
+ Assert(!HasParent(inRegistry, inChild) && !HasBro(inRegistry, inChild));
+ auto& childHierarchy = inRegistry.Get(inChild);
+ auto& parentHierarchy = inRegistry.Get(inParent);
+ childHierarchy.parent = inParent;
+ childHierarchy.nextBro = parentHierarchy.firstChild;
+ if (parentHierarchy.firstChild != entityNull) {
+ auto& oldFirstChildHierarchy = inRegistry.Get(parentHierarchy.firstChild);
+ oldFirstChildHierarchy.prevBro = inChild;
+ }
+ parentHierarchy.firstChild = inChild;
}
- CopyConstraint::CopyConstraint() = default;
+ void HierarchyUtils::DetachFromParent(ECRegistry& inRegistry, Entity inChild)
+ {
+ Assert(HasParent(inRegistry, inChild));
+ auto& childHierarchy = inRegistry.Get(inChild);
+ auto& parentHierarchy = inRegistry.Get(childHierarchy.parent);
+
+ if (parentHierarchy.firstChild == inChild) {
+ Assert(childHierarchy.prevBro == entityNull);
+ parentHierarchy.firstChild = childHierarchy.nextBro;
+ if (childHierarchy.nextBro != entityNull) {
+ auto& nextBroHierarchy = inRegistry.Get(childHierarchy.nextBro);
+ nextBroHierarchy.prevBro = entityNull;
+ }
+ childHierarchy.nextBro = entityNull;
+ } else {
+ Assert(childHierarchy.prevBro != entityNull);
+ const auto prevBro = childHierarchy.prevBro;
+ const auto nextBro = childHierarchy.nextBro;
+ auto& prevBroHierarchy = inRegistry.Get(prevBro);
+ Assert(prevBroHierarchy.nextBro == inChild);
+ prevBroHierarchy.nextBro = nextBro;
+ if (nextBro != entityNull) {
+ auto& nextBroHierarchy = inRegistry.Get(nextBro);
+ Assert(nextBroHierarchy.prevBro == inChild);
+ nextBroHierarchy.prevBro = prevBro;
+ }
+ childHierarchy.prevBro = entityNull;
+ childHierarchy.nextBro = entityNull;
+ }
+ childHierarchy.parent = entityNull;
+ }
+
+ void HierarchyUtils::TraverseChildren(ECRegistry& inRegistry, Entity inParent, const TraverseFunc& inFunc)
+ {
+ const auto& parentHierarchy = inRegistry.Get(inParent);
+ for (auto child = parentHierarchy.firstChild; child != entityNull;) {
+ const auto& childHierarchy = inRegistry.Get(child);
+ inFunc(child, inParent);
+ child = childHierarchy.nextBro;
+ }
+ }
- CopyConstraint::CopyConstraint(Entity inTarget)
- : target(inTarget)
+ void HierarchyUtils::TraverseChildrenRecursively(ECRegistry& inRegistry, Entity inParent, const TraverseFunc& inFunc) // NOLINT
{
+ const auto& parentHierarchy = inRegistry.Get(inParent);
+ for (auto child = parentHierarchy.firstChild; child != entityNull;) {
+ const auto& childHierarchy = inRegistry.Get(child);
+ inFunc(child, inParent);
+ TraverseChildrenRecursively(inRegistry, child, inFunc);
+ child = childHierarchy.nextBro;
+ }
}
}
diff --git a/Engine/Source/Runtime/Src/ECS.cpp b/Engine/Source/Runtime/Src/ECS.cpp
index a9b5dc59d..6e28987e1 100644
--- a/Engine/Source/Runtime/Src/ECS.cpp
+++ b/Engine/Source/Runtime/Src/ECS.cpp
@@ -413,12 +413,14 @@ namespace Runtime {
RuntimeFilter& RuntimeFilter::IncludeDyn(CompClass inClass)
{
+ Assert(!includes.contains(inClass));
includes.emplace(inClass);
return *this;
}
RuntimeFilter& RuntimeFilter::ExcludeDyn(CompClass inClass)
{
+ Assert(!excludes.contains(inClass));
excludes.emplace(inClass);
return *this;
}
@@ -460,11 +462,29 @@ namespace Runtime {
}
}
+ void Observer::EachThenClear(const EntityTraverseFunc& inFunc)
+ {
+ Each(inFunc);
+ Clear();
+ }
+
void Observer::Clear()
{
entities.clear();
}
+ const std::vector& Observer::All() const
+ {
+ return entities;
+ }
+
+ std::vector Observer::Pop()
+ {
+ auto result = entities;
+ Clear();
+ return result;
+ }
+
void Observer::UnbindAll()
{
for (auto& deleter : receiverHandles | std::views::values) {
diff --git a/Engine/Source/Runtime/Src/System/Scene.cpp b/Engine/Source/Runtime/Src/System/Scene.cpp
index 8009b3106..5bdae2d07 100644
--- a/Engine/Source/Runtime/Src/System/Scene.cpp
+++ b/Engine/Source/Runtime/Src/System/Scene.cpp
@@ -17,8 +17,8 @@ namespace Runtime {
, spotLightsObserver(inRegistry.EventsObserver())
{
transformUpdatedObserver
- .ObConstructed()
- .ObUpdated();
+ .ObConstructed()
+ .ObUpdated();
}
SceneSystem::~SceneSystem() = default;
@@ -42,7 +42,7 @@ namespace Runtime {
spotLightsObserver.Clear();
}
- Render::LightSceneProxy SceneSystem::MakeLightSceneProxy(const DirectionalLight& inDirectionalLight, const Transform* inTransform)
+ Render::LightSceneProxy SceneSystem::MakeLightSceneProxy(const DirectionalLight& inDirectionalLight, const WorldTransform* inTransform)
{
Render::LightSceneProxy result {};
result.type = Render::LightType::directional;
@@ -53,7 +53,7 @@ namespace Runtime {
return result;
}
- Render::LightSceneProxy SceneSystem::MakeLightSceneProxy(const PointLight& inPointLight, const Transform* inTransform)
+ Render::LightSceneProxy SceneSystem::MakeLightSceneProxy(const PointLight& inPointLight, const WorldTransform* inTransform)
{
Render::LightSceneProxy result {};
result.type = Render::LightType::point;
@@ -66,7 +66,7 @@ namespace Runtime {
return result;
}
- Render::LightSceneProxy SceneSystem::MakeLightSceneProxy(const SpotLight& inSpotLight, const Transform* inTransform)
+ Render::LightSceneProxy SceneSystem::MakeLightSceneProxy(const SpotLight& inSpotLight, const WorldTransform* inTransform)
{
Render::LightSceneProxy result {};
result.type = Render::LightType::spot;
diff --git a/Engine/Source/Runtime/Src/System/Transform.cpp b/Engine/Source/Runtime/Src/System/Transform.cpp
new file mode 100644
index 000000000..c5dec4307
--- /dev/null
+++ b/Engine/Source/Runtime/Src/System/Transform.cpp
@@ -0,0 +1,86 @@
+//
+// Created by johnk on 2025/1/21.
+//
+
+#include
+
+namespace Runtime {
+ TransformSystem::TransformSystem(ECRegistry& inRegistry)
+ : System(inRegistry)
+ , worldTransformUpdatedObserver(registry.Observer())
+ , localTransformUpdatedObserver(registry.Observer())
+ {
+ worldTransformUpdatedObserver.ObUpdated();
+ localTransformUpdatedObserver.ObUpdated();
+ }
+
+ TransformSystem::~TransformSystem() = default;
+
+ void TransformSystem::Tick(float inDeltaTimeMs)
+ {
+ // Step0: classify the updated entities
+ std::vector pendingUpdateLocalTransforms;
+ std::vector pendingUpdateChildrenWorldTransforms;
+ std::vector pendingUpdateSelfAndChildrenWorldTransforms;
+
+ pendingUpdateLocalTransforms.reserve(worldTransformUpdatedObserver.Size());
+ pendingUpdateChildrenWorldTransforms.reserve(worldTransformUpdatedObserver.Size());
+ worldTransformUpdatedObserver.EachThenClear([&](Entity e) -> void {
+ if (registry.Has(e) && registry.Has(e) && HierarchyUtils::HasParent(registry, e)) {
+ pendingUpdateLocalTransforms.emplace_back(e);
+ }
+
+ if (registry.Has(e) && HierarchyUtils::HasChildren(registry, e)) {
+ pendingUpdateChildrenWorldTransforms.emplace_back(e);
+ }
+ });
+
+ pendingUpdateSelfAndChildrenWorldTransforms.reserve(localTransformUpdatedObserver.Size());
+ localTransformUpdatedObserver.EachThenClear([&](Entity e) -> void {
+ if (registry.Has(e) && registry.Has(e) && HierarchyUtils::HasParent(registry, e)) {
+ pendingUpdateSelfAndChildrenWorldTransforms.emplace_back(e);
+ }
+ });
+
+ // Step1: update local transforms
+ for (auto e : pendingUpdateLocalTransforms) {
+ auto& localTransform = registry.Get(e);
+ const auto& worldTransform = registry.Get(e);
+ const auto& hierarchy = registry.Get(e);
+ const auto& parentWorldTransform = registry.Get(hierarchy.parent);
+
+ const auto& localToWorldMatrix = worldTransform.localToWorld.GetTransformMatrix();
+ const auto& parentLocalToWorldMatrix = parentWorldTransform.localToWorld.GetTransformMatrix();
+ // TODO localTransform.localToParent = parentLocalToWorldMatrix.Inverse() * localToWorldMatrix
+ }
+
+ // Step2: update world transforms
+ const auto updateWorldByLocal = [&](Entity child, Entity parent) -> void {
+ if (!registry.Has(child) || !registry.Has(child) || !registry.Has(parent)) {
+ return;
+ }
+
+ auto& childWorldTransform = registry.Get(child);
+ const auto& childLocalTransform = registry.Get(child);
+ const auto& parentWorldTransform = registry.Get(parent);
+
+ const auto& parentLocalToWorldMatrix = parentWorldTransform.localToWorld.GetTransformMatrix();
+ const auto& childLocalToParentMatrix = childLocalTransform.localToParent.GetTransformMatrix();
+ // TODO childWorldTransform.localToWorld = parentLocalToWorldMatrix * childLocalToParentMatrix
+ };
+
+ for (const auto e : pendingUpdateChildrenWorldTransforms) {
+ HierarchyUtils::TraverseChildrenRecursively(registry, e, [&](Entity child, Entity parent) -> void {
+ updateWorldByLocal(child, parent);
+ });
+ }
+ for (const auto e : pendingUpdateSelfAndChildrenWorldTransforms) {
+ const auto& hierarchy = registry.Get(e);
+ updateWorldByLocal(e, hierarchy.parent);
+
+ HierarchyUtils::TraverseChildrenRecursively(registry, e, [&](Entity child, Entity parent) -> void {
+ updateWorldByLocal(child, parent);
+ });
+ }
+ }
+}