From 5a0c423ddd0b58bca16f71ee753cec8697443cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Thu, 14 Jul 2022 15:21:28 +0200 Subject: [PATCH] Updates: - editor: quicksave, multiple scenes support, gui changes, additional shortcuts - physics: improvements for handling multiple scenes, and removal of physics objects - scripting: ability to override global scene and camera with custom scene and camera from cpp side --- .../WickedEngine-Documentation.md | 2 +- Editor/AnimationWindow.cpp | 19 +- Editor/AnimationWindow.h | 3 +- Editor/CameraWindow.cpp | 73 +-- Editor/CameraWindow.h | 6 +- Editor/DecalWindow.cpp | 9 +- Editor/DecalWindow.h | 1 + Editor/Editor.cpp | 558 ++++++++++-------- Editor/Editor.h | 90 ++- Editor/Editor_SOURCE.vcxitems | 8 - Editor/Editor_SOURCE.vcxitems.filters | 6 - Editor/EmitterWindow.cpp | 17 +- Editor/EmitterWindow.h | 1 + Editor/EnvProbeWindow.cpp | 19 +- Editor/EnvProbeWindow.h | 1 + Editor/ForceFieldWindow.cpp | 17 +- Editor/ForceFieldWindow.h | 1 + Editor/HairParticleWindow.cpp | 13 +- Editor/HairParticleWindow.h | 1 + Editor/IKWindow.cpp | 19 +- Editor/IKWindow.h | 1 + Editor/LayerWindow.cpp | 21 +- Editor/LayerWindow.h | 1 + Editor/LightWindow.cpp | 35 +- Editor/LightWindow.h | 1 + Editor/MaterialWindow.cpp | 81 +-- Editor/MaterialWindow.h | 1 + Editor/MeshWindow.cpp | 51 +- Editor/MeshWindow.h | 1 + Editor/ModelImporter_GLTF.cpp | 2 +- Editor/NameWindow.cpp | 11 +- Editor/NameWindow.h | 1 + Editor/ObjectWindow.cpp | 48 +- Editor/ObjectWindow.h | 2 +- Editor/PaintToolWindow.cpp | 14 +- Editor/PostprocessWindow.cpp | 3 +- Editor/PostprocessWindow.h | 2 + Editor/RendererWindow.cpp | 9 +- Editor/RendererWindow.h | 4 +- Editor/SoundWindow.cpp | 27 +- Editor/SoundWindow.h | 1 + Editor/SpringWindow.cpp | 23 +- Editor/SpringWindow.h | 1 + Editor/TerrainGenerator.h | 2 +- Editor/TransformWindow.cpp | 39 +- Editor/TransformWindow.h | 1 + Editor/Translator.cpp | 8 +- Editor/Translator.h | 1 + Editor/WeatherWindow.cpp | 35 +- Editor/WeatherWindow.h | 2 + Editor/images/arealight.dds | Bin 65664 -> 0 bytes Editor/images/blood1.png | Bin 8547 -> 0 bytes Editor/images/directional_light.dds | Bin 65664 -> 65684 bytes Editor/images/pointlight.dds | Bin 65664 -> 65684 bytes Editor/images/spotlight.dds | Bin 65664 -> 65684 bytes WickedEngine/wiGUI.cpp | 8 + WickedEngine/wiGUI.h | 2 + WickedEngine/wiPhysics.h | 10 +- WickedEngine/wiPhysics_Bullet.cpp | 395 ++++++++----- WickedEngine/wiRenderPath3D_BindLua.h | 3 + WickedEngine/wiRenderer_BindLua.cpp | 4 +- WickedEngine/wiScene.h | 6 +- WickedEngine/wiScene_BindLua.cpp | 35 +- WickedEngine/wiScene_BindLua.h | 9 + WickedEngine/wiVersion.cpp | 2 +- 65 files changed, 1032 insertions(+), 735 deletions(-) delete mode 100644 Editor/images/arealight.dds delete mode 100644 Editor/images/blood1.png diff --git a/Content/Documentation/WickedEngine-Documentation.md b/Content/Documentation/WickedEngine-Documentation.md index e9fd94831..20ee36c2b 100644 --- a/Content/Documentation/WickedEngine-Documentation.md +++ b/Content/Documentation/WickedEngine-Documentation.md @@ -244,7 +244,7 @@ Entity is a number, it can reference components through ComponentManager contain [[Header]](../../WickedEngine/wiScene.h) [[Cpp]](../../WickedEngine/wiScene.cpp) The logical scene representation using the Entity-Component System - GetScene
-Returns a global scene instance. The wi::renderer will use this scene instance to render the scene. The user can create multiple scenes as well, and merge those into the global scene so that those will be rendered as well. +Returns a global scene instance. This is a convenience feature for simple applications that need a single scene. The RenderPath3D will use the global scene by default, but it can be set to a custom scene if needed. - LoadModel()
There are two flavours to this. One of them immediately loads into the global scene. The other loads into a custom scene, which is usefult to manage the contents separately. This function will return an Entity that represents the root transform of the scene - if the attached parameter was true, otherwise it will return INVALID_ENTITY and no root transform will be created. - Pick
diff --git a/Editor/AnimationWindow.cpp b/Editor/AnimationWindow.cpp index f3f306ded..b991eeaaa 100644 --- a/Editor/AnimationWindow.cpp +++ b/Editor/AnimationWindow.cpp @@ -5,8 +5,9 @@ using namespace wi::ecs; using namespace wi::scene; -void AnimationWindow::Create(EditorComponent* editor) +void AnimationWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Animation Window"); SetSize(XMFLOAT2(520, 140)); @@ -21,7 +22,7 @@ void AnimationWindow::Create(EditorComponent* editor) animationsComboBox.SetPos(XMFLOAT2(x, y)); animationsComboBox.SetEnabled(false); animationsComboBox.OnSelect([&](wi::gui::EventArgs args) { - entity = wi::scene::GetScene().animations.GetEntity(args.iValue); + entity = editor->GetCurrentScene().animations.GetEntity(args.iValue); }); animationsComboBox.SetTooltip("Choose an animation clip..."); AddWidget(&animationsComboBox); @@ -31,7 +32,7 @@ void AnimationWindow::Create(EditorComponent* editor) loopedCheckBox.SetSize(XMFLOAT2(hei, hei)); loopedCheckBox.SetPos(XMFLOAT2(150, y += step)); loopedCheckBox.OnClick([&](wi::gui::EventArgs args) { - AnimationComponent* animation = wi::scene::GetScene().animations.GetComponent(entity); + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { animation->SetLooped(args.bValue); @@ -44,7 +45,7 @@ void AnimationWindow::Create(EditorComponent* editor) playButton.SetSize(XMFLOAT2(100, hei)); playButton.SetPos(XMFLOAT2(200, y)); playButton.OnClick([&](wi::gui::EventArgs args) { - AnimationComponent* animation = wi::scene::GetScene().animations.GetComponent(entity); + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { if (animation->IsPlaying()) @@ -64,7 +65,7 @@ void AnimationWindow::Create(EditorComponent* editor) stopButton.SetSize(XMFLOAT2(100, hei)); stopButton.SetPos(XMFLOAT2(310, y)); stopButton.OnClick([&](wi::gui::EventArgs args) { - AnimationComponent* animation = wi::scene::GetScene().animations.GetComponent(entity); + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { animation->Stop(); @@ -76,7 +77,7 @@ void AnimationWindow::Create(EditorComponent* editor) timerSlider.SetSize(XMFLOAT2(250, hei)); timerSlider.SetPos(XMFLOAT2(x, y += step)); timerSlider.OnSlide([&](wi::gui::EventArgs args) { - AnimationComponent* animation = wi::scene::GetScene().animations.GetComponent(entity); + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { animation->timer = args.fValue; @@ -90,7 +91,7 @@ void AnimationWindow::Create(EditorComponent* editor) amountSlider.SetSize(XMFLOAT2(250, hei)); amountSlider.SetPos(XMFLOAT2(x, y += step)); amountSlider.OnSlide([&](wi::gui::EventArgs args) { - AnimationComponent* animation = wi::scene::GetScene().animations.GetComponent(entity); + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { animation->amount = args.fValue; @@ -104,7 +105,7 @@ void AnimationWindow::Create(EditorComponent* editor) speedSlider.SetSize(XMFLOAT2(250, hei)); speedSlider.SetPos(XMFLOAT2(x, y += step)); speedSlider.OnSlide([&](wi::gui::EventArgs args) { - AnimationComponent* animation = wi::scene::GetScene().animations.GetComponent(entity); + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { animation->speed = args.fValue; @@ -125,7 +126,7 @@ void AnimationWindow::Update() { animationsComboBox.ClearItems(); - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); if (!scene.animations.Contains(entity)) { diff --git a/Editor/AnimationWindow.h b/Editor/AnimationWindow.h index e3425d59c..c1444dd5b 100644 --- a/Editor/AnimationWindow.h +++ b/Editor/AnimationWindow.h @@ -7,7 +7,8 @@ class AnimationWindow : public wi::gui::Window { public: void Create(EditorComponent* editor); - + + EditorComponent* editor = nullptr; wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY; wi::gui::ComboBox animationsComboBox; diff --git a/Editor/CameraWindow.cpp b/Editor/CameraWindow.cpp index 3e4989f6a..7a8c4d87c 100644 --- a/Editor/CameraWindow.cpp +++ b/Editor/CameraWindow.cpp @@ -7,32 +7,35 @@ using namespace wi::scene; void CameraWindow::ResetCam() { - move = {}; + if (editor == nullptr) + return; + auto& editorscene = editor->GetCurrentEditorScene(); - Scene& scene = wi::scene::GetScene(); + editorscene.cam_move = {}; - CameraComponent& camera = wi::scene::GetCamera(); + CameraComponent& camera = editorscene.camera; float width = camera.width; float height = camera.height; camera = CameraComponent(); camera.width = width; camera.height = height; - camera_transform.ClearTransform(); - camera_transform.Translate(XMFLOAT3(0, 2, -10)); - camera_transform.UpdateTransform(); - camera.TransformCamera(camera_transform); + editorscene.camera_transform.ClearTransform(); + editorscene.camera_transform.Translate(XMFLOAT3(0, 2, -10)); + editorscene.camera_transform.UpdateTransform(); + camera.TransformCamera(editorscene.camera_transform); camera.UpdateCamera(); - camera_target.ClearTransform(); - camera_target.UpdateTransform(); + editorscene.camera_target.ClearTransform(); + editorscene.camera_target.UpdateTransform(); } -void CameraWindow::Create(EditorComponent* editor) +void CameraWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Camera Window"); - camera_transform.MatrixTransform(wi::scene::GetCamera().GetInvView()); - camera_transform.UpdateTransform(); + editor->GetCurrentEditorScene().camera_transform.MatrixTransform(editor->GetCurrentEditorScene().camera.GetInvView()); + editor->GetCurrentEditorScene().camera_transform.UpdateTransform(); SetSize(XMFLOAT2(380, 360)); @@ -45,10 +48,10 @@ void CameraWindow::Create(EditorComponent* editor) farPlaneSlider.SetTooltip("Controls the camera's far clip plane, geometry farther than this will be clipped."); farPlaneSlider.SetSize(XMFLOAT2(100, hei)); farPlaneSlider.SetPos(XMFLOAT2(x, y)); - farPlaneSlider.SetValue(wi::scene::GetCamera().zFarP); + farPlaneSlider.SetValue(editor->GetCurrentEditorScene().camera.zFarP); farPlaneSlider.OnSlide([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); - CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = editor->GetCurrentScene(); + CameraComponent& camera = editor->GetCurrentEditorScene().camera; camera.zFarP = args.fValue; camera.UpdateCamera(); camera.SetDirty(); @@ -59,10 +62,10 @@ void CameraWindow::Create(EditorComponent* editor) nearPlaneSlider.SetTooltip("Controls the camera's near clip plane, geometry closer than this will be clipped."); nearPlaneSlider.SetSize(XMFLOAT2(100, hei)); nearPlaneSlider.SetPos(XMFLOAT2(x, y += step)); - nearPlaneSlider.SetValue(wi::scene::GetCamera().zNearP); + nearPlaneSlider.SetValue(editor->GetCurrentEditorScene().camera.zNearP); nearPlaneSlider.OnSlide([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); - CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = editor->GetCurrentScene(); + CameraComponent& camera = editor->GetCurrentEditorScene().camera; camera.zNearP = args.fValue; camera.UpdateCamera(); camera.SetDirty(); @@ -74,8 +77,8 @@ void CameraWindow::Create(EditorComponent* editor) fovSlider.SetSize(XMFLOAT2(100, hei)); fovSlider.SetPos(XMFLOAT2(x, y += step)); fovSlider.OnSlide([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); - CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = editor->GetCurrentScene(); + CameraComponent& camera = editor->GetCurrentEditorScene().camera; camera.fov = args.fValue / 180.f * XM_PI; camera.UpdateCamera(); camera.SetDirty(); @@ -87,8 +90,8 @@ void CameraWindow::Create(EditorComponent* editor) focalLengthSlider.SetSize(XMFLOAT2(100, hei)); focalLengthSlider.SetPos(XMFLOAT2(x, y += step)); focalLengthSlider.OnSlide([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); - CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = editor->GetCurrentScene(); + CameraComponent& camera = editor->GetCurrentEditorScene().camera; camera.focal_length = args.fValue; camera.UpdateCamera(); camera.SetDirty(); @@ -100,8 +103,8 @@ void CameraWindow::Create(EditorComponent* editor) apertureSizeSlider.SetSize(XMFLOAT2(100, hei)); apertureSizeSlider.SetPos(XMFLOAT2(x, y += step)); apertureSizeSlider.OnSlide([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); - CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = editor->GetCurrentScene(); + CameraComponent& camera = editor->GetCurrentEditorScene().camera; camera.aperture_size = args.fValue; camera.UpdateCamera(); camera.SetDirty(); @@ -113,8 +116,8 @@ void CameraWindow::Create(EditorComponent* editor) apertureShapeXSlider.SetSize(XMFLOAT2(100, hei)); apertureShapeXSlider.SetPos(XMFLOAT2(x, y += step)); apertureShapeXSlider.OnSlide([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); - CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = editor->GetCurrentScene(); + CameraComponent& camera = editor->GetCurrentEditorScene().camera; camera.aperture_shape.x = args.fValue; camera.UpdateCamera(); camera.SetDirty(); @@ -126,8 +129,8 @@ void CameraWindow::Create(EditorComponent* editor) apertureShapeYSlider.SetSize(XMFLOAT2(100, hei)); apertureShapeYSlider.SetPos(XMFLOAT2(x, y += step)); apertureShapeYSlider.OnSlide([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); - CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = editor->GetCurrentScene(); + CameraComponent& camera = editor->GetCurrentEditorScene().camera; camera.aperture_shape.y = args.fValue; camera.UpdateCamera(); camera.SetDirty(); @@ -171,9 +174,9 @@ void CameraWindow::Create(EditorComponent* editor) proxyButton.SetPos(XMFLOAT2(x, y += step * 2)); proxyButton.OnClick([=](wi::gui::EventArgs args) { - const CameraComponent& camera = wi::scene::GetCamera(); + const CameraComponent& camera = editor->GetCurrentEditorScene().camera; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); static int camcounter = 0; Entity entity = scene.Entity_CreateCamera("cam" + std::to_string(camcounter), camera.width, camera.height, camera.zNearP, camera.zFarP, camera.fov); @@ -195,7 +198,7 @@ void CameraWindow::Create(EditorComponent* editor) editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); SetEntity(entity); }); AddWidget(&proxyButton); @@ -224,7 +227,7 @@ void CameraWindow::SetEntity(Entity entity) { proxy = entity; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); if (scene.cameras.GetComponent(entity) != nullptr) { @@ -241,7 +244,7 @@ void CameraWindow::SetEntity(Entity entity) void CameraWindow::Update() { - CameraComponent& camera = wi::scene::GetCamera(); + CameraComponent& camera = editor->GetCurrentEditorScene().camera; farPlaneSlider.SetValue(camera.zFarP); nearPlaneSlider.SetValue(camera.zNearP); @@ -256,7 +259,7 @@ void CameraWindow::Update() // however this only works well for fps camera if (fpsCheckBox.GetCheck()) { - camera_transform.ClearTransform(); - camera_transform.MatrixTransform(camera.InvView); + editor->GetCurrentEditorScene().camera_transform.ClearTransform(); + editor->GetCurrentEditorScene().camera_transform.MatrixTransform(camera.InvView); } } diff --git a/Editor/CameraWindow.h b/Editor/CameraWindow.h index b80ef7f9f..e17cf4984 100644 --- a/Editor/CameraWindow.h +++ b/Editor/CameraWindow.h @@ -10,15 +10,11 @@ public: void ResetCam(); + EditorComponent* editor = nullptr; wi::ecs::Entity proxy = wi::ecs::INVALID_ENTITY; void SetEntity(wi::ecs::Entity entity); void Update(); - XMFLOAT3 move = {}; - - - wi::scene::TransformComponent camera_transform; - wi::scene::TransformComponent camera_target; wi::gui::Slider farPlaneSlider; wi::gui::Slider nearPlaneSlider; diff --git a/Editor/DecalWindow.cpp b/Editor/DecalWindow.cpp index 5c6515d3c..6e93a551c 100644 --- a/Editor/DecalWindow.cpp +++ b/Editor/DecalWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::ecs; using namespace wi::scene; -void DecalWindow::Create(EditorComponent* editor) +void DecalWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Decal Window"); SetSize(XMFLOAT2(420, 200)); @@ -37,12 +38,12 @@ void DecalWindow::Create(EditorComponent* editor) decalNameField.SetPos(XMFLOAT2(10, y+=step)); decalNameField.SetSize(XMFLOAT2(300, hei)); decalNameField.OnInputAccepted([=](wi::gui::EventArgs args) { - NameComponent* name = wi::scene::GetScene().names.GetComponent(entity); + NameComponent* name = editor->GetCurrentScene().names.GetComponent(entity); if (name != nullptr) { *name = args.sValue; - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); } }); AddWidget(&decalNameField); @@ -57,7 +58,7 @@ void DecalWindow::SetEntity(Entity entity) { this->entity = entity; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); const DecalComponent* decal = scene.decals.GetComponent(entity); if (decal != nullptr) diff --git a/Editor/DecalWindow.h b/Editor/DecalWindow.h index 97d42a803..b8668b8b1 100644 --- a/Editor/DecalWindow.h +++ b/Editor/DecalWindow.h @@ -8,6 +8,7 @@ class DecalWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 3c19c02b9..adc27b5ed 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "Editor.h" #include "wiRenderer.h" +#include "wiScene_BindLua.h" #include "ModelImporter.h" #include "Translator.h" @@ -91,6 +92,11 @@ void EditorComponent::ChangeRenderPath(RENDERPATH path) break; } + if (scenes.empty()) + { + NewScene(); + } + renderPath->resolutionScale = resolutionScale; renderPath->setBloomThreshold(3.0f); @@ -302,62 +308,62 @@ void EditorComponent::ResizeLayout() //////////////////////////////////////////////////////////////////////////////////// - translatorCheckBox.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 4 - 25, 0)); + translatorCheckBox.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 3 - 25, 0)); translatorCheckBox.SetSize(XMFLOAT2(18, 18)); - isScalatorCheckBox.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 4 - 25 - 40 * 2, 22)); + isScalatorCheckBox.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 3 - 25 - 40 * 2, 22)); isScalatorCheckBox.SetSize(XMFLOAT2(18, 18)); - isRotatorCheckBox.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 4 - 25 - 40 * 1, 22)); + isRotatorCheckBox.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 3 - 25 - 40 * 1, 22)); isRotatorCheckBox.SetSize(XMFLOAT2(18, 18)); - isTranslatorCheckBox.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 4 - 25, 22)); + isTranslatorCheckBox.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 3 - 25, 22)); isTranslatorCheckBox.SetSize(XMFLOAT2(18, 18)); - saveButton.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 4, 0)); + saveButton.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 3, 0)); saveButton.SetSize(XMFLOAT2(100, 40)); - modelButton.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 3, 0)); - modelButton.SetSize(XMFLOAT2(100, 40)); + openButton.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 2, 0)); + openButton.SetSize(XMFLOAT2(100, 40)); - scriptButton.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 2, 0)); - scriptButton.SetSize(XMFLOAT2(100, 40)); + closeButton.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 1, 0)); + closeButton.SetSize(XMFLOAT2(100, 40)); - clearButton.SetPos(XMFLOAT2(screenW - 50 - 55 - 105 * 1, 0)); - clearButton.SetSize(XMFLOAT2(100, 40)); + aboutButton.SetPos(XMFLOAT2(screenW - 50 - 55, 0)); + aboutButton.SetSize(XMFLOAT2(50, 40)); - helpButton.SetPos(XMFLOAT2(screenW - 50 - 55, 0)); - helpButton.SetSize(XMFLOAT2(50, 40)); - - helpLabel.SetSize(XMFLOAT2(screenW / 2.0f, screenH / 1.5f)); - helpLabel.SetPos(XMFLOAT2(screenW / 2.0f - helpLabel.scale.x / 2.0f, screenH / 2.0f - helpLabel.scale.y / 2.0f)); + aboutLabel.SetSize(XMFLOAT2(screenW / 2.0f, screenH / 1.5f)); + aboutLabel.SetPos(XMFLOAT2(screenW / 2.0f - aboutLabel.scale.x / 2.0f, screenH / 2.0f - aboutLabel.scale.y / 2.0f)); exitButton.SetPos(XMFLOAT2(screenW - 50, 0)); exitButton.SetSize(XMFLOAT2(50, 40)); - profilerEnabledCheckBox.SetSize(XMFLOAT2(20, 20)); - profilerEnabledCheckBox.SetPos(XMFLOAT2(screenW - 530, 45)); + profilerEnabledCheckBox.SetSize(XMFLOAT2(18, 18)); + profilerEnabledCheckBox.SetPos(XMFLOAT2(screenW - 485, 45)); - physicsEnabledCheckBox.SetSize(XMFLOAT2(20, 20)); - physicsEnabledCheckBox.SetPos(XMFLOAT2(screenW - 370, 45)); + physicsEnabledCheckBox.SetSize(XMFLOAT2(18, 18)); + physicsEnabledCheckBox.SetPos(XMFLOAT2(screenW - 390, 45)); - cinemaModeCheckBox.SetSize(XMFLOAT2(20, 20)); - cinemaModeCheckBox.SetPos(XMFLOAT2(screenW - 240, 45)); + cinemaModeCheckBox.SetSize(XMFLOAT2(18, 18)); + cinemaModeCheckBox.SetPos(XMFLOAT2(screenW - 260, 45)); - renderPathComboBox.SetSize(XMFLOAT2(100, 20)); - renderPathComboBox.SetPos(XMFLOAT2(screenW - 120, 45)); + renderPathComboBox.SetSize(XMFLOAT2(120, 18)); + renderPathComboBox.SetPos(XMFLOAT2(screenW - 140, 45)); - saveModeComboBox.SetSize(XMFLOAT2(120, 20)); - saveModeComboBox.SetPos(XMFLOAT2(screenW - 140, 70)); + sceneComboBox.SetSize(XMFLOAT2(120, 18)); + sceneComboBox.SetPos(XMFLOAT2(screenW - 140, 70)); - pathTraceTargetSlider.SetSize(XMFLOAT2(200, 20)); + saveModeComboBox.SetSize(XMFLOAT2(120, 18)); + saveModeComboBox.SetPos(XMFLOAT2(screenW - 140, 95)); + + pathTraceTargetSlider.SetSize(XMFLOAT2(200, 18)); pathTraceTargetSlider.SetPos(XMFLOAT2(screenW - 240, 100)); pathTraceStatisticsLabel.SetSize(XMFLOAT2(240, 60)); pathTraceStatisticsLabel.SetPos(XMFLOAT2(screenW - 240, 125)); - sceneGraphView.SetSize(XMFLOAT2(260, 300)); - sceneGraphView.SetPos(XMFLOAT2(0, screenH - sceneGraphView.scale_local.y)); + entityTree.SetSize(XMFLOAT2(260, 300)); + entityTree.SetPos(XMFLOAT2(0, screenH - entityTree.scale_local.y)); } void EditorComponent::Load() { @@ -365,7 +371,6 @@ void EditorComponent::Load() wi::jobsystem::Execute(ctx, [this](wi::jobsystem::JobArgs args) { pointLightTex = wi::resourcemanager::Load("images/pointlight.dds"); }); wi::jobsystem::Execute(ctx, [this](wi::jobsystem::JobArgs args) { spotLightTex = wi::resourcemanager::Load("images/spotlight.dds"); }); wi::jobsystem::Execute(ctx, [this](wi::jobsystem::JobArgs args) { dirLightTex = wi::resourcemanager::Load("images/directional_light.dds"); }); - wi::jobsystem::Execute(ctx, [this](wi::jobsystem::JobArgs args) { areaLightTex = wi::resourcemanager::Load("images/arealight.dds"); }); wi::jobsystem::Execute(ctx, [this](wi::jobsystem::JobArgs args) { decalTex = wi::resourcemanager::Load("images/decal.dds"); }); wi::jobsystem::Execute(ctx, [this](wi::jobsystem::JobArgs args) { forceFieldTex = wi::resourcemanager::Load("images/forcefield.dds"); }); wi::jobsystem::Execute(ctx, [this](wi::jobsystem::JobArgs args) { emitterTex = wi::resourcemanager::Load("images/emitter.dds"); }); @@ -376,7 +381,6 @@ void EditorComponent::Load() // wait for ctx is at the end of this function! - rendererWnd_Toggle.Create("Renderer"); rendererWnd_Toggle.SetTooltip("Renderer settings window"); rendererWnd_Toggle.OnClick([&](wi::gui::EventArgs args) { @@ -458,7 +462,7 @@ void EditorComponent::Load() //prop.object.cascadeMask = 1; // they won't be rendered into the largest shadow cascade } props_scene.Entity_Remove(object_entity); // The objects will be placed by terrain generator, we don't need the default object that the scene has anymore - wi::scene::GetScene().Merge(props_scene); + GetCurrentScene().Merge(props_scene); } // Rock prop: { @@ -488,7 +492,7 @@ void EditorComponent::Load() prop.object.draw_distance = 400; } props_scene.Entity_Remove(object_entity); // The objects will be placed by terrain generator, we don't need the default object that the scene has anymore - wi::scene::GetScene().Merge(props_scene); + GetCurrentScene().Merge(props_scene); } // Bush prop: { @@ -518,17 +522,17 @@ void EditorComponent::Load() prop.object.draw_distance = 200; } props_scene.Entity_Remove(object_entity); // The objects will be placed by terrain generator, we don't need the default object that the scene has anymore - wi::scene::GetScene().Merge(props_scene); + GetCurrentScene().Merge(props_scene); } terragen.init(); - RefreshSceneGraphView(); + RefreshEntityTree(); } terragen.SetVisible(!terragen.IsVisible()); - if (terragen.IsVisible() && !wi::scene::GetScene().transforms.Contains(terragen.terrainEntity)) + if (terragen.IsVisible() && !GetCurrentScene().transforms.Contains(terragen.terrainEntity)) { terragen.Generation_Restart(); - RefreshSceneGraphView(); + RefreshEntityTree(); } }); @@ -537,8 +541,8 @@ void EditorComponent::Load() /////////////////////// - wi::Color option_color_idle = wi::Color(255, 145, 145, 100); - wi::Color option_color_focus = wi::Color(255, 197, 193, 200); + wi::Color option_color_idle = wi::Color(100, 150, 150, 100); + wi::Color option_color_focus = wi::Color(100, 200, 200, 200); weatherWnd_Toggle.Create("Weather"); @@ -708,7 +712,7 @@ void EditorComponent::Load() translatorCheckBox.Create("Transform: "); - translatorCheckBox.SetTooltip("Enable the transform tool.\nTip: hold Left Ctrl to enable snap transform.\nYou can configure snap mode units in the Transform settings."); + translatorCheckBox.SetTooltip("Enable the transform tool (Ctrl + T).\nTip: hold Left Ctrl to enable snap transform.\nYou can configure snap mode units in the Transform settings."); translatorCheckBox.OnClick([&](wi::gui::EventArgs args) { translator.enabled = args.bValue; }); @@ -754,160 +758,110 @@ void EditorComponent::Load() saveButton.Create("Save"); - saveButton.SetTooltip("Save the current scene"); - saveButton.SetColor(wi::Color(0, 198, 101, 180), wi::gui::WIDGETSTATE::IDLE); - saveButton.SetColor(wi::Color(0, 255, 140, 255), wi::gui::WIDGETSTATE::FOCUS); + saveButton.SetTooltip("Save the current scene to a new file (Ctrl + Shift + S)"); + saveButton.SetColor(wi::Color(50, 180, 100, 180), wi::gui::WIDGETSTATE::IDLE); + saveButton.SetColor(wi::Color(50, 220, 140, 255), wi::gui::WIDGETSTATE::FOCUS); saveButton.OnClick([&](wi::gui::EventArgs args) { - - const bool dump_to_header = saveModeComboBox.GetSelected() == 2; - - wi::helper::FileDialogParams params; - params.type = wi::helper::FileDialogParams::SAVE; - if (dump_to_header) - { - params.description = "C++ header (.h)"; - params.extensions.push_back("h"); - } - else - { - params.description = "Wicked Scene (.wiscene)"; - params.extensions.push_back("wiscene"); - } - wi::helper::FileDialog(params, [=](std::string fileName) { - wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { - std::string filename = wi::helper::ReplaceExtension(fileName, params.extensions.front()); - wi::Archive archive = dump_to_header ? wi::Archive() : wi::Archive(filename, false); - if (archive.IsOpen()) - { - Scene& scene = wi::scene::GetScene(); - - wi::resourcemanager::Mode embed_mode = (wi::resourcemanager::Mode)saveModeComboBox.GetItemUserData(saveModeComboBox.GetSelected()); - wi::resourcemanager::SetMode(embed_mode); - - terragen.BakeVirtualTexturesToFiles(); - scene.Serialize(archive); - - if (dump_to_header) - { - archive.SaveHeaderFile(filename, wi::helper::RemoveExtension(wi::helper::GetFileNameFromPath(filename))); - } - - ResetHistory(); - } - else - { - wi::helper::messageBox("Could not create " + fileName + "!"); - } - }); - }); + SaveAs(); }); GetGUI().AddWidget(&saveButton); - modelButton.Create("Load Model"); - modelButton.SetTooltip("Load a scene / import model into the editor..."); - modelButton.SetColor(wi::Color(0, 89, 255, 180), wi::gui::WIDGETSTATE::IDLE); - modelButton.SetColor(wi::Color(112, 155, 255, 255), wi::gui::WIDGETSTATE::FOCUS); - modelButton.OnClick([&](wi::gui::EventArgs args) { + openButton.Create("Open"); + openButton.SetTooltip("Open a scene, import a model or execute a Lua script..."); + openButton.SetColor(wi::Color(50, 100, 255, 180), wi::gui::WIDGETSTATE::IDLE); + openButton.SetColor(wi::Color(120, 160, 255, 255), wi::gui::WIDGETSTATE::FOCUS); + openButton.OnClick([&](wi::gui::EventArgs args) { wi::helper::FileDialogParams params; params.type = wi::helper::FileDialogParams::OPEN; - params.description = "Model formats (.wiscene, .obj, .gltf, .glb)"; + params.description = ".wiscene, .obj, .gltf, .glb, .lua"; params.extensions.push_back("wiscene"); params.extensions.push_back("obj"); params.extensions.push_back("gltf"); params.extensions.push_back("glb"); + params.extensions.push_back("lua"); wi::helper::FileDialog(params, [&](std::string fileName) { wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { - size_t camera_count_prev = wi::scene::GetScene().cameras.GetCount(); + std::string extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(fileName)); + if (!extension.compare("LUA")) + { + wi::lua::RunFile(fileName); + return; + } + + size_t camera_count_prev = GetCurrentScene().cameras.GetCount(); main->loader.addLoadingFunction([=](wi::jobsystem::JobArgs args) { - std::string extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(fileName)); if (!extension.compare("WISCENE")) // engine-serialized { - wi::scene::LoadModel(fileName); + wi::scene::LoadModel(GetCurrentScene(), fileName); + GetCurrentEditorScene().path = fileName; } else if (!extension.compare("OBJ")) // wavefront-obj { Scene scene; ImportModel_OBJ(fileName, scene); - wi::scene::GetScene().Merge(scene); + GetCurrentScene().Merge(scene); } else if (!extension.compare("GLTF")) // text-based gltf { Scene scene; ImportModel_GLTF(fileName, scene); - wi::scene::GetScene().Merge(scene); + GetCurrentScene().Merge(scene); } else if (!extension.compare("GLB")) // binary gltf { Scene scene; ImportModel_GLTF(fileName, scene); - wi::scene::GetScene().Merge(scene); + GetCurrentScene().Merge(scene); } }); main->loader.onFinished([=] { // Detect when the new scene contains a new camera, and snap the camera onto it: - size_t camera_count = wi::scene::GetScene().cameras.GetCount(); + size_t camera_count = GetCurrentScene().cameras.GetCount(); if (camera_count > 0 && camera_count > camera_count_prev) { - Entity entity = wi::scene::GetScene().cameras.GetEntity(camera_count_prev); + Entity entity = GetCurrentScene().cameras.GetEntity(camera_count_prev); if (entity != INVALID_ENTITY) { - TransformComponent* camera_transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* camera_transform = GetCurrentScene().transforms.GetComponent(entity); if (camera_transform != nullptr) { - cameraWnd.camera_transform = *camera_transform; + GetCurrentEditorScene().camera_transform = *camera_transform; } - CameraComponent* cam = wi::scene::GetScene().cameras.GetComponent(entity); + CameraComponent* cam = GetCurrentScene().cameras.GetComponent(entity); if (cam != nullptr) { - wi::scene::GetCamera() = *cam; + GetCurrentEditorScene().camera = *cam; // camera aspect should be always for the current screen - wi::scene::GetCamera().width = (float)renderPath->GetInternalResolution().x; - wi::scene::GetCamera().height = (float)renderPath->GetInternalResolution().y; + GetCurrentEditorScene().camera.width = (float)renderPath->GetInternalResolution().x; + GetCurrentEditorScene().camera.height = (float)renderPath->GetInternalResolution().y; } } } main->ActivatePath(this, 0.2f, wi::Color::Black()); weatherWnd.Update(); - RefreshSceneGraphView(); + RefreshEntityTree(); + RefreshSceneList(); }); main->ActivatePath(&main->loader, 0.2f, wi::Color::Black()); ResetHistory(); }); }); }); - GetGUI().AddWidget(&modelButton); + GetGUI().AddWidget(&openButton); - scriptButton.Create("Load Script"); - scriptButton.SetTooltip("Load a Lua script..."); - scriptButton.SetColor(wi::Color(255, 33, 140, 180), wi::gui::WIDGETSTATE::IDLE); - scriptButton.SetColor(wi::Color(255, 100, 140, 255), wi::gui::WIDGETSTATE::FOCUS); - scriptButton.OnClick([&](wi::gui::EventArgs args) { - wi::helper::FileDialogParams params; - params.type = wi::helper::FileDialogParams::OPEN; - params.description = "Lua script"; - params.extensions.push_back("lua"); - wi::helper::FileDialog(params, [](std::string fileName) { - wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { - wi::lua::RunFile(fileName); - }); - }); - }); - GetGUI().AddWidget(&scriptButton); - - - clearButton.Create("Clear World"); - clearButton.SetTooltip("Delete everything from the scene. This operation is not undoable!"); - clearButton.SetColor(wi::Color(255, 173, 43, 180), wi::gui::WIDGETSTATE::IDLE); - clearButton.SetColor(wi::Color(255, 235, 173, 255), wi::gui::WIDGETSTATE::FOCUS); - clearButton.OnClick([&](wi::gui::EventArgs args) { + closeButton.Create("Close"); + closeButton.SetTooltip("Close the current scene.\nThis will clear everything from the currently selected scene, and delete the scene.\nThis operation cannot be undone!"); + closeButton.SetColor(wi::Color(255, 130, 100, 180), wi::gui::WIDGETSTATE::IDLE); + closeButton.SetColor(wi::Color(255, 200, 150, 255), wi::gui::WIDGETSTATE::FOCUS); + closeButton.OnClick([&](wi::gui::EventArgs args) { terragen.Generation_Cancel(); // This is to recreate the terragen from scratch, but it has implicitly deleted copy ctor so it's weird: @@ -915,7 +869,7 @@ void EditorComponent::Load() new (&terragen) TerrainGenerator; translator.selected.clear(); - wi::scene::Scene& scene = wi::scene::GetScene(); + wi::scene::Scene& scene = GetCurrentScene(); wi::renderer::ClearWorld(scene); objectWnd.SetEntity(INVALID_ENTITY); meshWnd.SetEntity(INVALID_ENTITY, -1); @@ -935,65 +889,88 @@ void EditorComponent::Load() layerWnd.SetEntity(INVALID_ENTITY); nameWnd.SetEntity(INVALID_ENTITY); - RefreshSceneGraphView(); + RefreshEntityTree(); ResetHistory(); + GetCurrentEditorScene().path.clear(); + + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + if (scenes.size() > 1) + { + scenes.erase(scenes.begin() + current_scene); + } + SetCurrentScene(std::max(0, current_scene - 1)); + }); }); - GetGUI().AddWidget(&clearButton); + GetGUI().AddWidget(&closeButton); - helpButton.Create("?"); - helpButton.SetTooltip("Help"); - helpButton.SetColor(wi::Color(34, 158, 214, 180), wi::gui::WIDGETSTATE::IDLE); - helpButton.SetColor(wi::Color(113, 183, 214, 255), wi::gui::WIDGETSTATE::FOCUS); - helpButton.OnClick([&](wi::gui::EventArgs args) { - helpLabel.SetVisible(!helpLabel.IsVisible()); + aboutButton.Create("?"); + aboutButton.SetTooltip("About..."); + aboutButton.SetColor(wi::Color(50, 160, 200, 180), wi::gui::WIDGETSTATE::IDLE); + aboutButton.SetColor(wi::Color(120, 200, 200, 255), wi::gui::WIDGETSTATE::FOCUS); + aboutButton.OnClick([&](wi::gui::EventArgs args) { + aboutLabel.SetVisible(!aboutLabel.IsVisible()); }); - GetGUI().AddWidget(&helpButton); + GetGUI().AddWidget(&aboutButton); { std::string ss; - ss += "Help:\n"; + ss += "Wicked Engine Editor v"; + ss += wi::version::GetVersionString(); + ss += "\nCreated by Turánszki János"; + ss += "\n\nWebsite: https://wickedengine.net/"; + ss += "\nGithub page: https://github.com/turanszkij/WickedEngine"; + ss += "\nDiscord chat: https://discord.gg/CFjRYmE"; + ss += "\nYou can support the project on Patreon: https://www.patreon.com/wickedengine"; + ss += "\n\nControls\n"; + ss += "------------\n"; ss += "Move camera: WASD, or Contoller left stick or D-pad\n"; ss += "Look: Middle mouse button / arrow keys / controller right stick\n"; ss += "Select: Right mouse button\n"; ss += "Interact with water: Left mouse button when nothing is selected\n"; - ss += "Camera speed: Left Shift button or controller R2/RT\n"; + ss += "Faster camera: Left Shift button or controller R2/RT\n"; ss += "Snap transform: Left Ctrl (hold while transforming)\n"; - ss += "Camera up: E, down: Q\n"; + ss += "Camera up: E\n"; + ss += "Camera down: Q\n"; ss += "Duplicate entity: Ctrl + D\n"; ss += "Select All: Ctrl + A\n"; - ss += "Deselect All: Escape\n"; + ss += "Deselect All: Esc\n"; ss += "Undo: Ctrl + Z\n"; ss += "Redo: Ctrl + Y\n"; ss += "Copy: Ctrl + C\n"; ss += "Cut: Ctrl + X\n"; ss += "Paste: Ctrl + V\n"; ss += "Delete: Delete button\n"; + ss += "Save As: Ctrl + Shift + S\n"; + ss += "Save: Ctrl + S\n"; + ss += "Transform: Ctrl + T\n"; ss += "Inspector mode: I button (hold), hovered entity information will be displayed near mouse position.\n"; ss += "Place Instances: Ctrl + Shift + Left mouse click (place clipboard onto clicked surface)\n"; ss += "Script Console / backlog: HOME button\n"; ss += "\n"; + ss += "\nTips\n"; + ss += "-------\n"; ss += "You can find sample scenes in the Content/models directory. Try to load one.\n"; ss += "You can also import models from .OBJ, .GLTF, .GLB files.\n"; +#ifndef PLATFORM_UWP ss += "You can find a program configuration file at Editor/config.ini\n"; +#endif // PLATFORM_UWP ss += "You can find sample LUA scripts in the Content/scripts directory. Try to load one.\n"; ss += "You can find a startup script at Editor/startup.lua (this will be executed on program start, if exists)\n"; ss += "\nFor questions, bug reports, feedback, requests, please open an issue at:\n"; - ss += "https://github.com/turanszkij/WickedEngine\n"; - ss += "\nDevblog: https://wickedengine.net/\n"; - ss += "Discord: https://discord.gg/CFjRYmE\n"; + ss += "https://github.com/turanszkij/WickedEngine/issues\n"; - helpLabel.Create("HelpLabel"); - helpLabel.SetText(ss); - helpLabel.SetVisible(false); - helpLabel.SetColor(wi::Color(113, 183, 214, 100)); - GetGUI().AddWidget(&helpLabel); + aboutLabel.Create("AboutLabel"); + aboutLabel.SetText(ss); + aboutLabel.SetVisible(false); + aboutLabel.SetColor(wi::Color(113, 183, 214, 100)); + GetGUI().AddWidget(&aboutLabel); } exitButton.Create("X"); exitButton.SetTooltip("Exit"); - exitButton.SetColor(wi::Color(190, 0, 0, 180), wi::gui::WIDGETSTATE::IDLE); - exitButton.SetColor(wi::Color(255, 0, 0, 255), wi::gui::WIDGETSTATE::FOCUS); + exitButton.SetColor(wi::Color(160, 50, 50, 180), wi::gui::WIDGETSTATE::IDLE); + exitButton.SetColor(wi::Color(200, 50, 50, 255), wi::gui::WIDGETSTATE::FOCUS); exitButton.OnClick([this](wi::gui::EventArgs args) { terragen.Generation_Cancel(); wi::platform::Exit(); @@ -1001,7 +978,7 @@ void EditorComponent::Load() GetGUI().AddWidget(&exitButton); - profilerEnabledCheckBox.Create("Profiler Enabled: "); + profilerEnabledCheckBox.Create("Profiler: "); profilerEnabledCheckBox.SetTooltip("Toggle Profiler On/Off"); profilerEnabledCheckBox.OnClick([&](wi::gui::EventArgs args) { wi::profiler::SetEnabled(args.bValue); @@ -1009,7 +986,7 @@ void EditorComponent::Load() profilerEnabledCheckBox.SetCheck(wi::profiler::IsEnabled()); GetGUI().AddWidget(&profilerEnabledCheckBox); - physicsEnabledCheckBox.Create("Physics Simulation: "); + physicsEnabledCheckBox.Create("Physics: "); physicsEnabledCheckBox.SetTooltip("Toggle Physics Simulation On/Off"); physicsEnabledCheckBox.OnClick([&](wi::gui::EventArgs args) { wi::physics::SetSimulationEnabled(args.bValue); @@ -1031,8 +1008,8 @@ void EditorComponent::Load() GetGUI().AddWidget(&cinemaModeCheckBox); - sceneGraphView.Create("Scene graph view"); - sceneGraphView.OnSelect([this](wi::gui::EventArgs args) { + entityTree.Create("Entities"); + entityTree.OnSelect([this](wi::gui::EventArgs args) { if (args.iValue < 0) return; @@ -1044,9 +1021,9 @@ void EditorComponent::Load() translator.selected.clear(); - for (int i = 0; i < sceneGraphView.GetItemCount(); ++i) + for (int i = 0; i < entityTree.GetItemCount(); ++i) { - const wi::gui::TreeList::Item& item = sceneGraphView.GetItem(i); + const wi::gui::TreeList::Item& item = entityTree.GetItem(i); if (item.selected) { wi::scene::PickResult pick; @@ -1059,7 +1036,7 @@ void EditorComponent::Load() RecordSelection(archive); }); - GetGUI().AddWidget(&sceneGraphView); + GetGUI().AddWidget(&entityTree); renderPathComboBox.Create("Render Path: "); @@ -1074,9 +1051,23 @@ void EditorComponent::Load() GetGUI().AddWidget(&renderPathComboBox); + sceneComboBox.Create("Scene: "); + sceneComboBox.OnSelect([&](wi::gui::EventArgs args) { + if (args.iValue >= int(scenes.size())) + { + NewScene(); + } + SetCurrentScene(args.iValue); + }); + sceneComboBox.SetEnabled(true); + sceneComboBox.SetColor(wi::Color(50, 100, 255, 180), wi::gui::WIDGETSTATE::IDLE); + sceneComboBox.SetColor(wi::Color(120, 160, 255, 255), wi::gui::WIDGETSTATE::FOCUS); + GetGUI().AddWidget(&sceneComboBox); + + saveModeComboBox.Create("Save Mode: "); - saveModeComboBox.SetColor(wi::Color(0, 198, 101, 180), wi::gui::WIDGETSTATE::IDLE); - saveModeComboBox.SetColor(wi::Color(0, 255, 140, 255), wi::gui::WIDGETSTATE::FOCUS); + saveModeComboBox.SetColor(wi::Color(50, 180, 100, 180), wi::gui::WIDGETSTATE::IDLE); + saveModeComboBox.SetColor(wi::Color(50, 220, 140, 255), wi::gui::WIDGETSTATE::FOCUS); saveModeComboBox.AddItem("Embed resources", (uint64_t)wi::resourcemanager::Mode::ALLOW_RETAIN_FILEDATA); saveModeComboBox.AddItem("No embedding", (uint64_t)wi::resourcemanager::Mode::ALLOW_RETAIN_FILEDATA_BUT_DISABLE_EMBEDDING); saveModeComboBox.AddItem("Dump to header", (uint64_t)wi::resourcemanager::Mode::ALLOW_RETAIN_FILEDATA); @@ -1139,7 +1130,7 @@ void EditorComponent::Load() wi::jobsystem::Wait(ctx); - RenderPath2D::Load(); + RenderPath2D::Load(); } void EditorComponent::Start() { @@ -1161,10 +1152,12 @@ void EditorComponent::Update(float dt) { wi::profiler::range_id profrange = wi::profiler::BeginRangeCPU("Editor Update"); - Scene& scene = wi::scene::GetScene(); - CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = GetCurrentScene(); + EditorScene& editorscene = GetCurrentEditorScene(); + CameraComponent& camera = editorscene.camera; - translator.Update(camera, *this); + terragen.scene = &scene; + translator.scene = &scene; if (scene.forces.Contains(grass_interaction_entity)) { @@ -1202,9 +1195,9 @@ void EditorComponent::Update(float dt) bool deleting = wi::input::Press(wi::input::KEYBOARD_BUTTON_DELETE); // Camera control: - XMFLOAT4 currentMouse = wi::input::GetPointer(); if (!wi::backlog::isActive() && !GetGUI().HasFocus()) { + XMFLOAT4 currentMouse = wi::input::GetPointer(); static XMFLOAT4 originalMouse = XMFLOAT4(0, 0, 0, 0); static bool camControlStart = true; if (camControlStart) @@ -1273,7 +1266,7 @@ void EditorComponent::Update(float dt) const float clampedDT = std::min(dt, 0.1f); // if dt > 100 millisec, don't allow the camera to jump too far... const float speed = ((wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT) ? 10.0f : 1.0f) + rightTrigger.x * 10.0f) * cameraWnd.movespeedSlider.GetValue() * clampedDT; - XMVECTOR move = XMLoadFloat3(&cameraWnd.move); + XMVECTOR move = XMLoadFloat3(&editorscene.cam_move); XMVECTOR moveNew = XMVectorSet(leftStick.x, 0, leftStick.y, 0); if (!wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL)) @@ -1299,17 +1292,17 @@ void EditorComponent::Update(float dt) if (abs(xDif) + abs(yDif) > 0 || moveLength > 0.0001f) { - XMMATRIX camRot = XMMatrixRotationQuaternion(XMLoadFloat4(&cameraWnd.camera_transform.rotation_local)); + XMMATRIX camRot = XMMatrixRotationQuaternion(XMLoadFloat4(&editorscene.camera_transform.rotation_local)); XMVECTOR move_rot = XMVector3TransformNormal(move, camRot); XMFLOAT3 _move; XMStoreFloat3(&_move, move_rot); - cameraWnd.camera_transform.Translate(_move); - cameraWnd.camera_transform.RotateRollPitchYaw(XMFLOAT3(yDif, xDif, 0)); + editorscene.camera_transform.Translate(_move); + editorscene.camera_transform.RotateRollPitchYaw(XMFLOAT3(yDif, xDif, 0)); camera.SetDirty(); } - cameraWnd.camera_transform.UpdateTransform(); - XMStoreFloat3(&cameraWnd.move, move); + editorscene.camera_transform.UpdateTransform(); + XMStoreFloat3(&editorscene.cam_move, move); } else { @@ -1320,29 +1313,29 @@ void EditorComponent::Update(float dt) XMVECTOR V = XMVectorAdd(camera.GetRight() * xDif, camera.GetUp() * yDif) * 10; XMFLOAT3 vec; XMStoreFloat3(&vec, V); - cameraWnd.camera_target.Translate(vec); + editorscene.camera_target.Translate(vec); } else if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) || currentMouse.z != 0.0f) { - cameraWnd.camera_transform.Translate(XMFLOAT3(0, 0, yDif * 4 + currentMouse.z)); - cameraWnd.camera_transform.translation_local.z = std::min(0.0f, cameraWnd.camera_transform.translation_local.z); + editorscene.camera_transform.Translate(XMFLOAT3(0, 0, yDif * 4 + currentMouse.z)); + editorscene.camera_transform.translation_local.z = std::min(0.0f, editorscene.camera_transform.translation_local.z); camera.SetDirty(); } else if (abs(xDif) + abs(yDif) > 0) { - cameraWnd.camera_target.RotateRollPitchYaw(XMFLOAT3(yDif * 2, xDif * 2, 0)); + editorscene.camera_target.RotateRollPitchYaw(XMFLOAT3(yDif * 2, xDif * 2, 0)); camera.SetDirty(); } - cameraWnd.camera_target.UpdateTransform(); - cameraWnd.camera_transform.UpdateTransform_Parented(cameraWnd.camera_target); + editorscene.camera_target.UpdateTransform(); + editorscene.camera_transform.UpdateTransform_Parented(editorscene.camera_target); } inspector_mode = wi::input::Down((wi::input::BUTTON)'I'); // Begin picking: unsigned int pickMask = rendererWnd.GetPickType(); - Ray pickRay = wi::renderer::GetPickRay((long)currentMouse.x, (long)currentMouse.y, *this); + Ray pickRay = wi::renderer::GetPickRay((long)currentMouse.x, (long)currentMouse.y, *this, camera); { hovered = wi::scene::PickResult(); @@ -1513,7 +1506,7 @@ void EditorComponent::Update(float dt) inspector_mode ) { - hovered = wi::scene::Pick(pickRay, pickMask); + hovered = wi::scene::Pick(pickRay, pickMask, ~0u, scene); } } } @@ -1537,14 +1530,14 @@ void EditorComponent::Update(float dt) // if not water or softbody, put a decal on it: static int decalselector = 0; decalselector = (decalselector + 1) % 2; - Entity entity = scene.Entity_CreateDecal("editorDecal", (decalselector == 0 ? "images/leaf.dds" : "images/blood1.png")); + Entity entity = scene.Entity_CreateDecal("editorDecal", (decalselector == 0 ? "images/leaf.dds" : "images/logo_small.png")); TransformComponent& transform = *scene.transforms.GetComponent(entity); transform.MatrixTransform(hovered.orientation); transform.RotateRollPitchYaw(XMFLOAT3(XM_PIDIV2, 0, 0)); transform.Scale(XMFLOAT3(2, 2, 2)); scene.Component_Attach(entity, hovered.entity); - RefreshSceneGraphView(); + RefreshEntityTree(); } else { @@ -1636,12 +1629,30 @@ void EditorComponent::Update(float dt) // record NEW selection state... RecordSelection(archive); - RefreshSceneGraphView(); + RefreshEntityTree(); } // Control operations... if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL)) { + // Enable transform tool + if (wi::input::Press((wi::input::BUTTON)'T')) + { + translator.enabled = !translator.enabled; + translatorCheckBox.SetCheck(translator.enabled); + } + // Save + if (wi::input::Press((wi::input::BUTTON)'S')) + { + if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT) || GetCurrentEditorScene().path.empty()) + { + SaveAs(); + } + else + { + Save(GetCurrentEditorScene().path); + } + } // Select All if (wi::input::Press((wi::input::BUTTON)'A')) { @@ -1682,7 +1693,7 @@ void EditorComponent::Update(float dt) for (size_t i = 0; i < count; ++i) { wi::scene::PickResult picked; - picked.entity = scene.Entity_Serialize(clipboard, seri, INVALID_ENTITY, Scene::EntitySerializeFlags::RECURSIVE | Scene::EntitySerializeFlags::KEEP_INTERNAL_ENTITY_REFERENCES); + picked.entity = scene.Entity_Serialize(clipboard, seri, INVALID_ENTITY, Scene::EntitySerializeFlags::RECURSIVE); AddSelected(picked); addedEntities.push_back(picked.entity); } @@ -1690,7 +1701,7 @@ void EditorComponent::Update(float dt) RecordSelection(archive); RecordAddedEntity(archive, addedEntities); - RefreshSceneGraphView(); + RefreshEntityTree(); } // Duplicate Instances if (wi::input::Press((wi::input::BUTTON)'D')) @@ -1718,7 +1729,7 @@ void EditorComponent::Update(float dt) RecordSelection(archive); RecordAddedEntity(archive, addedEntities); - RefreshSceneGraphView(); + RefreshEntityTree(); } // Put Instances if (clipboard.IsOpen() && hovered.subsetIndex >= 0 && wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT) && wi::input::Press(wi::input::MOUSE_BUTTON_LEFT)) @@ -1764,21 +1775,21 @@ void EditorComponent::Update(float dt) RecordSelection(archive); RecordAddedEntity(archive, addedEntities); - RefreshSceneGraphView(); + RefreshEntityTree(); } // Undo if (wi::input::Press((wi::input::BUTTON)'Z')) { ConsumeHistoryOperation(true); - RefreshSceneGraphView(); + RefreshEntityTree(); } // Redo if (wi::input::Press((wi::input::BUTTON)'Y')) { ConsumeHistoryOperation(false); - RefreshSceneGraphView(); + RefreshEntityTree(); } } @@ -1804,7 +1815,7 @@ void EditorComponent::Update(float dt) ClearSelected(); - RefreshSceneGraphView(); + RefreshEntityTree(); } // Update window data bindings... @@ -1931,12 +1942,12 @@ void EditorComponent::Update(float dt) TransformComponent* proxy = scene.transforms.GetComponent(cameraWnd.proxy); if (proxy != nullptr) { - cameraWnd.camera_transform.Lerp(cameraWnd.camera_transform, *proxy, 1.0f - cameraWnd.followSlider.GetValue()); - cameraWnd.camera_transform.UpdateTransform(); + editorscene.camera_transform.Lerp(editorscene.camera_transform, *proxy, 1.0f - cameraWnd.followSlider.GetValue()); + editorscene.camera_transform.UpdateTransform(); } } - camera.TransformCamera(cameraWnd.camera_transform); + camera.TransformCamera(editorscene.camera_transform); camera.UpdateCamera(); wi::RenderPath3D_PathTracing* pathtracer = dynamic_cast(renderPath.get()); @@ -1956,7 +1967,7 @@ void EditorComponent::Update(float dt) } else { - ss += "Denoiser not available\n"; + ss += "Denoiser not available!\nTo find out how to enable the denoiser, visit the documentation."; } pathTraceStatisticsLabel.SetText(ss); } @@ -1967,6 +1978,8 @@ void EditorComponent::Update(float dt) RenderPath2D::Update(dt); + translator.Update(camera, *this); + renderPath->colorspace = colorspace; renderPath->Update(dt); } @@ -1978,7 +1991,7 @@ void EditorComponent::PostUpdate() } void EditorComponent::Render() const { - Scene& scene = wi::scene::GetScene(); + const Scene& scene = GetCurrentScene(); // Hovered item boxes: if (!cinemaModeCheckBox.GetCheck()) @@ -2187,9 +2200,9 @@ void EditorComponent::Render() const const XMFLOAT4 glow = wi::math::Lerp(wi::math::Lerp(XMFLOAT4(1, 1, 1, 1), selectionColor, 0.4f), selectionColor, selectionColorIntensity); const wi::Color selectedEntityColor = wi::Color::fromFloat4(glow); - const CameraComponent& camera = wi::scene::GetCamera(); + const CameraComponent& camera = GetCurrentEditorScene().camera; - Scene& scene = wi::scene::GetScene(); + const Scene& scene = GetCurrentScene(); // remove camera jittering CameraComponent cam = *renderPath->camera; @@ -2243,7 +2256,6 @@ void EditorComponent::Render() const wi::image::Draw(&dirLightTex.GetTexture(), fx, cmd); break; default: - wi::image::Draw(&areaLightTex.GetTexture(), fx, cmd); break; } } @@ -2554,7 +2566,7 @@ void EditorComponent::Render() const } - translator.Draw(wi::scene::GetCamera(), cmd); + translator.Draw(GetCurrentEditorScene().camera, cmd); device->RenderPassEnd(cmd); } @@ -2572,19 +2584,19 @@ void EditorComponent::Compose(CommandList cmd) const RenderPath2D::Compose(cmd); } -void EditorComponent::PushToSceneGraphView(wi::ecs::Entity entity, int level) +void EditorComponent::PushToEntityTree(wi::ecs::Entity entity, int level) { - if (scenegraphview_added_items.count(entity) != 0) + if (entitytree_added_items.count(entity) != 0) { return; } - const Scene& scene = wi::scene::GetScene(); + const Scene& scene = GetCurrentScene(); wi::gui::TreeList::Item item; item.level = level; item.userdata = entity; item.selected = IsSelected(entity); - item.open = scenegraphview_opened_items.count(entity) != 0; + item.open = entitytree_opened_items.count(entity) != 0; const NameComponent* name = scene.names.GetComponent(entity); if (name == nullptr) { @@ -2598,50 +2610,50 @@ void EditorComponent::PushToSceneGraphView(wi::ecs::Entity entity, int level) { item.name = name->name; } - sceneGraphView.AddItem(item); + entityTree.AddItem(item); - scenegraphview_added_items.insert(entity); + entitytree_added_items.insert(entity); for (size_t i = 0; i < scene.hierarchy.GetCount(); ++i) { if (scene.hierarchy[i].parentID == entity) { - PushToSceneGraphView(scene.hierarchy.GetEntity(i), level + 1); + PushToEntityTree(scene.hierarchy.GetEntity(i), level + 1); } } } -void EditorComponent::RefreshSceneGraphView() +void EditorComponent::RefreshEntityTree() { - const Scene& scene = wi::scene::GetScene(); + const Scene& scene = GetCurrentScene(); - for (int i = 0; i < sceneGraphView.GetItemCount(); ++i) + for (int i = 0; i < entityTree.GetItemCount(); ++i) { - const wi::gui::TreeList::Item& item = sceneGraphView.GetItem(i); + const wi::gui::TreeList::Item& item = entityTree.GetItem(i); if (item.open) { - scenegraphview_opened_items.insert((Entity)item.userdata); + entitytree_opened_items.insert((Entity)item.userdata); } } - sceneGraphView.ClearItems(); + entityTree.ClearItems(); // Add hierarchy: for (size_t i = 0; i < scene.hierarchy.GetCount(); ++i) { - PushToSceneGraphView(scene.hierarchy[i].parentID, 0); + PushToEntityTree(scene.hierarchy[i].parentID, 0); } // Any transform left that is not part of a hierarchy: for (size_t i = 0; i < scene.transforms.GetCount(); ++i) { - PushToSceneGraphView(scene.transforms.GetEntity(i), 0); + PushToEntityTree(scene.transforms.GetEntity(i), 0); } // Add materials: for (size_t i = 0; i < scene.materials.GetCount(); ++i) { Entity entity = scene.materials.GetEntity(i); - if (scenegraphview_added_items.count(entity) != 0) + if (entitytree_added_items.count(entity) != 0) { continue; } @@ -2649,19 +2661,19 @@ void EditorComponent::RefreshSceneGraphView() wi::gui::TreeList::Item item; item.userdata = entity; item.selected = IsSelected(entity); - item.open = scenegraphview_opened_items.count(entity) != 0; + item.open = entitytree_opened_items.count(entity) != 0; const NameComponent* name = scene.names.GetComponent(entity); item.name = name == nullptr ? std::to_string(entity) : name->name; - sceneGraphView.AddItem(item); + entityTree.AddItem(item); - scenegraphview_added_items.insert(entity); + entitytree_added_items.insert(entity); } // Add meshes: for (size_t i = 0; i < scene.meshes.GetCount(); ++i) { Entity entity = scene.meshes.GetEntity(i); - if (scenegraphview_added_items.count(entity) != 0) + if (entitytree_added_items.count(entity) != 0) { continue; } @@ -2669,16 +2681,16 @@ void EditorComponent::RefreshSceneGraphView() wi::gui::TreeList::Item item; item.userdata = entity; item.selected = IsSelected(entity); - item.open = scenegraphview_opened_items.count(entity) != 0; + item.open = entitytree_opened_items.count(entity) != 0; const NameComponent* name = scene.names.GetComponent(entity); item.name = name == nullptr ? std::to_string(entity) : name->name; - sceneGraphView.AddItem(item); + entityTree.AddItem(item); - scenegraphview_added_items.insert(entity); + entitytree_added_items.insert(entity); } - scenegraphview_added_items.clear(); - scenegraphview_opened_items.clear(); + entitytree_added_items.clear(); + entitytree_opened_items.clear(); } void EditorComponent::ClearSelected() @@ -2735,14 +2747,14 @@ void EditorComponent::RecordSelection(wi::Archive& archive) const archive << x.distance; } } -void EditorComponent::RecordAddedEntity(wi::Archive& archive, wi::ecs::Entity entity) const +void EditorComponent::RecordAddedEntity(wi::Archive& archive, wi::ecs::Entity entity) { const wi::vector entities = { entity }; RecordAddedEntity(archive, entities); } -void EditorComponent::RecordAddedEntity(wi::Archive& archive, const wi::vector& entities) const +void EditorComponent::RecordAddedEntity(wi::Archive& archive, const wi::vector& entities) { - Scene& scene = GetScene(); + Scene& scene = GetCurrentScene(); EntitySerializer seri; archive << entities; @@ -2754,35 +2766,39 @@ void EditorComponent::RecordAddedEntity(wi::Archive& archive, const wi::vector(history.size()) > historyPos) + while (static_cast(editorscene.history.size()) > editorscene.historyPos) { - history.pop_back(); + editorscene.history.pop_back(); } - history.emplace_back(); - history.back().SetReadModeAndResetPos(false); + editorscene.history.emplace_back(); + editorscene.history.back().SetReadModeAndResetPos(false); - return history.back(); + return editorscene.history.back(); } void EditorComponent::ConsumeHistoryOperation(bool undo) { - if ((undo && historyPos >= 0) || (!undo && historyPos < (int)history.size() - 1)) + EditorScene& editorscene = GetCurrentEditorScene(); + + if ((undo && editorscene.historyPos >= 0) || (!undo && editorscene.historyPos < (int)editorscene.history.size() - 1)) { if (!undo) { - historyPos++; + editorscene.historyPos++; } - Scene& scene = wi::scene::GetScene(); + Scene& scene = GetCurrentScene(); - wi::Archive& archive = history[historyPos]; + wi::Archive& archive = editorscene.history[editorscene.historyPos]; archive.SetReadModeAndResetPos(true); int temp; @@ -2977,11 +2993,67 @@ void EditorComponent::ConsumeHistoryOperation(bool undo) if (undo) { - historyPos--; + editorscene.historyPos--; } scene.Update(0); } - RefreshSceneGraphView(); + RefreshEntityTree(); +} + +void EditorComponent::Save(const std::string& filename) +{ + const bool dump_to_header = saveModeComboBox.GetSelected() == 2; + + wi::Archive archive = dump_to_header ? wi::Archive() : wi::Archive(filename, false); + if (archive.IsOpen()) + { + Scene& scene = GetCurrentScene(); + + wi::resourcemanager::Mode embed_mode = (wi::resourcemanager::Mode)saveModeComboBox.GetItemUserData(saveModeComboBox.GetSelected()); + wi::resourcemanager::SetMode(embed_mode); + + terragen.BakeVirtualTexturesToFiles(); + scene.Serialize(archive); + + if (dump_to_header) + { + archive.SaveHeaderFile(filename, wi::helper::RemoveExtension(wi::helper::GetFileNameFromPath(filename))); + } + + GetCurrentEditorScene().path = filename; + } + else + { + wi::helper::messageBox("Could not create " + filename + "!"); + return; + } + + RefreshSceneList(); + + wi::backlog::post("Scene " + std::to_string(current_scene) + " saved: " + GetCurrentEditorScene().path); +} +void EditorComponent::SaveAs() +{ + const bool dump_to_header = saveModeComboBox.GetSelected() == 2; + + wi::helper::FileDialogParams params; + params.type = wi::helper::FileDialogParams::SAVE; + if (dump_to_header) + { + params.description = "C++ header (.h)"; + params.extensions.push_back("h"); + } + else + { + params.description = "Wicked Scene (.wiscene)"; + params.extensions.push_back("wiscene"); + } + wi::helper::FileDialog(params, [=](std::string fileName) { + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + std::string filename = wi::helper::ReplaceExtension(fileName, params.extensions.front()); + Save(filename); + }); + }); } diff --git a/Editor/Editor.h b/Editor/Editor.h index 1803110de..665ecdca6 100644 --- a/Editor/Editor.h +++ b/Editor/Editor.h @@ -2,6 +2,7 @@ #include "WickedEngine.h" #include "Translator.h" #include "TerrainGenerator.h" +#include "wiScene_BindLua.h" #include "MaterialWindow.h" #include "PostprocessWindow.h" @@ -39,7 +40,7 @@ class Editor; class EditorComponent : public wi::RenderPath2D { private: - wi::Resource pointLightTex, spotLightTex, dirLightTex, areaLightTex, decalTex, forceFieldTex, emitterTex, hairTex, cameraTex, armatureTex, soundTex; + wi::Resource pointLightTex, spotLightTex, dirLightTex, decalTex, forceFieldTex, emitterTex, hairTex, cameraTex, armatureTex, soundTex; public: MaterialWindow materialWnd; PostprocessWindow postprocessWnd; @@ -94,22 +95,22 @@ public: wi::gui::CheckBox isTranslatorCheckBox; wi::gui::Button saveButton; wi::gui::ComboBox saveModeComboBox; - wi::gui::Button modelButton; - wi::gui::Button scriptButton; - wi::gui::Button clearButton; - wi::gui::Button helpButton; + wi::gui::Button openButton; + wi::gui::Button closeButton; + wi::gui::Button aboutButton; wi::gui::Button exitButton; wi::gui::CheckBox profilerEnabledCheckBox; wi::gui::CheckBox physicsEnabledCheckBox; wi::gui::CheckBox cinemaModeCheckBox; wi::gui::ComboBox renderPathComboBox; - wi::gui::Label helpLabel; + wi::gui::ComboBox sceneComboBox; + wi::gui::Label aboutLabel; - wi::gui::TreeList sceneGraphView; - wi::unordered_set scenegraphview_added_items; - wi::unordered_set scenegraphview_opened_items; - void PushToSceneGraphView(wi::ecs::Entity entity, int level); - void RefreshSceneGraphView(); + wi::gui::TreeList entityTree; + wi::unordered_set entitytree_added_items; + wi::unordered_set entitytree_opened_items; + void PushToEntityTree(wi::ecs::Entity entity, int level); + void RefreshEntityTree(); wi::gui::Slider pathTraceTargetSlider; wi::gui::Label pathTraceStatisticsLabel; @@ -165,8 +166,6 @@ public: wi::Archive clipboard; - wi::vector history; - int historyPos = -1; enum HistoryOperationType { HISTORYOP_TRANSLATOR, @@ -178,12 +177,73 @@ public: }; void RecordSelection(wi::Archive& archive) const; - void RecordAddedEntity(wi::Archive& archive, wi::ecs::Entity entity) const; - void RecordAddedEntity(wi::Archive& archive, const wi::vector& entities) const; + void RecordAddedEntity(wi::Archive& archive, wi::ecs::Entity entity); + void RecordAddedEntity(wi::Archive& archive, const wi::vector& entities); void ResetHistory(); wi::Archive& AdvanceHistory(); void ConsumeHistoryOperation(bool undo); + + void Save(const std::string& filename); + void SaveAs(); + + struct EditorScene + { + std::string path; + wi::scene::Scene scene; + XMFLOAT3 cam_move = {}; + wi::scene::CameraComponent camera; + wi::scene::TransformComponent camera_transform; + wi::scene::TransformComponent camera_target; + wi::vector history; + int historyPos = -1; + }; + wi::vector> scenes; + int current_scene = 0; + EditorScene& GetCurrentEditorScene() { return *scenes[current_scene].get(); } + const EditorScene& GetCurrentEditorScene() const { return *scenes[current_scene].get(); } + wi::scene::Scene& GetCurrentScene() { return scenes[current_scene].get()->scene; } + const wi::scene::Scene& GetCurrentScene() const { return scenes[current_scene].get()->scene; } + void SetCurrentScene(int index) + { + current_scene = index; + this->renderPath->scene = &scenes[current_scene].get()->scene; + this->renderPath->camera = &scenes[current_scene].get()->camera; + wi::lua::scene::SetGlobalScene(this->renderPath->scene); + wi::lua::scene::SetGlobalCamera(this->renderPath->camera); + RefreshEntityTree(); + RefreshSceneList(); + } + void RefreshSceneList() + { + sceneComboBox.ClearItems(); + for (int i = 0; i < int(scenes.size()); ++i) + { + if (scenes[i]->path.empty()) + { + sceneComboBox.AddItem("Untitled"); + } + else + { + sceneComboBox.AddItem(wi::helper::RemoveExtension(wi::helper::GetFileNameFromPath(scenes[i]->path))); + } + } + sceneComboBox.AddItem("[New]"); + sceneComboBox.SetSelectedWithoutCallback(current_scene); + std::string tooltip = "Choose a scene"; + if (!GetCurrentEditorScene().path.empty()) + { + tooltip += "\nCurrent path: " + GetCurrentEditorScene().path; + } + sceneComboBox.SetTooltip(tooltip); + } + void NewScene() + { + scenes.push_back(std::make_unique()); + SetCurrentScene(int(scenes.size()) - 1); + RefreshSceneList(); + cameraWnd.ResetCam(); + } }; class Editor : public wi::Application diff --git a/Editor/Editor_SOURCE.vcxitems b/Editor/Editor_SOURCE.vcxitems index ffc0758de..15601d3d8 100644 --- a/Editor/Editor_SOURCE.vcxitems +++ b/Editor/Editor_SOURCE.vcxitems @@ -179,18 +179,10 @@ - - true - true - true true - - true - true - true true diff --git a/Editor/Editor_SOURCE.vcxitems.filters b/Editor/Editor_SOURCE.vcxitems.filters index 7f48632cb..8736a0717 100644 --- a/Editor/Editor_SOURCE.vcxitems.filters +++ b/Editor/Editor_SOURCE.vcxitems.filters @@ -134,15 +134,9 @@ - - images - images - - images - images diff --git a/Editor/EmitterWindow.cpp b/Editor/EmitterWindow.cpp index 643f9a025..0e4f99ad1 100644 --- a/Editor/EmitterWindow.cpp +++ b/Editor/EmitterWindow.cpp @@ -7,8 +7,9 @@ using namespace wi::ecs; using namespace wi::scene; -void EmitterWindow::Create(EditorComponent* editor) +void EmitterWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Emitter Window"); SetSize(XMFLOAT2(680, 420)); @@ -22,12 +23,12 @@ void EmitterWindow::Create(EditorComponent* editor) emitterNameField.SetPos(XMFLOAT2(x, y)); emitterNameField.SetSize(XMFLOAT2(300, itemheight)); emitterNameField.OnInputAccepted([=](wi::gui::EventArgs args) { - NameComponent* name = wi::scene::GetScene().names.GetComponent(entity); + NameComponent* name = editor->GetCurrentScene().names.GetComponent(entity); if (name != nullptr) { *name = args.sValue; - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); } }); AddWidget(&emitterNameField); @@ -36,7 +37,7 @@ void EmitterWindow::Create(EditorComponent* editor) addButton.SetPos(XMFLOAT2(x, y += step)); addButton.SetSize(XMFLOAT2(150, itemheight)); addButton.OnClick([=](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); Entity entity = scene.Entity_CreateEmitter("editorEmitter"); wi::Archive& archive = editor->AdvanceHistory(); @@ -49,7 +50,7 @@ void EmitterWindow::Create(EditorComponent* editor) editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); SetEntity(entity); }); addButton.SetTooltip("Add new emitter particle system."); @@ -82,7 +83,7 @@ void EmitterWindow::Create(EditorComponent* editor) } else { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); emitter->meshID = scene.meshes.GetEntity(args.iValue - 1); } } @@ -746,7 +747,7 @@ wi::EmittedParticleSystem* EmitterWindow::GetEmitter() return nullptr; } - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); wi::EmittedParticleSystem* emitter = scene.emitters.GetComponent(entity); return emitter; @@ -760,7 +761,7 @@ void EmitterWindow::UpdateData() return; } - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); meshComboBox.ClearItems(); meshComboBox.AddItem("NO MESH"); diff --git a/Editor/EmitterWindow.h b/Editor/EmitterWindow.h index f150a4ba6..bbe9425fd 100644 --- a/Editor/EmitterWindow.h +++ b/Editor/EmitterWindow.h @@ -10,6 +10,7 @@ class EmitterWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/EnvProbeWindow.cpp b/Editor/EnvProbeWindow.cpp index eff080aca..58aaf7dd2 100644 --- a/Editor/EnvProbeWindow.cpp +++ b/Editor/EnvProbeWindow.cpp @@ -5,8 +5,9 @@ using namespace wi::ecs; using namespace wi::scene; -void EnvProbeWindow::Create(EditorComponent* editor) +void EnvProbeWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Environment Probe Window"); SetSize(XMFLOAT2(420, 220)); @@ -25,7 +26,7 @@ void EnvProbeWindow::Create(EditorComponent* editor) realTimeCheckBox.SetPos(XMFLOAT2(x + 100, y)); realTimeCheckBox.SetEnabled(false); realTimeCheckBox.OnClick([&](wi::gui::EventArgs args) { - EnvironmentProbeComponent* probe = wi::scene::GetScene().probes.GetComponent(entity); + EnvironmentProbeComponent* probe = editor->GetCurrentScene().probes.GetComponent(entity); if (probe != nullptr) { probe->SetRealTime(args.bValue); @@ -39,7 +40,7 @@ void EnvProbeWindow::Create(EditorComponent* editor) msaaCheckBox.SetPos(XMFLOAT2(x + 200, y)); msaaCheckBox.SetEnabled(false); msaaCheckBox.OnClick([&](wi::gui::EventArgs args) { - EnvironmentProbeComponent* probe = wi::scene::GetScene().probes.GetComponent(entity); + EnvironmentProbeComponent* probe = editor->GetCurrentScene().probes.GetComponent(entity); if (probe != nullptr) { probe->SetMSAA(args.bValue); @@ -53,8 +54,8 @@ void EnvProbeWindow::Create(EditorComponent* editor) generateButton.SetPos(XMFLOAT2(x, y += step)); generateButton.OnClick([=](wi::gui::EventArgs args) { XMFLOAT3 pos; - XMStoreFloat3(&pos, XMVectorAdd(wi::scene::GetCamera().GetEye(), wi::scene::GetCamera().GetAt() * 4)); - Entity entity = wi::scene::GetScene().Entity_CreateEnvironmentProbe("editorProbe", pos); + XMStoreFloat3(&pos, XMVectorAdd(editor->GetCurrentEditorScene().camera.GetEye(), editor->GetCurrentEditorScene().camera.GetAt() * 4)); + Entity entity = editor->GetCurrentScene().Entity_CreateEnvironmentProbe("editorProbe", pos); wi::Archive& archive = editor->AdvanceHistory(); archive << EditorComponent::HISTORYOP_ADD; @@ -66,7 +67,7 @@ void EnvProbeWindow::Create(EditorComponent* editor) editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); SetEntity(entity); }); AddWidget(&generateButton); @@ -76,7 +77,7 @@ void EnvProbeWindow::Create(EditorComponent* editor) refreshButton.SetPos(XMFLOAT2(x + 120, y)); refreshButton.SetEnabled(false); refreshButton.OnClick([&](wi::gui::EventArgs args) { - EnvironmentProbeComponent* probe = wi::scene::GetScene().probes.GetComponent(entity); + EnvironmentProbeComponent* probe = editor->GetCurrentScene().probes.GetComponent(entity); if (probe != nullptr) { probe->SetDirty(); @@ -89,7 +90,7 @@ void EnvProbeWindow::Create(EditorComponent* editor) refreshAllButton.SetPos(XMFLOAT2(x + 240, y)); refreshAllButton.SetEnabled(true); refreshAllButton.OnClick([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); for (size_t i = 0; i < scene.probes.GetCount(); ++i) { EnvironmentProbeComponent& probe = scene.probes[i]; @@ -111,7 +112,7 @@ void EnvProbeWindow::SetEntity(Entity entity) { this->entity = entity; - const EnvironmentProbeComponent* probe = wi::scene::GetScene().probes.GetComponent(entity); + const EnvironmentProbeComponent* probe = editor->GetCurrentScene().probes.GetComponent(entity); if (probe == nullptr) { diff --git a/Editor/EnvProbeWindow.h b/Editor/EnvProbeWindow.h index ccfc7e533..7434e4368 100644 --- a/Editor/EnvProbeWindow.h +++ b/Editor/EnvProbeWindow.h @@ -8,6 +8,7 @@ class EnvProbeWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/ForceFieldWindow.cpp b/Editor/ForceFieldWindow.cpp index a67fef5e6..67f31ee87 100644 --- a/Editor/ForceFieldWindow.cpp +++ b/Editor/ForceFieldWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::ecs; using namespace wi::scene; -void ForceFieldWindow::Create(EditorComponent* editor) +void ForceFieldWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Force Field Window"); SetSize(XMFLOAT2(420, 120)); @@ -20,8 +21,8 @@ void ForceFieldWindow::Create(EditorComponent* editor) addButton.SetSize(XMFLOAT2(150, hei)); addButton.SetPos(XMFLOAT2(x, y)); addButton.OnClick([=](wi::gui::EventArgs args) { - Entity entity = wi::scene::GetScene().Entity_CreateForce("editorForce"); - ForceFieldComponent* force = wi::scene::GetScene().forces.GetComponent(entity); + Entity entity = editor->GetCurrentScene().Entity_CreateForce("editorForce"); + ForceFieldComponent* force = editor->GetCurrentScene().forces.GetComponent(entity); if (force != nullptr) { switch (typeComboBox.GetSelected()) @@ -47,7 +48,7 @@ void ForceFieldWindow::Create(EditorComponent* editor) editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); SetEntity(entity); } else @@ -63,7 +64,7 @@ void ForceFieldWindow::Create(EditorComponent* editor) typeComboBox.SetPos(XMFLOAT2(x, y += step)); typeComboBox.SetSize(XMFLOAT2(200, hei)); typeComboBox.OnSelect([&](wi::gui::EventArgs args) { - ForceFieldComponent* force = wi::scene::GetScene().forces.GetComponent(entity); + ForceFieldComponent* force = editor->GetCurrentScene().forces.GetComponent(entity); if (force != nullptr && args.iValue >= 0) { switch (args.iValue) @@ -91,7 +92,7 @@ void ForceFieldWindow::Create(EditorComponent* editor) gravitySlider.SetSize(XMFLOAT2(200, hei)); gravitySlider.SetPos(XMFLOAT2(x, y += step)); gravitySlider.OnSlide([&](wi::gui::EventArgs args) { - ForceFieldComponent* force = wi::scene::GetScene().forces.GetComponent(entity); + ForceFieldComponent* force = editor->GetCurrentScene().forces.GetComponent(entity); if (force != nullptr) { force->gravity = args.fValue; @@ -106,7 +107,7 @@ void ForceFieldWindow::Create(EditorComponent* editor) rangeSlider.SetSize(XMFLOAT2(200, hei)); rangeSlider.SetPos(XMFLOAT2(x, y += step)); rangeSlider.OnSlide([&](wi::gui::EventArgs args) { - ForceFieldComponent* force = wi::scene::GetScene().forces.GetComponent(entity); + ForceFieldComponent* force = editor->GetCurrentScene().forces.GetComponent(entity); if (force != nullptr) { force->range = args.fValue; @@ -128,7 +129,7 @@ void ForceFieldWindow::SetEntity(Entity entity) { this->entity = entity; - const ForceFieldComponent* force = wi::scene::GetScene().forces.GetComponent(entity); + const ForceFieldComponent* force = editor->GetCurrentScene().forces.GetComponent(entity); if (force != nullptr) { diff --git a/Editor/ForceFieldWindow.h b/Editor/ForceFieldWindow.h index ac9d326a6..9aa3b1099 100644 --- a/Editor/ForceFieldWindow.h +++ b/Editor/ForceFieldWindow.h @@ -8,6 +8,7 @@ class ForceFieldWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/HairParticleWindow.cpp b/Editor/HairParticleWindow.cpp index e3444de6b..3185bedfe 100644 --- a/Editor/HairParticleWindow.cpp +++ b/Editor/HairParticleWindow.cpp @@ -5,8 +5,9 @@ using namespace wi::ecs; using namespace wi::scene; -void HairParticleWindow::Create(EditorComponent* editor) +void HairParticleWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Hair Particle System Window"); SetSize(XMFLOAT2(600, 260)); @@ -20,7 +21,7 @@ void HairParticleWindow::Create(EditorComponent* editor) addButton.SetPos(XMFLOAT2(x, y)); addButton.SetSize(XMFLOAT2(200, hei)); addButton.OnClick([=](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); Entity entity = scene.Entity_CreateHair("editorHair"); wi::Archive& archive = editor->AdvanceHistory(); @@ -33,7 +34,7 @@ void HairParticleWindow::Create(EditorComponent* editor) editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); SetEntity(entity); }); addButton.SetTooltip("Add new hair particle system."); @@ -53,7 +54,7 @@ void HairParticleWindow::Create(EditorComponent* editor) } else { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); hair->meshID = scene.meshes.GetEntity(args.iValue - 1); } } @@ -267,7 +268,7 @@ wi::HairParticleSystem* HairParticleWindow::GetHair() return nullptr; } - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); wi::HairParticleSystem* hair = scene.hairs.GetComponent(entity); return hair; @@ -281,7 +282,7 @@ void HairParticleWindow::UpdateData() return; } - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); meshComboBox.ClearItems(); meshComboBox.AddItem("NO MESH"); diff --git a/Editor/HairParticleWindow.h b/Editor/HairParticleWindow.h index a972fd223..346638adb 100644 --- a/Editor/HairParticleWindow.h +++ b/Editor/HairParticleWindow.h @@ -10,6 +10,7 @@ class HairParticleWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/IKWindow.cpp b/Editor/IKWindow.cpp index 285966e1d..25f46af73 100644 --- a/Editor/IKWindow.cpp +++ b/Editor/IKWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::ecs; using namespace wi::scene; -void IKWindow::Create(EditorComponent* editor) +void IKWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Inverse Kinematics (IK) Window"); SetSize(XMFLOAT2(400, 150)); @@ -28,7 +29,7 @@ void IKWindow::Create(EditorComponent* editor) targetCombo.SetPos(XMFLOAT2(x, y += step)); targetCombo.SetEnabled(false); targetCombo.OnSelect([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); InverseKinematicsComponent* ik = scene.inverse_kinematics.GetComponent(entity); if (ik != nullptr) { @@ -50,7 +51,7 @@ void IKWindow::Create(EditorComponent* editor) disabledCheckBox.SetPos(XMFLOAT2(x, y += step)); disabledCheckBox.SetSize(XMFLOAT2(hei, hei)); disabledCheckBox.OnClick([=](wi::gui::EventArgs args) { - wi::scene::GetScene().inverse_kinematics.GetComponent(entity)->SetDisabled(args.bValue); + editor->GetCurrentScene().inverse_kinematics.GetComponent(entity)->SetDisabled(args.bValue); }); AddWidget(&disabledCheckBox); @@ -59,7 +60,7 @@ void IKWindow::Create(EditorComponent* editor) chainLengthSlider.SetPos(XMFLOAT2(x, y += step)); chainLengthSlider.SetSize(XMFLOAT2(siz, hei)); chainLengthSlider.OnSlide([&](wi::gui::EventArgs args) { - wi::scene::GetScene().inverse_kinematics.GetComponent(entity)->chain_length = args.iValue; + editor->GetCurrentScene().inverse_kinematics.GetComponent(entity)->chain_length = args.iValue; }); AddWidget(&chainLengthSlider); @@ -68,7 +69,7 @@ void IKWindow::Create(EditorComponent* editor) iterationCountSlider.SetPos(XMFLOAT2(x, y += step)); iterationCountSlider.SetSize(XMFLOAT2(siz, hei)); iterationCountSlider.OnSlide([&](wi::gui::EventArgs args) { - wi::scene::GetScene().inverse_kinematics.GetComponent(entity)->iteration_count = args.iValue; + editor->GetCurrentScene().inverse_kinematics.GetComponent(entity)->iteration_count = args.iValue; }); AddWidget(&iterationCountSlider); @@ -82,7 +83,7 @@ void IKWindow::SetEntity(Entity entity) { this->entity = entity; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); const InverseKinematicsComponent* ik = scene.inverse_kinematics.GetComponent(entity); if (ik != nullptr) @@ -112,7 +113,7 @@ void IKWindow::SetEntity(Entity entity) SetEnabled(false); } - const TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + const TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { createButton.SetEnabled(true); @@ -121,7 +122,7 @@ void IKWindow::SetEntity(Entity entity) { createButton.SetText("Create"); createButton.OnClick([=](wi::gui::EventArgs args) { - wi::scene::GetScene().inverse_kinematics.Create(entity).chain_length = 1; + editor->GetCurrentScene().inverse_kinematics.Create(entity).chain_length = 1; SetEntity(entity); }); } @@ -129,7 +130,7 @@ void IKWindow::SetEntity(Entity entity) { createButton.SetText("Remove"); createButton.OnClick([=](wi::gui::EventArgs args) { - wi::scene::GetScene().inverse_kinematics.Remove_KeepSorted(entity); + editor->GetCurrentScene().inverse_kinematics.Remove_KeepSorted(entity); SetEntity(entity); }); } diff --git a/Editor/IKWindow.h b/Editor/IKWindow.h index dfd71acc1..4a3b216cd 100644 --- a/Editor/IKWindow.h +++ b/Editor/IKWindow.h @@ -8,6 +8,7 @@ class IKWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/LayerWindow.cpp b/Editor/LayerWindow.cpp index 9e8dd71dc..ab30cd44f 100644 --- a/Editor/LayerWindow.cpp +++ b/Editor/LayerWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::ecs; using namespace wi::scene; -void LayerWindow::Create(EditorComponent* editor) +void LayerWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Layer Window"); SetSize(XMFLOAT2(420, 290)); @@ -31,10 +32,10 @@ void LayerWindow::Create(EditorComponent* editor) layers[i].SetPos(XMFLOAT2(x + (i % 8) * 50, y + (i / 8) * step)); layers[i].OnClick([=](wi::gui::EventArgs args) { - LayerComponent* layer = wi::scene::GetScene().layers.GetComponent(entity); + LayerComponent* layer = editor->GetCurrentScene().layers.GetComponent(entity); if (layer == nullptr) { - layer = &wi::scene::GetScene().layers.Create(entity); + layer = &editor->GetCurrentScene().layers.Create(entity); } if (args.bValue) @@ -55,10 +56,10 @@ void LayerWindow::Create(EditorComponent* editor) enableAllButton.Create("Enable ALL"); enableAllButton.SetPos(XMFLOAT2(x, y)); enableAllButton.OnClick([this](wi::gui::EventArgs args) { - LayerComponent* layer = wi::scene::GetScene().layers.GetComponent(entity); + LayerComponent* layer = editor->GetCurrentScene().layers.GetComponent(entity); if (layer == nullptr) { - layer = &wi::scene::GetScene().layers.Create(entity); + layer = &editor->GetCurrentScene().layers.Create(entity); } if (layer == nullptr) return; @@ -69,10 +70,10 @@ void LayerWindow::Create(EditorComponent* editor) enableNoneButton.Create("Enable NONE"); enableNoneButton.SetPos(XMFLOAT2(x + 120, y)); enableNoneButton.OnClick([this](wi::gui::EventArgs args) { - LayerComponent* layer = wi::scene::GetScene().layers.GetComponent(entity); + LayerComponent* layer = editor->GetCurrentScene().layers.GetComponent(entity); if (layer == nullptr) { - layer = &wi::scene::GetScene().layers.Create(entity); + layer = &editor->GetCurrentScene().layers.Create(entity); } if (layer == nullptr) return; @@ -94,7 +95,7 @@ void LayerWindow::SetEntity(Entity entity) { SetEnabled(true); - LayerComponent* layer = wi::scene::GetScene().layers.GetComponent(entity); + LayerComponent* layer = editor->GetCurrentScene().layers.GetComponent(entity); if (layer == nullptr) { for (uint32_t i = 0; i < 32; ++i) @@ -109,13 +110,13 @@ void LayerWindow::SetEntity(Entity entity) layers[i].SetCheck(layer->GetLayerMask() & 1 << i); } - HierarchyComponent* hier = wi::scene::GetScene().hierarchy.GetComponent(entity); + HierarchyComponent* hier = editor->GetCurrentScene().hierarchy.GetComponent(entity); if (hier != nullptr) { hier->layerMask_bind = layer->layerMask; } - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->SetDirty(); diff --git a/Editor/LayerWindow.h b/Editor/LayerWindow.h index 215680fb7..7a1ce0079 100644 --- a/Editor/LayerWindow.h +++ b/Editor/LayerWindow.h @@ -8,6 +8,7 @@ class LayerWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/LightWindow.cpp b/Editor/LightWindow.cpp index ddd4f1e4b..1a056c38d 100644 --- a/Editor/LightWindow.cpp +++ b/Editor/LightWindow.cpp @@ -9,8 +9,9 @@ using namespace wi::graphics; using namespace wi::scene; -void LightWindow::Create(EditorComponent* editor) +void LightWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Light Window"); SetSize(XMFLOAT2(650, 300)); @@ -23,7 +24,7 @@ void LightWindow::Create(EditorComponent* editor) intensitySlider.SetSize(XMFLOAT2(100, hei)); intensitySlider.SetPos(XMFLOAT2(x, y)); intensitySlider.OnSlide([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->intensity = args.fValue; @@ -37,7 +38,7 @@ void LightWindow::Create(EditorComponent* editor) rangeSlider.SetSize(XMFLOAT2(100, hei)); rangeSlider.SetPos(XMFLOAT2(x, y += step)); rangeSlider.OnSlide([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->range = args.fValue; @@ -51,7 +52,7 @@ void LightWindow::Create(EditorComponent* editor) outerConeAngleSlider.SetSize(XMFLOAT2(100, hei)); outerConeAngleSlider.SetPos(XMFLOAT2(x, y += step)); outerConeAngleSlider.OnSlide([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->outerConeAngle = args.fValue; @@ -65,7 +66,7 @@ void LightWindow::Create(EditorComponent* editor) innerConeAngleSlider.SetSize(XMFLOAT2(100, hei)); innerConeAngleSlider.SetPos(XMFLOAT2(x, y += step)); innerConeAngleSlider.OnSlide([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->innerConeAngle = args.fValue; @@ -79,7 +80,7 @@ void LightWindow::Create(EditorComponent* editor) shadowCheckBox.SetSize(XMFLOAT2(hei, hei)); shadowCheckBox.SetPos(XMFLOAT2(x, y += step)); shadowCheckBox.OnClick([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->SetCastShadow(args.bValue); @@ -93,7 +94,7 @@ void LightWindow::Create(EditorComponent* editor) volumetricsCheckBox.SetSize(XMFLOAT2(hei, hei)); volumetricsCheckBox.SetPos(XMFLOAT2(x, y += step)); volumetricsCheckBox.OnClick([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->SetVolumetricsEnabled(args.bValue); @@ -107,7 +108,7 @@ void LightWindow::Create(EditorComponent* editor) haloCheckBox.SetSize(XMFLOAT2(hei, hei)); haloCheckBox.SetPos(XMFLOAT2(x, y += step)); haloCheckBox.OnClick([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->SetVisualizerEnabled(args.bValue); @@ -121,7 +122,7 @@ void LightWindow::Create(EditorComponent* editor) staticCheckBox.SetSize(XMFLOAT2(hei, hei)); staticCheckBox.SetPos(XMFLOAT2(x, y += step)); staticCheckBox.OnClick([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->SetStatic(args.bValue); @@ -135,8 +136,8 @@ void LightWindow::Create(EditorComponent* editor) addLightButton.SetPos(XMFLOAT2(x, y += step)); addLightButton.SetSize(XMFLOAT2(150, hei)); addLightButton.OnClick([=](wi::gui::EventArgs args) { - Entity entity = wi::scene::GetScene().Entity_CreateLight("editorLight", XMFLOAT3(0, 3, 0), XMFLOAT3(1, 1, 1), 2, 60); - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + Entity entity = editor->GetCurrentScene().Entity_CreateLight("editorLight", XMFLOAT3(0, 3, 0), XMFLOAT3(1, 1, 1), 2, 60); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->type = (LightComponent::LightType)typeSelectorComboBox.GetSelected(); @@ -166,7 +167,7 @@ void LightWindow::Create(EditorComponent* editor) editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); SetEntity(entity); } else @@ -183,7 +184,7 @@ void LightWindow::Create(EditorComponent* editor) colorPicker.SetVisible(true); colorPicker.SetEnabled(false); colorPicker.OnColorChanged([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { light->color = args.color.toFloat3(); @@ -195,7 +196,7 @@ void LightWindow::Create(EditorComponent* editor) typeSelectorComboBox.SetSize(XMFLOAT2(150, hei)); typeSelectorComboBox.SetPos(XMFLOAT2(x, y += step)); typeSelectorComboBox.OnSelect([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr && args.iValue >= 0) { light->SetType((LightComponent::LightType)args.iValue); @@ -226,7 +227,7 @@ void LightWindow::Create(EditorComponent* editor) shadowResolutionComboBox.AddItem("1024", 1024); shadowResolutionComboBox.AddItem("2048", 2048); shadowResolutionComboBox.OnSelect([&](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light == nullptr) return; light->forced_shadow_resolution = int(args.userdata); @@ -250,7 +251,7 @@ void LightWindow::Create(EditorComponent* editor) lensflare_Button[i].SetPos(XMFLOAT2(x, y += step)); lensflare_Button[i].SetSize(XMFLOAT2(260, hei)); lensflare_Button[i].OnClick([=](wi::gui::EventArgs args) { - LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light == nullptr) return; @@ -295,7 +296,7 @@ void LightWindow::SetEntity(Entity entity) { this->entity = entity; - const LightComponent* light = wi::scene::GetScene().lights.GetComponent(entity); + const LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); if (light != nullptr) { diff --git a/Editor/LightWindow.h b/Editor/LightWindow.h index 972524b8a..75c2a3775 100644 --- a/Editor/LightWindow.h +++ b/Editor/LightWindow.h @@ -8,6 +8,7 @@ class LightWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/MaterialWindow.cpp b/Editor/MaterialWindow.cpp index 7d0c294ec..7864a0ba2 100644 --- a/Editor/MaterialWindow.cpp +++ b/Editor/MaterialWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::graphics; using namespace wi::ecs; using namespace wi::scene; -void MaterialWindow::Create(EditorComponent* editor) +void MaterialWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Material Window"); SetSize(XMFLOAT2(730, 620)); @@ -20,7 +21,7 @@ void MaterialWindow::Create(EditorComponent* editor) shadowReceiveCheckBox.SetPos(XMFLOAT2(540, y)); shadowReceiveCheckBox.SetSize(XMFLOAT2(hei, hei)); shadowReceiveCheckBox.OnClick([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetReceiveShadow(args.bValue); }); @@ -31,7 +32,7 @@ void MaterialWindow::Create(EditorComponent* editor) shadowCasterCheckBox.SetPos(XMFLOAT2(670, y)); shadowCasterCheckBox.SetSize(XMFLOAT2(hei, hei)); shadowCasterCheckBox.OnClick([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetCastShadow(args.bValue); }); @@ -42,7 +43,7 @@ void MaterialWindow::Create(EditorComponent* editor) useVertexColorsCheckBox.SetPos(XMFLOAT2(670, y += step)); useVertexColorsCheckBox.SetSize(XMFLOAT2(hei, hei)); useVertexColorsCheckBox.OnClick([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetUseVertexColors(args.bValue); }); @@ -53,7 +54,7 @@ void MaterialWindow::Create(EditorComponent* editor) specularGlossinessCheckBox.SetPos(XMFLOAT2(670, y += step)); specularGlossinessCheckBox.SetSize(XMFLOAT2(hei, hei)); specularGlossinessCheckBox.OnClick([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetUseSpecularGlossinessWorkflow(args.bValue); SetEntity(entity); @@ -65,7 +66,7 @@ void MaterialWindow::Create(EditorComponent* editor) occlusionPrimaryCheckBox.SetPos(XMFLOAT2(670, y += step)); occlusionPrimaryCheckBox.SetSize(XMFLOAT2(hei, hei)); occlusionPrimaryCheckBox.OnClick([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetOcclusionEnabled_Primary(args.bValue); }); @@ -76,7 +77,7 @@ void MaterialWindow::Create(EditorComponent* editor) occlusionSecondaryCheckBox.SetPos(XMFLOAT2(670, y += step)); occlusionSecondaryCheckBox.SetSize(XMFLOAT2(hei, hei)); occlusionSecondaryCheckBox.OnClick([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetOcclusionEnabled_Secondary(args.bValue); }); @@ -87,7 +88,7 @@ void MaterialWindow::Create(EditorComponent* editor) windCheckBox.SetPos(XMFLOAT2(670, y += step)); windCheckBox.SetSize(XMFLOAT2(hei, hei)); windCheckBox.OnClick([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetUseWind(args.bValue); }); @@ -98,7 +99,7 @@ void MaterialWindow::Create(EditorComponent* editor) doubleSidedCheckBox.SetPos(XMFLOAT2(540, y)); doubleSidedCheckBox.SetSize(XMFLOAT2(hei, hei)); doubleSidedCheckBox.OnClick([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetDoubleSided(args.bValue); }); @@ -115,7 +116,7 @@ void MaterialWindow::Create(EditorComponent* editor) shaderTypeComboBox.SetPos(XMFLOAT2(x, y += step)); shaderTypeComboBox.SetSize(XMFLOAT2(wid, hei)); shaderTypeComboBox.OnSelect([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { if (args.iValue >= MaterialComponent::SHADERTYPE_COUNT) @@ -153,7 +154,7 @@ void MaterialWindow::Create(EditorComponent* editor) blendModeComboBox.SetPos(XMFLOAT2(x, y += step)); blendModeComboBox.SetSize(XMFLOAT2(wid, hei)); blendModeComboBox.OnSelect([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr && args.iValue >= 0) { material->userBlendMode = (wi::enums::BLENDMODE)args.iValue; @@ -173,7 +174,7 @@ void MaterialWindow::Create(EditorComponent* editor) shadingRateComboBox.SetPos(XMFLOAT2(x, y += step)); shadingRateComboBox.SetSize(XMFLOAT2(wid, hei)); shadingRateComboBox.OnSelect([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->shadingRate = (ShadingRate)args.iValue; @@ -201,7 +202,7 @@ void MaterialWindow::Create(EditorComponent* editor) normalMapSlider.SetSize(XMFLOAT2(wid, hei)); normalMapSlider.SetPos(XMFLOAT2(x, y += step)); normalMapSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetNormalMapStrength(args.fValue); }); @@ -212,7 +213,7 @@ void MaterialWindow::Create(EditorComponent* editor) roughnessSlider.SetSize(XMFLOAT2(wid, hei)); roughnessSlider.SetPos(XMFLOAT2(x, y += step)); roughnessSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetRoughness(args.fValue); }); @@ -223,7 +224,7 @@ void MaterialWindow::Create(EditorComponent* editor) reflectanceSlider.SetSize(XMFLOAT2(wid, hei)); reflectanceSlider.SetPos(XMFLOAT2(x, y += step)); reflectanceSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetReflectance(args.fValue); }); @@ -234,7 +235,7 @@ void MaterialWindow::Create(EditorComponent* editor) metalnessSlider.SetSize(XMFLOAT2(wid, hei)); metalnessSlider.SetPos(XMFLOAT2(x, y += step)); metalnessSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetMetalness(args.fValue); }); @@ -245,7 +246,7 @@ void MaterialWindow::Create(EditorComponent* editor) alphaRefSlider.SetSize(XMFLOAT2(wid, hei)); alphaRefSlider.SetPos(XMFLOAT2(x, y += step)); alphaRefSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetAlphaRef(args.fValue); }); @@ -256,7 +257,7 @@ void MaterialWindow::Create(EditorComponent* editor) emissiveSlider.SetSize(XMFLOAT2(wid, hei)); emissiveSlider.SetPos(XMFLOAT2(x, y += step)); emissiveSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetEmissiveStrength(args.fValue); }); @@ -267,7 +268,7 @@ void MaterialWindow::Create(EditorComponent* editor) transmissionSlider.SetSize(XMFLOAT2(wid, hei)); transmissionSlider.SetPos(XMFLOAT2(x, y += step)); transmissionSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetTransmissionAmount(args.fValue); }); @@ -278,7 +279,7 @@ void MaterialWindow::Create(EditorComponent* editor) refractionSlider.SetSize(XMFLOAT2(wid, hei)); refractionSlider.SetPos(XMFLOAT2(x, y += step)); refractionSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetRefractionAmount(args.fValue); }); @@ -289,7 +290,7 @@ void MaterialWindow::Create(EditorComponent* editor) pomSlider.SetSize(XMFLOAT2(wid, hei)); pomSlider.SetPos(XMFLOAT2(x, y += step)); pomSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetParallaxOcclusionMapping(args.fValue); }); @@ -300,7 +301,7 @@ void MaterialWindow::Create(EditorComponent* editor) displacementMappingSlider.SetSize(XMFLOAT2(wid, hei)); displacementMappingSlider.SetPos(XMFLOAT2(x, y += step)); displacementMappingSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetDisplacementMapping(args.fValue); }); @@ -311,7 +312,7 @@ void MaterialWindow::Create(EditorComponent* editor) subsurfaceScatteringSlider.SetSize(XMFLOAT2(wid, hei)); subsurfaceScatteringSlider.SetPos(XMFLOAT2(x, y += step)); subsurfaceScatteringSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) material->SetSubsurfaceScatteringAmount(args.fValue); }); @@ -322,7 +323,7 @@ void MaterialWindow::Create(EditorComponent* editor) texAnimFrameRateSlider.SetSize(XMFLOAT2(wid, hei)); texAnimFrameRateSlider.SetPos(XMFLOAT2(x, y += step)); texAnimFrameRateSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->texAnimFrameRate = args.fValue; @@ -335,7 +336,7 @@ void MaterialWindow::Create(EditorComponent* editor) texAnimDirectionSliderU.SetSize(XMFLOAT2(wid, hei)); texAnimDirectionSliderU.SetPos(XMFLOAT2(x, y += step)); texAnimDirectionSliderU.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->texAnimDirection.x = args.fValue; @@ -348,7 +349,7 @@ void MaterialWindow::Create(EditorComponent* editor) texAnimDirectionSliderV.SetSize(XMFLOAT2(wid, hei)); texAnimDirectionSliderV.SetPos(XMFLOAT2(x, y += step)); texAnimDirectionSliderV.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->texAnimDirection.y = args.fValue; @@ -361,7 +362,7 @@ void MaterialWindow::Create(EditorComponent* editor) texMulSliderX.SetSize(XMFLOAT2(wid, hei)); texMulSliderX.SetPos(XMFLOAT2(x, y += step)); texMulSliderX.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->SetDirty(); @@ -375,7 +376,7 @@ void MaterialWindow::Create(EditorComponent* editor) texMulSliderY.SetSize(XMFLOAT2(wid, hei)); texMulSliderY.SetPos(XMFLOAT2(x, y += step)); texMulSliderY.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->SetDirty(); @@ -390,7 +391,7 @@ void MaterialWindow::Create(EditorComponent* editor) sheenRoughnessSlider.SetSize(XMFLOAT2(wid, hei)); sheenRoughnessSlider.SetPos(XMFLOAT2(x, y += step)); sheenRoughnessSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->SetSheenRoughness(args.fValue); @@ -403,7 +404,7 @@ void MaterialWindow::Create(EditorComponent* editor) clearcoatSlider.SetSize(XMFLOAT2(wid, hei)); clearcoatSlider.SetPos(XMFLOAT2(x, y += step)); clearcoatSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->SetClearcoatFactor(args.fValue); @@ -416,7 +417,7 @@ void MaterialWindow::Create(EditorComponent* editor) clearcoatRoughnessSlider.SetSize(XMFLOAT2(wid, hei)); clearcoatRoughnessSlider.SetPos(XMFLOAT2(x, y += step)); clearcoatRoughnessSlider.OnSlide([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { material->SetClearcoatRoughness(args.fValue); @@ -436,12 +437,12 @@ void MaterialWindow::Create(EditorComponent* editor) materialNameField.SetPos(XMFLOAT2(10, y)); materialNameField.SetSize(XMFLOAT2(300, hei)); materialNameField.OnInputAccepted([=](wi::gui::EventArgs args) { - NameComponent* name = wi::scene::GetScene().names.GetComponent(entity); + NameComponent* name = editor->GetCurrentScene().names.GetComponent(entity); if (name != nullptr) { *name = args.sValue; - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); } }); AddWidget(&materialNameField); @@ -450,7 +451,7 @@ void MaterialWindow::Create(EditorComponent* editor) newMaterialButton.SetPos(XMFLOAT2(10 + 5 + 300, y)); newMaterialButton.SetSize(XMFLOAT2(100, hei)); newMaterialButton.OnClick([=](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); Entity entity = scene.Entity_CreateMaterial("editorMaterial"); wi::Archive& archive = editor->AdvanceHistory(); @@ -459,7 +460,7 @@ void MaterialWindow::Create(EditorComponent* editor) editor->ClearSelected(); editor->AddSelected(entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); @@ -484,7 +485,7 @@ void MaterialWindow::Create(EditorComponent* editor) colorPicker.SetVisible(true); colorPicker.SetEnabled(true); colorPicker.OnColorChanged([&](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { switch (colorComboBox.GetSelected()) @@ -616,7 +617,7 @@ void MaterialWindow::Create(EditorComponent* editor) break; } - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material == nullptr) return; textureSlotButton.SetImage(material->textures[args.iValue].resource); @@ -634,7 +635,7 @@ void MaterialWindow::Create(EditorComponent* editor) textureSlotButton.sprites[wi::gui::ACTIVE].params.color = wi::Color::White(); textureSlotButton.sprites[wi::gui::DEACTIVATING].params.color = wi::Color::Gray(); textureSlotButton.OnClick([this](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material == nullptr) return; @@ -678,7 +679,7 @@ void MaterialWindow::Create(EditorComponent* editor) textureSlotUvsetField.SetPos(XMFLOAT2(x + textureSlotLabel.GetScale().x + 2, y)); textureSlotUvsetField.SetSize(XMFLOAT2(hei, hei)); textureSlotUvsetField.OnInputAccepted([this](wi::gui::EventArgs args) { - MaterialComponent* material = wi::scene::GetScene().materials.GetComponent(entity); + MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity); if (material != nullptr) { int slot = textureSlotComboBox.GetSelected(); @@ -700,7 +701,7 @@ void MaterialWindow::SetEntity(Entity entity) { this->entity = entity; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); MaterialComponent* material = scene.materials.GetComponent(entity); if (material != nullptr) diff --git a/Editor/MaterialWindow.h b/Editor/MaterialWindow.h index 34dc1fcb3..d58203c7e 100644 --- a/Editor/MaterialWindow.h +++ b/Editor/MaterialWindow.h @@ -8,6 +8,7 @@ class MaterialWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index bf78a02b9..975e89a22 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -11,8 +11,9 @@ using namespace wi::ecs; using namespace wi::scene; -void MeshWindow::Create(EditorComponent* editor) +void MeshWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Mesh Window"); SetSize(XMFLOAT2(580, 380)); @@ -36,7 +37,7 @@ void MeshWindow::Create(EditorComponent* editor) subsetComboBox.SetPos(XMFLOAT2(x, y)); subsetComboBox.SetEnabled(false); subsetComboBox.OnSelect([=](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); MeshComponent* mesh = scene.meshes.GetComponent(entity); if (mesh != nullptr) { @@ -55,7 +56,7 @@ void MeshWindow::Create(EditorComponent* editor) doubleSidedCheckBox.SetSize(XMFLOAT2(hei, hei)); doubleSidedCheckBox.SetPos(XMFLOAT2(x, y += step)); doubleSidedCheckBox.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->SetDoubleSided(args.bValue); @@ -69,7 +70,7 @@ void MeshWindow::Create(EditorComponent* editor) softbodyCheckBox.SetPos(XMFLOAT2(x, y += step)); softbodyCheckBox.OnClick([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); SoftBodyPhysicsComponent* physicscomponent = scene.softbodies.GetComponent(entity); if (args.bValue) @@ -87,7 +88,7 @@ void MeshWindow::Create(EditorComponent* editor) if (physicscomponent != nullptr) { scene.softbodies.Remove(entity); - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->CreateRenderData(); @@ -103,7 +104,7 @@ void MeshWindow::Create(EditorComponent* editor) massSlider.SetSize(XMFLOAT2(100, hei)); massSlider.SetPos(XMFLOAT2(x, y += step)); massSlider.OnSlide([&](wi::gui::EventArgs args) { - SoftBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().softbodies.GetComponent(entity); + SoftBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->mass = args.fValue; @@ -116,7 +117,7 @@ void MeshWindow::Create(EditorComponent* editor) frictionSlider.SetSize(XMFLOAT2(100, hei)); frictionSlider.SetPos(XMFLOAT2(x, y += step)); frictionSlider.OnSlide([&](wi::gui::EventArgs args) { - SoftBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().softbodies.GetComponent(entity); + SoftBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->friction = args.fValue; @@ -129,7 +130,7 @@ void MeshWindow::Create(EditorComponent* editor) restitutionSlider.SetSize(XMFLOAT2(100, hei)); restitutionSlider.SetPos(XMFLOAT2(x, y += step)); restitutionSlider.OnSlide([&](wi::gui::EventArgs args) { - SoftBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().softbodies.GetComponent(entity); + SoftBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->restitution = args.fValue; @@ -142,7 +143,7 @@ void MeshWindow::Create(EditorComponent* editor) impostorCreateButton.SetSize(XMFLOAT2(200, hei)); impostorCreateButton.SetPos(XMFLOAT2(x - 50, y += step)); impostorCreateButton.OnClick([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); ImpostorComponent* impostor = scene.impostors.GetComponent(entity); if (impostor == nullptr) { @@ -162,7 +163,7 @@ void MeshWindow::Create(EditorComponent* editor) impostorDistanceSlider.SetSize(XMFLOAT2(100, hei)); impostorDistanceSlider.SetPos(XMFLOAT2(x, y += step)); impostorDistanceSlider.OnSlide([&](wi::gui::EventArgs args) { - ImpostorComponent* impostor = wi::scene::GetScene().impostors.GetComponent(entity); + ImpostorComponent* impostor = editor->GetCurrentScene().impostors.GetComponent(entity); if (impostor != nullptr) { impostor->swapInDistance = args.fValue; @@ -175,7 +176,7 @@ void MeshWindow::Create(EditorComponent* editor) tessellationFactorSlider.SetSize(XMFLOAT2(100, hei)); tessellationFactorSlider.SetPos(XMFLOAT2(x, y += step)); tessellationFactorSlider.OnSlide([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->tessellationFactor = args.fValue; @@ -188,7 +189,7 @@ void MeshWindow::Create(EditorComponent* editor) flipCullingButton.SetSize(XMFLOAT2(200, hei)); flipCullingButton.SetPos(XMFLOAT2(x - 50, y += step)); flipCullingButton.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->FlipCulling(); @@ -202,7 +203,7 @@ void MeshWindow::Create(EditorComponent* editor) flipNormalsButton.SetSize(XMFLOAT2(200, hei)); flipNormalsButton.SetPos(XMFLOAT2(x - 50, y += step)); flipNormalsButton.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->FlipNormals(); @@ -216,7 +217,7 @@ void MeshWindow::Create(EditorComponent* editor) computeNormalsSmoothButton.SetSize(XMFLOAT2(200, hei)); computeNormalsSmoothButton.SetPos(XMFLOAT2(x - 50, y += step)); computeNormalsSmoothButton.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_SMOOTH); @@ -230,7 +231,7 @@ void MeshWindow::Create(EditorComponent* editor) computeNormalsHardButton.SetSize(XMFLOAT2(200, hei)); computeNormalsHardButton.SetPos(XMFLOAT2(x - 50, y += step)); computeNormalsHardButton.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_HARD); @@ -244,7 +245,7 @@ void MeshWindow::Create(EditorComponent* editor) recenterButton.SetSize(XMFLOAT2(200, hei)); recenterButton.SetPos(XMFLOAT2(x - 50, y += step)); recenterButton.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->Recenter(); @@ -258,7 +259,7 @@ void MeshWindow::Create(EditorComponent* editor) recenterToBottomButton.SetSize(XMFLOAT2(200, hei)); recenterToBottomButton.SetPos(XMFLOAT2(x - 50, y += step)); recenterToBottomButton.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->RecenterToBottom(); @@ -272,7 +273,7 @@ void MeshWindow::Create(EditorComponent* editor) mergeButton.SetSize(XMFLOAT2(200, hei)); mergeButton.SetPos(XMFLOAT2(x - 50, y += step)); mergeButton.OnClick([=](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); MeshComponent merged_mesh; bool valid_normals = false; bool valid_uvset_0 = false; @@ -454,7 +455,7 @@ void MeshWindow::Create(EditorComponent* editor) optimizeButton.SetSize(XMFLOAT2(200, hei)); optimizeButton.SetPos(XMFLOAT2(x - 50, y += step)); optimizeButton.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { // https://github.com/zeux/meshoptimizer#vertex-cache-optimization @@ -486,7 +487,7 @@ void MeshWindow::Create(EditorComponent* editor) subsetMaterialComboBox.SetPos(XMFLOAT2(x + 180, y)); subsetMaterialComboBox.SetEnabled(false); subsetMaterialComboBox.OnSelect([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); MeshComponent* mesh = scene.meshes.GetComponent(entity); if (mesh != nullptr && subset >= 0 && subset < mesh->subsets.size()) { @@ -510,7 +511,7 @@ void MeshWindow::Create(EditorComponent* editor) morphTargetCombo.SetSize(XMFLOAT2(100, hei)); morphTargetCombo.SetPos(XMFLOAT2(x + 280, y += step)); morphTargetCombo.OnSelect([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr && args.iValue < (int)mesh->targets.size()) { morphTargetSlider.SetValue(mesh->targets[args.iValue].weight); @@ -524,7 +525,7 @@ void MeshWindow::Create(EditorComponent* editor) morphTargetSlider.SetSize(XMFLOAT2(100, hei)); morphTargetSlider.SetPos(XMFLOAT2(x + 280, y += step)); morphTargetSlider.OnSlide([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr && morphTargetCombo.GetSelected() < (int)mesh->targets.size()) { mesh->targets[morphTargetCombo.GetSelected()].weight = args.fValue; @@ -538,7 +539,7 @@ void MeshWindow::Create(EditorComponent* editor) lodgenButton.SetSize(XMFLOAT2(200, hei)); lodgenButton.SetPos(XMFLOAT2(x + 180, y += step)); lodgenButton.OnClick([&](wi::gui::EventArgs args) { - MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); + MeshComponent* mesh = editor->GetCurrentScene().meshes.GetComponent(entity); if (mesh != nullptr) { if (mesh->subsets_per_lod == 0) @@ -682,7 +683,7 @@ void MeshWindow::SetEntity(Entity entity, int subset) this->entity = entity; this->subset = subset; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); const MeshComponent* mesh = scene.meshes.GetComponent(entity); @@ -748,7 +749,7 @@ void MeshWindow::SetEntity(Entity entity, int subset) softbodyCheckBox.SetCheck(false); - SoftBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().softbodies.GetComponent(entity); + SoftBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { softbodyCheckBox.SetCheck(true); diff --git a/Editor/MeshWindow.h b/Editor/MeshWindow.h index 98f64e865..1d31c4092 100644 --- a/Editor/MeshWindow.h +++ b/Editor/MeshWindow.h @@ -8,6 +8,7 @@ class MeshWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; int subset = -1; void SetEntity(wi::ecs::Entity entity, int subset); diff --git a/Editor/ModelImporter_GLTF.cpp b/Editor/ModelImporter_GLTF.cpp index 14a4df57e..d305e4217 100644 --- a/Editor/ModelImporter_GLTF.cpp +++ b/Editor/ModelImporter_GLTF.cpp @@ -190,7 +190,7 @@ void LoadNode(int nodeIndex, Entity parent, LoaderState& state) node.name = "cam" + std::to_string(camID++); } - entity = scene.Entity_CreateCamera(node.name, wi::scene::GetCamera().width, wi::scene::GetCamera().height); + entity = scene.Entity_CreateCamera(node.name, 16, 9); } auto ext_lights_punctual = node.extensions.find("KHR_lights_punctual"); diff --git a/Editor/NameWindow.cpp b/Editor/NameWindow.cpp index d5a6e32c1..5d09c2194 100644 --- a/Editor/NameWindow.cpp +++ b/Editor/NameWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::ecs; using namespace wi::scene; -void NameWindow::Create(EditorComponent* editor) +void NameWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Name Window"); SetSize(XMFLOAT2(360, 80)); @@ -22,14 +23,14 @@ void NameWindow::Create(EditorComponent* editor) nameInput.SetPos(XMFLOAT2(x, y)); nameInput.SetSize(XMFLOAT2(siz, hei)); nameInput.OnInputAccepted([=](wi::gui::EventArgs args) { - NameComponent* name = wi::scene::GetScene().names.GetComponent(entity); + NameComponent* name = editor->GetCurrentScene().names.GetComponent(entity); if (name == nullptr) { - name = &wi::scene::GetScene().names.Create(entity); + name = &editor->GetCurrentScene().names.Create(entity); } name->name = args.sValue; - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); }); AddWidget(&nameInput); @@ -47,7 +48,7 @@ void NameWindow::SetEntity(Entity entity) { SetEnabled(true); - NameComponent* name = wi::scene::GetScene().names.GetComponent(entity); + NameComponent* name = editor->GetCurrentScene().names.GetComponent(entity); if (name != nullptr) { nameInput.SetValue(name->name); diff --git a/Editor/NameWindow.h b/Editor/NameWindow.h index 2935dbeb0..a9636b9c2 100644 --- a/Editor/NameWindow.h +++ b/Editor/NameWindow.h @@ -8,6 +8,7 @@ class NameWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/ObjectWindow.cpp b/Editor/ObjectWindow.cpp index 3959b6724..0d9adfa1e 100644 --- a/Editor/ObjectWindow.cpp +++ b/Editor/ObjectWindow.cpp @@ -256,9 +256,9 @@ static Atlas_Dim GenerateMeshAtlas(MeshComponent& meshcomponent, uint32_t resolu } -void ObjectWindow::Create(EditorComponent* editor) +void ObjectWindow::Create(EditorComponent* _editor) { - this->editor = editor; + editor = _editor; wi::gui::Window::Create("Object Window"); SetSize(XMFLOAT2(670, 320)); @@ -280,7 +280,7 @@ void ObjectWindow::Create(EditorComponent* editor) renderableCheckBox.SetPos(XMFLOAT2(x, y += step)); renderableCheckBox.SetCheck(true); renderableCheckBox.OnClick([&](wi::gui::EventArgs args) { - ObjectComponent* object = wi::scene::GetScene().objects.GetComponent(entity); + ObjectComponent* object = editor->GetCurrentScene().objects.GetComponent(entity); if (object != nullptr) { object->SetRenderable(args.bValue); @@ -294,7 +294,7 @@ void ObjectWindow::Create(EditorComponent* editor) shadowCheckBox.SetPos(XMFLOAT2(x, y += step)); shadowCheckBox.SetCheck(true); shadowCheckBox.OnClick([&](wi::gui::EventArgs args) { - ObjectComponent* object = wi::scene::GetScene().objects.GetComponent(entity); + ObjectComponent* object = editor->GetCurrentScene().objects.GetComponent(entity); if (object != nullptr) { object->SetCastShadow(args.bValue); @@ -307,7 +307,7 @@ void ObjectWindow::Create(EditorComponent* editor) ditherSlider.SetSize(XMFLOAT2(100, hei)); ditherSlider.SetPos(XMFLOAT2(x, y += step)); ditherSlider.OnSlide([&](wi::gui::EventArgs args) { - ObjectComponent* object = wi::scene::GetScene().objects.GetComponent(entity); + ObjectComponent* object = editor->GetCurrentScene().objects.GetComponent(entity); if (object != nullptr) { object->color.w = 1 - args.fValue; @@ -320,7 +320,7 @@ void ObjectWindow::Create(EditorComponent* editor) cascadeMaskSlider.SetSize(XMFLOAT2(100, hei)); cascadeMaskSlider.SetPos(XMFLOAT2(x, y += step)); cascadeMaskSlider.OnSlide([&](wi::gui::EventArgs args) { - ObjectComponent* object = wi::scene::GetScene().objects.GetComponent(entity); + ObjectComponent* object = editor->GetCurrentScene().objects.GetComponent(entity); if (object != nullptr) { object->cascadeMask = (uint32_t)args.iValue; @@ -333,7 +333,7 @@ void ObjectWindow::Create(EditorComponent* editor) lodSlider.SetSize(XMFLOAT2(100, hei)); lodSlider.SetPos(XMFLOAT2(x, y += step)); lodSlider.OnSlide([&](wi::gui::EventArgs args) { - ObjectComponent* object = wi::scene::GetScene().objects.GetComponent(entity); + ObjectComponent* object = editor->GetCurrentScene().objects.GetComponent(entity); if (object != nullptr) { object->lod_distance_multiplier = args.fValue; @@ -364,7 +364,7 @@ void ObjectWindow::Create(EditorComponent* editor) if (entity == INVALID_ENTITY) return; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); RigidBodyPhysicsComponent* physicscomponent = scene.rigidbodies.GetComponent(entity); if (args.iValue == 0) @@ -465,7 +465,7 @@ void ObjectWindow::Create(EditorComponent* editor) XSlider.SetSize(XMFLOAT2(100, hei)); XSlider.SetPos(XMFLOAT2(x, y += step)); XSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { switch (physicscomponent->shape) @@ -490,7 +490,7 @@ void ObjectWindow::Create(EditorComponent* editor) YSlider.SetSize(XMFLOAT2(100, hei)); YSlider.SetPos(XMFLOAT2(x, y += step)); YSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { switch (physicscomponent->shape) @@ -512,7 +512,7 @@ void ObjectWindow::Create(EditorComponent* editor) ZSlider.SetSize(XMFLOAT2(100, hei)); ZSlider.SetPos(XMFLOAT2(x, y += step)); ZSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { switch (physicscomponent->shape) @@ -532,7 +532,7 @@ void ObjectWindow::Create(EditorComponent* editor) massSlider.SetSize(XMFLOAT2(100, hei)); massSlider.SetPos(XMFLOAT2(x, y += step)); massSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->mass = args.fValue; @@ -545,7 +545,7 @@ void ObjectWindow::Create(EditorComponent* editor) frictionSlider.SetSize(XMFLOAT2(100, hei)); frictionSlider.SetPos(XMFLOAT2(x, y += step)); frictionSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->friction = args.fValue; @@ -558,7 +558,7 @@ void ObjectWindow::Create(EditorComponent* editor) restitutionSlider.SetSize(XMFLOAT2(100, hei)); restitutionSlider.SetPos(XMFLOAT2(x, y += step)); restitutionSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->restitution = args.fValue; @@ -571,7 +571,7 @@ void ObjectWindow::Create(EditorComponent* editor) lineardampingSlider.SetSize(XMFLOAT2(100, hei)); lineardampingSlider.SetPos(XMFLOAT2(x, y += step)); lineardampingSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->damping_linear = args.fValue; @@ -584,7 +584,7 @@ void ObjectWindow::Create(EditorComponent* editor) angulardampingSlider.SetSize(XMFLOAT2(100, hei)); angulardampingSlider.SetPos(XMFLOAT2(x, y += step)); angulardampingSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->damping_angular = args.fValue; @@ -597,7 +597,7 @@ void ObjectWindow::Create(EditorComponent* editor) physicsMeshLODSlider.SetSize(XMFLOAT2(100, hei)); physicsMeshLODSlider.SetPos(XMFLOAT2(x, y += step)); physicsMeshLODSlider.OnSlide([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { if (physicscomponent->mesh_lod != uint32_t(args.iValue)) @@ -616,7 +616,7 @@ void ObjectWindow::Create(EditorComponent* editor) kinematicCheckBox.SetPos(XMFLOAT2(x, y += step)); kinematicCheckBox.SetCheck(false); kinematicCheckBox.OnClick([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->SetKinematic(args.bValue); @@ -630,7 +630,7 @@ void ObjectWindow::Create(EditorComponent* editor) disabledeactivationCheckBox.SetPos(XMFLOAT2(x, y += step)); disabledeactivationCheckBox.SetCheck(false); disabledeactivationCheckBox.OnClick([&](wi::gui::EventArgs args) { - RigidBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().rigidbodies.GetComponent(entity); + RigidBodyPhysicsComponent* physicscomponent = editor->GetCurrentScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->SetDisableDeactivation(args.bValue); @@ -669,7 +669,7 @@ void ObjectWindow::Create(EditorComponent* editor) generateLightmapButton.SetSize(XMFLOAT2(140, hei)); generateLightmapButton.OnClick([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); enum UV_GEN_TYPE { @@ -750,7 +750,7 @@ void ObjectWindow::Create(EditorComponent* editor) stopLightmapGenButton.SetSize(XMFLOAT2(140, hei)); stopLightmapGenButton.OnClick([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); for (auto& x : this->editor->translator.selected) { @@ -771,7 +771,7 @@ void ObjectWindow::Create(EditorComponent* editor) clearLightmapButton.SetSize(XMFLOAT2(140, hei)); clearLightmapButton.OnClick([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); for (auto& x : this->editor->translator.selected) { @@ -800,7 +800,7 @@ void ObjectWindow::Create(EditorComponent* editor) colorPicker.SetVisible(true); colorPicker.SetEnabled(true); colorPicker.OnColorChanged([&](wi::gui::EventArgs args) { - ObjectComponent* object = wi::scene::GetScene().objects.GetComponent(entity); + ObjectComponent* object = editor->GetCurrentScene().objects.GetComponent(entity); if (object != nullptr) { switch (colorComboBox.GetSelected()) @@ -835,7 +835,7 @@ void ObjectWindow::SetEntity(Entity entity) this->entity = entity; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); const ObjectComponent* object = scene.objects.GetComponent(entity); diff --git a/Editor/ObjectWindow.h b/Editor/ObjectWindow.h index 32c2e9ee0..422800b1d 100644 --- a/Editor/ObjectWindow.h +++ b/Editor/ObjectWindow.h @@ -8,7 +8,7 @@ class ObjectWindow : public wi::gui::Window public: void Create(EditorComponent* editor); - EditorComponent* editor; + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/PaintToolWindow.cpp b/Editor/PaintToolWindow.cpp index 0ef772d1b..617c47bc9 100644 --- a/Editor/PaintToolWindow.cpp +++ b/Editor/PaintToolWindow.cpp @@ -9,9 +9,9 @@ using namespace wi::ecs; using namespace wi::scene; using namespace wi::graphics; -void PaintToolWindow::Create(EditorComponent* editor) +void PaintToolWindow::Create(EditorComponent* _editor) { - this->editor = editor; + editor = _editor; wi::gui::Window::Create("Paint Tool Window"); SetSize(XMFLOAT2(360, 540)); @@ -162,7 +162,7 @@ void PaintToolWindow::Create(EditorComponent* editor) saveTextureButton.SetPos(XMFLOAT2(x, y += step)); saveTextureButton.OnClick([this] (wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); ObjectComponent* object = scene.objects.GetComponent(entity); if (object == nullptr || object->meshID == INVALID_ENTITY) return; @@ -321,8 +321,8 @@ void PaintToolWindow::Update(float dt) const bool backfaces = backfaceCheckBox.GetCheck(); const bool wireframe = wireCheckBox.GetCheck(); - Scene& scene = wi::scene::GetScene(); - const CameraComponent& camera = wi::scene::GetCamera(); + Scene& scene = editor->GetCurrentScene(); + const CameraComponent& camera = editor->GetCurrentEditorScene().camera; const XMVECTOR C = XMLoadFloat2(&pos); const XMMATRIX VP = camera.GetViewProjection(); const XMVECTOR MUL = XMVectorSet(0.5f, -0.5f, 1, 1); @@ -1044,7 +1044,7 @@ void PaintToolWindow::RecordHistory(bool start, CommandList cmd) } wi::Archive& archive = *currentHistory; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); switch (GetMode()) { @@ -1177,7 +1177,7 @@ void PaintToolWindow::ConsumeHistoryOperation(wi::Archive& archive, bool undo) modeComboBox.SetSelected(historymode); - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); switch (historymode) { diff --git a/Editor/PostprocessWindow.cpp b/Editor/PostprocessWindow.cpp index ce4975def..dcbabe5bf 100644 --- a/Editor/PostprocessWindow.cpp +++ b/Editor/PostprocessWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::graphics; -void PostprocessWindow::Create(EditorComponent* editor) +void PostprocessWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("PostProcess Window"); SetSize(XMFLOAT2(420, 400)); diff --git a/Editor/PostprocessWindow.h b/Editor/PostprocessWindow.h index cccad26ff..cf338f7af 100644 --- a/Editor/PostprocessWindow.h +++ b/Editor/PostprocessWindow.h @@ -8,6 +8,8 @@ class PostprocessWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; + wi::gui::Slider exposureSlider; wi::gui::CheckBox lensFlareCheckBox; wi::gui::CheckBox lightShaftsCheckBox; diff --git a/Editor/RendererWindow.cpp b/Editor/RendererWindow.cpp index 9f20c5aa5..6f0e84360 100644 --- a/Editor/RendererWindow.cpp +++ b/Editor/RendererWindow.cpp @@ -3,8 +3,9 @@ #include "Editor.h" #include "shaders/ShaderInterop_DDGI.h" -void RendererWindow::Create(EditorComponent* editor) +void RendererWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Renderer Window"); wi::renderer::SetToDrawDebugEnvProbes(true); @@ -67,7 +68,7 @@ void RendererWindow::Create(EditorComponent* editor) resolutionScaleSlider.SetSize(XMFLOAT2(100, itemheight)); resolutionScaleSlider.SetPos(XMFLOAT2(x, y += step)); resolutionScaleSlider.SetValue(editor->resolutionScale); - resolutionScaleSlider.OnSlide([editor](wi::gui::EventArgs args) { + resolutionScaleSlider.OnSlide([=](wi::gui::EventArgs args) { if (editor->resolutionScale != args.fValue) { editor->renderPath->resolutionScale = args.fValue; @@ -82,7 +83,7 @@ void RendererWindow::Create(EditorComponent* editor) GIBoostSlider.SetSize(XMFLOAT2(100, itemheight)); GIBoostSlider.SetPos(XMFLOAT2(x, y += step)); GIBoostSlider.SetValue(wi::renderer::GetGIBoost()); - GIBoostSlider.OnSlide([editor](wi::gui::EventArgs args) { + GIBoostSlider.OnSlide([=](wi::gui::EventArgs args) { wi::renderer::SetGIBoost(args.fValue); }); AddWidget(&GIBoostSlider); @@ -237,7 +238,7 @@ void RendererWindow::Create(EditorComponent* editor) variableRateShadingClassificationCheckBox.SetTooltip("Enable classification of variable rate shading on the screen. Less important parts will be shaded with lesser resolution.\nRequires Tier2 support for variable shading rate"); variableRateShadingClassificationCheckBox.SetPos(XMFLOAT2(x, y += step)); variableRateShadingClassificationCheckBox.SetSize(XMFLOAT2(itemheight, itemheight)); - variableRateShadingClassificationCheckBox.OnClick([editor](wi::gui::EventArgs args) { + variableRateShadingClassificationCheckBox.OnClick([=](wi::gui::EventArgs args) { wi::renderer::SetVariableRateShadingClassification(args.bValue); editor->ResizeBuffers(); }); diff --git a/Editor/RendererWindow.h b/Editor/RendererWindow.h index 59fb8f169..3ca245e9e 100644 --- a/Editor/RendererWindow.h +++ b/Editor/RendererWindow.h @@ -21,7 +21,9 @@ enum PICKTYPE class RendererWindow : public wi::gui::Window { public: - void Create(EditorComponent* editorcomponent); + void Create(EditorComponent* editor); + + EditorComponent* editor = nullptr; wi::gui::CheckBox vsyncCheckBox; wi::gui::ComboBox swapchainComboBox; diff --git a/Editor/SoundWindow.cpp b/Editor/SoundWindow.cpp index c408795b7..591debca6 100644 --- a/Editor/SoundWindow.cpp +++ b/Editor/SoundWindow.cpp @@ -7,8 +7,9 @@ using namespace wi::graphics; using namespace wi::ecs; using namespace wi::scene; -void SoundWindow::Create(EditorComponent* editor) +void SoundWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Sound Window"); SetSize(XMFLOAT2(440, 220)); @@ -69,7 +70,7 @@ void SoundWindow::Create(EditorComponent* editor) params.extensions = wi::resourcemanager::GetSupportedSoundExtensions(); wi::helper::FileDialog(params, [=](std::string fileName) { wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { - Entity entity = GetScene().Entity_CreateSound("editorSound", fileName); + Entity entity = editor->GetCurrentScene().Entity_CreateSound("editorSound", fileName); wi::Archive& archive = editor->AdvanceHistory(); archive << EditorComponent::HISTORYOP_ADD; @@ -81,7 +82,7 @@ void SoundWindow::Create(EditorComponent* editor) editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); SetEntity(entity); }); }); @@ -98,14 +99,14 @@ void SoundWindow::Create(EditorComponent* editor) nameField.SetPos(XMFLOAT2(x, y += step)); nameField.SetSize(XMFLOAT2(300, hei)); nameField.OnInputAccepted([=](wi::gui::EventArgs args) { - NameComponent* name = wi::scene::GetScene().names.GetComponent(entity); + NameComponent* name = editor->GetCurrentScene().names.GetComponent(entity); if (name == nullptr) { - name = &wi::scene::GetScene().names.Create(entity); + name = &editor->GetCurrentScene().names.Create(entity); } *name = args.sValue; - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); }); AddWidget(&nameField); nameField.SetEnabled(false); @@ -115,7 +116,7 @@ void SoundWindow::Create(EditorComponent* editor) playstopButton.SetPos(XMFLOAT2(x, y += step)); playstopButton.SetSize(XMFLOAT2(80, hei)); playstopButton.OnClick([&](wi::gui::EventArgs args) { - SoundComponent* sound = GetScene().sounds.GetComponent(entity); + SoundComponent* sound = editor->GetCurrentScene().sounds.GetComponent(entity); if (sound != nullptr) { if (sound->IsPlaying()) @@ -138,7 +139,7 @@ void SoundWindow::Create(EditorComponent* editor) loopedCheckbox.SetPos(XMFLOAT2(x + 150, y)); loopedCheckbox.SetSize(XMFLOAT2(hei, hei)); loopedCheckbox.OnClick([&](wi::gui::EventArgs args) { - SoundComponent* sound = GetScene().sounds.GetComponent(entity); + SoundComponent* sound = editor->GetCurrentScene().sounds.GetComponent(entity); if (sound != nullptr) { sound->SetLooped(args.bValue); @@ -152,7 +153,7 @@ void SoundWindow::Create(EditorComponent* editor) reverbCheckbox.SetPos(XMFLOAT2(x + 240, y)); reverbCheckbox.SetSize(XMFLOAT2(hei, hei)); reverbCheckbox.OnClick([&](wi::gui::EventArgs args) { - SoundComponent* sound = GetScene().sounds.GetComponent(entity); + SoundComponent* sound = editor->GetCurrentScene().sounds.GetComponent(entity); if (sound != nullptr) { sound->soundinstance.SetEnableReverb(args.bValue); @@ -167,7 +168,7 @@ void SoundWindow::Create(EditorComponent* editor) disable3dCheckbox.SetPos(XMFLOAT2(x + 300, y)); disable3dCheckbox.SetSize(XMFLOAT2(hei, hei)); disable3dCheckbox.OnClick([&](wi::gui::EventArgs args) { - SoundComponent* sound = GetScene().sounds.GetComponent(entity); + SoundComponent* sound = editor->GetCurrentScene().sounds.GetComponent(entity); if (sound != nullptr) { sound->SetDisable3D(args.bValue); @@ -182,7 +183,7 @@ void SoundWindow::Create(EditorComponent* editor) volumeSlider.SetPos(XMFLOAT2(x + 60, y += step)); volumeSlider.SetSize(XMFLOAT2(240, hei)); volumeSlider.OnSlide([&](wi::gui::EventArgs args) { - SoundComponent* sound = GetScene().sounds.GetComponent(entity); + SoundComponent* sound = editor->GetCurrentScene().sounds.GetComponent(entity); if (sound != nullptr) { sound->volume = args.fValue; @@ -195,7 +196,7 @@ void SoundWindow::Create(EditorComponent* editor) submixComboBox.SetPos(XMFLOAT2(x + 80, y += step)); submixComboBox.SetSize(XMFLOAT2(180, hei)); submixComboBox.OnSelect([&](wi::gui::EventArgs args) { - SoundComponent* sound = GetScene().sounds.GetComponent(entity); + SoundComponent* sound = editor->GetCurrentScene().sounds.GetComponent(entity); if (sound != nullptr) { sound->soundinstance.type = (wi::audio::SUBMIX_TYPE)args.iValue; @@ -222,7 +223,7 @@ void SoundWindow::SetEntity(Entity entity) { this->entity = entity; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); SoundComponent* sound = scene.sounds.GetComponent(entity); NameComponent* name = scene.names.GetComponent(entity); diff --git a/Editor/SoundWindow.h b/Editor/SoundWindow.h index 326d3fcd9..137aa85ee 100644 --- a/Editor/SoundWindow.h +++ b/Editor/SoundWindow.h @@ -8,6 +8,7 @@ class SoundWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/SpringWindow.cpp b/Editor/SpringWindow.cpp index 0e3a36f1a..8f3d53f67 100644 --- a/Editor/SpringWindow.cpp +++ b/Editor/SpringWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::ecs; using namespace wi::scene; -void SpringWindow::Create(EditorComponent* editor) +void SpringWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Spring Window"); SetSize(XMFLOAT2(460, 200)); @@ -34,7 +35,7 @@ void SpringWindow::Create(EditorComponent* editor) disabledCheckBox.SetPos(XMFLOAT2(x, y += step)); disabledCheckBox.SetSize(XMFLOAT2(hei, hei)); disabledCheckBox.OnClick([=](wi::gui::EventArgs args) { - wi::scene::GetScene().springs.GetComponent(entity)->SetDisabled(args.bValue); + editor->GetCurrentScene().springs.GetComponent(entity)->SetDisabled(args.bValue); }); AddWidget(&disabledCheckBox); @@ -43,7 +44,7 @@ void SpringWindow::Create(EditorComponent* editor) stretchCheckBox.SetPos(XMFLOAT2(x, y += step)); stretchCheckBox.SetSize(XMFLOAT2(hei, hei)); stretchCheckBox.OnClick([=](wi::gui::EventArgs args) { - wi::scene::GetScene().springs.GetComponent(entity)->SetStretchEnabled(args.bValue); + editor->GetCurrentScene().springs.GetComponent(entity)->SetStretchEnabled(args.bValue); }); AddWidget(&stretchCheckBox); @@ -52,7 +53,7 @@ void SpringWindow::Create(EditorComponent* editor) gravityCheckBox.SetPos(XMFLOAT2(x, y += step)); gravityCheckBox.SetSize(XMFLOAT2(hei, hei)); gravityCheckBox.OnClick([=](wi::gui::EventArgs args) { - wi::scene::GetScene().springs.GetComponent(entity)->SetGravityEnabled(args.bValue); + editor->GetCurrentScene().springs.GetComponent(entity)->SetGravityEnabled(args.bValue); }); AddWidget(&gravityCheckBox); @@ -61,7 +62,7 @@ void SpringWindow::Create(EditorComponent* editor) stiffnessSlider.SetPos(XMFLOAT2(x, y += step)); stiffnessSlider.SetSize(XMFLOAT2(siz, hei)); stiffnessSlider.OnSlide([&](wi::gui::EventArgs args) { - wi::scene::GetScene().springs.GetComponent(entity)->stiffness = args.fValue; + editor->GetCurrentScene().springs.GetComponent(entity)->stiffness = args.fValue; }); AddWidget(&stiffnessSlider); @@ -70,7 +71,7 @@ void SpringWindow::Create(EditorComponent* editor) dampingSlider.SetPos(XMFLOAT2(x, y += step)); dampingSlider.SetSize(XMFLOAT2(siz, hei)); dampingSlider.OnSlide([&](wi::gui::EventArgs args) { - wi::scene::GetScene().springs.GetComponent(entity)->damping = args.fValue; + editor->GetCurrentScene().springs.GetComponent(entity)->damping = args.fValue; }); AddWidget(&dampingSlider); @@ -79,7 +80,7 @@ void SpringWindow::Create(EditorComponent* editor) windSlider.SetPos(XMFLOAT2(x, y += step)); windSlider.SetSize(XMFLOAT2(siz, hei)); windSlider.OnSlide([&](wi::gui::EventArgs args) { - wi::scene::GetScene().springs.GetComponent(entity)->wind_affection = args.fValue; + editor->GetCurrentScene().springs.GetComponent(entity)->wind_affection = args.fValue; }); AddWidget(&windSlider); @@ -93,7 +94,7 @@ void SpringWindow::SetEntity(Entity entity) { this->entity = entity; - const SpringComponent* spring = wi::scene::GetScene().springs.GetComponent(entity); + const SpringComponent* spring = editor->GetCurrentScene().springs.GetComponent(entity); if (spring != nullptr) { @@ -111,7 +112,7 @@ void SpringWindow::SetEntity(Entity entity) SetEnabled(false); } - const TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + const TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { createButton.SetEnabled(true); @@ -120,7 +121,7 @@ void SpringWindow::SetEntity(Entity entity) { createButton.SetText("Create"); createButton.OnClick([=](wi::gui::EventArgs args) { - wi::scene::GetScene().springs.Create(entity); + editor->GetCurrentScene().springs.Create(entity); SetEntity(entity); }); } @@ -128,7 +129,7 @@ void SpringWindow::SetEntity(Entity entity) { createButton.SetText("Remove"); createButton.OnClick([=](wi::gui::EventArgs args) { - wi::scene::GetScene().springs.Remove_KeepSorted(entity); + editor->GetCurrentScene().springs.Remove_KeepSorted(entity); SetEntity(entity); }); } diff --git a/Editor/SpringWindow.h b/Editor/SpringWindow.h index d1a40efec..00f828367 100644 --- a/Editor/SpringWindow.h +++ b/Editor/SpringWindow.h @@ -8,6 +8,7 @@ class SpringWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/TerrainGenerator.h b/Editor/TerrainGenerator.h index 4c4033cc0..f36e8c193 100644 --- a/Editor/TerrainGenerator.h +++ b/Editor/TerrainGenerator.h @@ -52,7 +52,7 @@ struct ChunkData struct TerrainGenerator : public wi::gui::Window { wi::ecs::Entity terrainEntity = wi::ecs::INVALID_ENTITY; - wi::scene::Scene* scene = &wi::scene::GetScene(); // by default it uses the global scene, but this can be changed + wi::scene::Scene* scene = nullptr; wi::scene::MaterialComponent material_Base; wi::scene::MaterialComponent material_Slope; wi::scene::MaterialComponent material_LowAltitude; diff --git a/Editor/TransformWindow.cpp b/Editor/TransformWindow.cpp index 2c1dd6831..e53b5b890 100644 --- a/Editor/TransformWindow.cpp +++ b/Editor/TransformWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::ecs; using namespace wi::scene; -void TransformWindow::Create(EditorComponent* editor) +void TransformWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Transform Window"); SetSize(XMFLOAT2(480, 200)); @@ -23,7 +24,7 @@ void TransformWindow::Create(EditorComponent* editor) createButton.SetSize(XMFLOAT2(350, hei)); createButton.OnClick([=](wi::gui::EventArgs args) { Entity entity = CreateEntity(); - wi::scene::GetScene().transforms.Create(entity); + editor->GetCurrentScene().transforms.Create(entity); wi::Archive& archive = editor->AdvanceHistory(); archive << EditorComponent::HISTORYOP_ADD; @@ -35,7 +36,7 @@ void TransformWindow::Create(EditorComponent* editor) editor->RecordSelection(archive); editor->RecordAddedEntity(archive, entity); - editor->RefreshSceneGraphView(); + editor->RefreshEntityTree(); SetEntity(entity); }); AddWidget(&createButton); @@ -45,7 +46,7 @@ void TransformWindow::Create(EditorComponent* editor) clearButton.SetPos(XMFLOAT2(x, y += step)); clearButton.SetSize(XMFLOAT2(350, hei)); clearButton.OnClick([=](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->ClearTransform(); @@ -58,7 +59,7 @@ void TransformWindow::Create(EditorComponent* editor) parentCombo.SetPos(XMFLOAT2(x, y += step)); parentCombo.SetEnabled(false); parentCombo.OnSelect([&](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); if (args.iValue == 0) { @@ -79,7 +80,7 @@ void TransformWindow::Create(EditorComponent* editor) txInput.SetPos(XMFLOAT2(x, y += step)); txInput.SetSize(XMFLOAT2(siz, hei)); txInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->translation_local.x = args.fValue; @@ -94,7 +95,7 @@ void TransformWindow::Create(EditorComponent* editor) tyInput.SetPos(XMFLOAT2(x, y += step)); tyInput.SetSize(XMFLOAT2(siz, hei)); tyInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->translation_local.y = args.fValue; @@ -109,7 +110,7 @@ void TransformWindow::Create(EditorComponent* editor) tzInput.SetPos(XMFLOAT2(x, y += step)); tzInput.SetSize(XMFLOAT2(siz, hei)); tzInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->translation_local.z = args.fValue; @@ -131,7 +132,7 @@ void TransformWindow::Create(EditorComponent* editor) rollInput.SetPos(XMFLOAT2(x, y += step)); rollInput.SetSize(XMFLOAT2(siz, hei)); rollInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { float roll = float(std::atof(rollInput.GetValue().c_str())) / 180.0f * XM_PI; @@ -152,7 +153,7 @@ void TransformWindow::Create(EditorComponent* editor) pitchInput.SetPos(XMFLOAT2(x, y += step)); pitchInput.SetSize(XMFLOAT2(siz, hei)); pitchInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { float roll = float(std::atof(rollInput.GetValue().c_str())) / 180.0f * XM_PI; @@ -173,7 +174,7 @@ void TransformWindow::Create(EditorComponent* editor) yawInput.SetPos(XMFLOAT2(x, y += step)); yawInput.SetSize(XMFLOAT2(siz, hei)); yawInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { float roll = float(std::atof(rollInput.GetValue().c_str())) / 180.0f * XM_PI; @@ -197,7 +198,7 @@ void TransformWindow::Create(EditorComponent* editor) rxInput.SetPos(XMFLOAT2(x, y += step)); rxInput.SetSize(XMFLOAT2(siz, hei)); rxInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->rotation_local.x = args.fValue; @@ -214,7 +215,7 @@ void TransformWindow::Create(EditorComponent* editor) ryInput.SetPos(XMFLOAT2(x, y += step)); ryInput.SetSize(XMFLOAT2(siz, hei)); ryInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->rotation_local.y = args.fValue; @@ -231,7 +232,7 @@ void TransformWindow::Create(EditorComponent* editor) rzInput.SetPos(XMFLOAT2(x, y += step)); rzInput.SetSize(XMFLOAT2(siz, hei)); rzInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->rotation_local.z = args.fValue; @@ -248,7 +249,7 @@ void TransformWindow::Create(EditorComponent* editor) rwInput.SetPos(XMFLOAT2(x, y += step)); rwInput.SetSize(XMFLOAT2(siz, hei)); rwInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->rotation_local.w = args.fValue; @@ -271,7 +272,7 @@ void TransformWindow::Create(EditorComponent* editor) sxInput.SetPos(XMFLOAT2(x, y += step)); sxInput.SetSize(XMFLOAT2(siz, hei)); sxInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->scale_local.x = args.fValue; @@ -286,7 +287,7 @@ void TransformWindow::Create(EditorComponent* editor) syInput.SetPos(XMFLOAT2(x, y += step)); syInput.SetSize(XMFLOAT2(siz, hei)); syInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->scale_local.y = args.fValue; @@ -301,7 +302,7 @@ void TransformWindow::Create(EditorComponent* editor) szInput.SetPos(XMFLOAT2(x, y += step)); szInput.SetSize(XMFLOAT2(siz, hei)); szInput.OnInputAccepted([&](wi::gui::EventArgs args) { - TransformComponent* transform = wi::scene::GetScene().transforms.GetComponent(entity); + TransformComponent* transform = editor->GetCurrentScene().transforms.GetComponent(entity); if (transform != nullptr) { transform->scale_local.z = args.fValue; @@ -357,7 +358,7 @@ void TransformWindow::SetEntity(Entity entity) { this->entity = entity; - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); const TransformComponent* transform = scene.transforms.GetComponent(entity); if (transform != nullptr) diff --git a/Editor/TransformWindow.h b/Editor/TransformWindow.h index bf4cc7054..86876bd64 100644 --- a/Editor/TransformWindow.h +++ b/Editor/TransformWindow.h @@ -8,6 +8,7 @@ class TransformWindow : public wi::gui::Window public: void Create(EditorComponent* editor); + EditorComponent* editor = nullptr; wi::ecs::Entity entity; void SetEntity(wi::ecs::Entity entity); diff --git a/Editor/Translator.cpp b/Editor/Translator.cpp index 79b9ed943..cdd091014 100644 --- a/Editor/Translator.cpp +++ b/Editor/Translator.cpp @@ -122,7 +122,7 @@ void Translator::Update(const CameraComponent& camera, const wi::Canvas& canvas) // Non recursive selection will be computed to not apply recursive operations two times // A recursive operation is for example translating a parent transform // An other recursive operation is serializing selected parent entities - Scene& scene = wi::scene::GetScene(); + Scene& scene = *this->scene; selectedEntitiesLookup.clear(); for (auto& x : selected) { @@ -504,7 +504,7 @@ void Translator::Draw(const CameraComponent& camera, CommandList cmd) const Translator_Internal::LoadShaders(); } - Scene& scene = wi::scene::GetScene(); + Scene& scene = *this->scene; GraphicsDevice* device = wi::graphics::GetDevice(); @@ -1055,7 +1055,7 @@ void Translator::Draw(const CameraComponent& camera, CommandList cmd) const void Translator::PreTranslate() { - Scene& scene = wi::scene::GetScene(); + Scene& scene = *this->scene; if (!dragging) { @@ -1101,7 +1101,7 @@ void Translator::PreTranslate() } void Translator::PostTranslate() { - Scene& scene = wi::scene::GetScene(); + Scene& scene = *this->scene; int i = 0; for (auto& x : selectedEntitiesNonRecursive) diff --git a/Editor/Translator.h b/Editor/Translator.h index 6eb52c91e..f3b6f3ea4 100644 --- a/Editor/Translator.h +++ b/Editor/Translator.h @@ -24,6 +24,7 @@ public: // Apply translator to selection void PostTranslate(); + wi::scene::Scene* scene = nullptr; wi::scene::TransformComponent transform; wi::vector selected; // all the selected picks wi::unordered_set selectedEntitiesLookup; // fast lookup for selected entities diff --git a/Editor/WeatherWindow.cpp b/Editor/WeatherWindow.cpp index 667c1b37f..94a149851 100644 --- a/Editor/WeatherWindow.cpp +++ b/Editor/WeatherWindow.cpp @@ -6,8 +6,9 @@ using namespace wi::ecs; using namespace wi::scene; using namespace wi::graphics; -void WeatherWindow::Create(EditorComponent* editor) +void WeatherWindow::Create(EditorComponent* _editor) { + editor = _editor; wi::gui::Window::Create("Weather Window"); SetSize(XMFLOAT2(660, 300)); @@ -277,7 +278,7 @@ void WeatherWindow::Create(EditorComponent* editor) weather.SetOceanEnabled(args.bValue); if (!weather.IsOceanEnabled()) { - GetScene().ocean = {}; + editor->GetCurrentScene().ocean = {}; } }); AddWidget(&ocean_enabledCheckBox); @@ -286,31 +287,31 @@ void WeatherWindow::Create(EditorComponent* editor) ocean_patchSizeSlider.Create(1, 1000, 1000, 100000, "Patch size: "); ocean_patchSizeSlider.SetSize(XMFLOAT2(100, hei)); ocean_patchSizeSlider.SetPos(XMFLOAT2(x, y += step)); - ocean_patchSizeSlider.SetValue(wi::scene::GetScene().weather.oceanParameters.patch_length); + ocean_patchSizeSlider.SetValue(editor->GetCurrentScene().weather.oceanParameters.patch_length); ocean_patchSizeSlider.SetTooltip("Adjust water tiling patch size"); ocean_patchSizeSlider.OnSlide([&](wi::gui::EventArgs args) { auto& weather = GetWeather(); weather.oceanParameters.patch_length = args.fValue; - GetScene().ocean = {}; + editor->GetCurrentScene().ocean = {}; }); AddWidget(&ocean_patchSizeSlider); ocean_waveAmplitudeSlider.Create(0, 1000, 1000, 100000, "Wave amplitude: "); ocean_waveAmplitudeSlider.SetSize(XMFLOAT2(100, hei)); ocean_waveAmplitudeSlider.SetPos(XMFLOAT2(x, y += step)); - ocean_waveAmplitudeSlider.SetValue(wi::scene::GetScene().weather.oceanParameters.wave_amplitude); + ocean_waveAmplitudeSlider.SetValue(editor->GetCurrentScene().weather.oceanParameters.wave_amplitude); ocean_waveAmplitudeSlider.SetTooltip("Adjust wave size"); ocean_waveAmplitudeSlider.OnSlide([&](wi::gui::EventArgs args) { auto& weather = GetWeather(); weather.oceanParameters.wave_amplitude = args.fValue; - GetScene().ocean = {}; + editor->GetCurrentScene().ocean = {}; }); AddWidget(&ocean_waveAmplitudeSlider); ocean_choppyScaleSlider.Create(0, 10, 1000, 100000, "Choppiness: "); ocean_choppyScaleSlider.SetSize(XMFLOAT2(100, hei)); ocean_choppyScaleSlider.SetPos(XMFLOAT2(x, y += step)); - ocean_choppyScaleSlider.SetValue(wi::scene::GetScene().weather.oceanParameters.choppy_scale); + ocean_choppyScaleSlider.SetValue(editor->GetCurrentScene().weather.oceanParameters.choppy_scale); ocean_choppyScaleSlider.SetTooltip("Adjust wave choppiness"); ocean_choppyScaleSlider.OnSlide([&](wi::gui::EventArgs args) { auto& weather = GetWeather(); @@ -321,19 +322,19 @@ void WeatherWindow::Create(EditorComponent* editor) ocean_windDependencySlider.Create(0, 1, 1000, 100000, "Wind dependency: "); ocean_windDependencySlider.SetSize(XMFLOAT2(100, hei)); ocean_windDependencySlider.SetPos(XMFLOAT2(x, y += step)); - ocean_windDependencySlider.SetValue(wi::scene::GetScene().weather.oceanParameters.wind_dependency); + ocean_windDependencySlider.SetValue(editor->GetCurrentScene().weather.oceanParameters.wind_dependency); ocean_windDependencySlider.SetTooltip("Adjust wind contribution"); ocean_windDependencySlider.OnSlide([&](wi::gui::EventArgs args) { auto& weather = GetWeather(); weather.oceanParameters.wind_dependency = args.fValue; - GetScene().ocean = {}; + editor->GetCurrentScene().ocean = {}; }); AddWidget(&ocean_windDependencySlider); ocean_timeScaleSlider.Create(0, 4, 1000, 100000, "Time scale: "); ocean_timeScaleSlider.SetSize(XMFLOAT2(100, hei)); ocean_timeScaleSlider.SetPos(XMFLOAT2(x, y += step)); - ocean_timeScaleSlider.SetValue(wi::scene::GetScene().weather.oceanParameters.time_scale); + ocean_timeScaleSlider.SetValue(editor->GetCurrentScene().weather.oceanParameters.time_scale); ocean_timeScaleSlider.SetTooltip("Adjust simulation speed"); ocean_timeScaleSlider.OnSlide([&](wi::gui::EventArgs args) { auto& weather = GetWeather(); @@ -382,7 +383,7 @@ void WeatherWindow::Create(EditorComponent* editor) ocean_resetButton.OnClick([=](wi::gui::EventArgs args) { auto& weather = GetWeather(); weather.oceanParameters = wi::Ocean::OceanParameters(); - GetScene().ocean = {}; + editor->GetCurrentScene().ocean = {}; }); AddWidget(&ocean_resetButton); @@ -477,7 +478,7 @@ void WeatherWindow::Create(EditorComponent* editor) preset0Button.SetPos(XMFLOAT2(x, y += step)); preset0Button.OnClick([=](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); scene.weathers.Clear(); scene.weather = WeatherComponent(); @@ -594,7 +595,7 @@ void WeatherWindow::Create(EditorComponent* editor) eliminateCoarseCascadesButton.SetPos(XMFLOAT2(x, y += step * 2)); eliminateCoarseCascadesButton.OnClick([=](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); for (size_t i = 0; i < scene.objects.GetCount(); ++i) { scene.objects[i].cascadeMask = 1; @@ -610,7 +611,7 @@ void WeatherWindow::Create(EditorComponent* editor) ktxConvButton.SetPos(XMFLOAT2(x, y += step)); ktxConvButton.OnClick([=](wi::gui::EventArgs args) { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); wi::unordered_map conv; for (uint32_t i = 0; i < scene.materials.GetCount(); ++i) @@ -664,7 +665,7 @@ void WeatherWindow::Create(EditorComponent* editor) void WeatherWindow::Update() { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); if (scene.weathers.GetCount() > 0) { auto& weather = scene.weathers[0]; @@ -739,7 +740,7 @@ void WeatherWindow::Update() WeatherComponent& WeatherWindow::GetWeather() const { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); if (scene.weathers.GetCount() == 0) { scene.weathers.Create(CreateEntity()); @@ -749,7 +750,7 @@ WeatherComponent& WeatherWindow::GetWeather() const void WeatherWindow::InvalidateProbes() const { - Scene& scene = wi::scene::GetScene(); + Scene& scene = editor->GetCurrentScene(); // Also, we invalidate all environment probes to reflect the sky changes. for (size_t i = 0; i < scene.probes.GetCount(); ++i) diff --git a/Editor/WeatherWindow.h b/Editor/WeatherWindow.h index 33aa849ff..eaea29b01 100644 --- a/Editor/WeatherWindow.h +++ b/Editor/WeatherWindow.h @@ -11,6 +11,8 @@ public: void Update(); + EditorComponent* editor = nullptr; + wi::scene::WeatherComponent& GetWeather() const; void InvalidateProbes() const; diff --git a/Editor/images/arealight.dds b/Editor/images/arealight.dds deleted file mode 100644 index d115f72567255f547551ff4dc310111553c59324..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 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 MWgVq@1-q#K0Y}gGg#Z8m diff --git a/Editor/images/directional_light.dds b/Editor/images/directional_light.dds index 95e043615513f4cbc48121ef6b1aab0513d1d088..b2e5d5b8e0b327865528c9f76caea028e5a25a48 100644 GIT binary patch literal 65684 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 literal 65664 zcmeHw3tSY}z5nbkEW5z6D-Qwj0kVK7D5!u>c6~%$Ul3sd6V&w)qlqYLlvtx|wN0De zdIbzIDH^S|X&RFzF;PowWZU$n<>s~_rr2D`729adTYR7al=J`oW|^5YYkHBWY5TvK zPa^!7+1Z(M&iD8Go!@ivnpNuiurSpNh&Uf46gSJVuM!&=3IQzsJg?5`f+BEs|8vWstro*Nt>AcX*^WN_B z^#1lo{gz4JAE>R}P&VwoK#2|rZ+M0uj{)Y|jXM*S@#t-J|D?p)WRgtv3Q%j{tX(!gFXlsaiJThWxH}Uu%o2cf8W2~L;o{UlOKQyWpcvtY>H%N{DV82h| z$L!tSDt(_uuO0X6u&-}C?(}$nbAM@OyMF$I`3>aV61#qhET4k3t2dMlJu9#GXk%^6 z&SCe-^pG&DcMktq{n}Lc9~E1^sL86x{}GXp|BDs*Z#AjqpMO}G$9VQ}SsyTHu1~TZ z7tvF!7Fng4xjXgzB> z@?hlK8wJ7XbVjgjM^$0(SmD&weHLeePGF;$?pfTNv4rFWZbA5OYtF@_Yg=u{u1+vG zodx+xy*?SyzDU7)Fa0O4WbfVDCOwZvAMP>kYLNA<+x`CE+)otdj@mEF|3Q}84Qq!f{9xn2`aR8c8)Gi*qV?Vw z!Lril3R*!pc6E`(h@UKb_LlL2dGDt43k?OicK5x*y!!t>?A3se`4jpA|BDp)mk>;} zqW@ZSp!aQWl%9v=pC*P^Ziv znB%ePpaHqU(^q4K43kDH__MB~>l=LNtLYDKNqYET(x&r*P+Xj!KeYEg9WU?00*Ka) zk)BVZ-`!xue-iz5uaE!h-$(03rx)zf_-phas-BVcS&d$Zbu_(sSUFGh2^FoWnJmjU z+>iFV4&{36cV9AjB>GN|x0gSa$m?IDZwO4c=w!SH^lk{`@t+a3Au=yZk%#+6wp9

eLf@)BK>PbioW2vR+Ynti?1GHv_+T~ur^c!A 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 A-v9sr diff --git a/Editor/images/pointlight.dds b/Editor/images/pointlight.dds index b273e6c37ddee69120a8020506c2e80b8682e782..940a11a5f414625ef411df1869be6545d43b2f3b 100644 GIT binary patch literal 65684 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 J{%$A!{y%dQoz4IN literal 65664 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 Me(2(d|0+NHe{H#4f&c&j literal 65664 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 0) diff --git a/WickedEngine/wiGUI.h b/WickedEngine/wiGUI.h index 5203d58ac..d53de821a 100644 --- a/WickedEngine/wiGUI.h +++ b/WickedEngine/wiGUI.h @@ -336,6 +336,8 @@ namespace wi::gui void SetSelectedByUserdata(uint64_t userdata); void SetSelectedByUserdataWithoutCallback(uint64_t userdata); // SetSelectedByUserdata() but the OnSelect callback will not be executed int GetSelected() const; + void SetItemText(int index, const std::string& text); + void SetItemUserdata(int index, uint64_t userdata); std::string GetItemText(int index) const; uint64_t GetItemUserData(int index) const; size_t GetItemCount() const { return items.size(); } diff --git a/WickedEngine/wiPhysics.h b/WickedEngine/wiPhysics.h index 1a5f71c04..7602a0883 100644 --- a/WickedEngine/wiPhysics.h +++ b/WickedEngine/wiPhysics.h @@ -37,30 +37,30 @@ namespace wi::physics // Apply force at body center void ApplyForce( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& force ); // Apply force at body local position void ApplyForceAt( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& force, const XMFLOAT3& at ); // Apply impulse at body center void ApplyImpulse( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& impulse ); // Apply impulse at body local position void ApplyImpulseAt( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& impulse, const XMFLOAT3& at ); void ApplyTorque( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& torque ); } diff --git a/WickedEngine/wiPhysics_Bullet.cpp b/WickedEngine/wiPhysics_Bullet.cpp index dab1f1c73..b6bc9ffbc 100644 --- a/WickedEngine/wiPhysics_Bullet.cpp +++ b/WickedEngine/wiPhysics_Bullet.cpp @@ -20,79 +20,141 @@ using namespace wi::scene; namespace wi::physics { - bool ENABLED = true; - bool SIMULATION_ENABLED = true; - bool DEBUGDRAW_ENABLED = false; - int ACCURACY = 10; - std::mutex physicsLock; - - btVector3 gravity(0, -10, 0); - int softbodyIterationCount = 5; - btSoftBodyRigidBodyCollisionConfiguration collisionConfiguration; - btDbvtBroadphase overlappingPairCache; - btSequentialImpulseConstraintSolver solver; - btCollisionDispatcher dispatcher(&collisionConfiguration); - btSoftRigidDynamicsWorld dynamicsWorld(&dispatcher, &overlappingPairCache, &solver, &collisionConfiguration); - - class DebugDraw : public btIDebugDraw + namespace bullet { - void drawLine(const btVector3& from, const btVector3& to, const btVector3& color) override + bool ENABLED = true; + bool SIMULATION_ENABLED = true; + bool DEBUGDRAW_ENABLED = false; + int ACCURACY = 10; + int softbodyIterationCount = 5; + std::mutex physicsLock; + + class DebugDraw : public btIDebugDraw { - wi::renderer::RenderableLine line; - line.start = XMFLOAT3(from.x(), from.y(), from.z()); - line.end = XMFLOAT3(to.x(), to.y(), to.z()); - line.color_start = line.color_end = XMFLOAT4(color.x(), color.y(), color.z(), 1.0f); - wi::renderer::DrawLine(line); - } - void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color) override + void drawLine(const btVector3& from, const btVector3& to, const btVector3& color) override + { + wi::renderer::RenderableLine line; + line.start = XMFLOAT3(from.x(), from.y(), from.z()); + line.end = XMFLOAT3(to.x(), to.y(), to.z()); + line.color_start = line.color_end = XMFLOAT4(color.x(), color.y(), color.z(), 1.0f); + wi::renderer::DrawLine(line); + } + void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color) override + { + } + void reportErrorWarning(const char* warningString) override + { + wi::backlog::post(warningString); + } + void draw3dText(const btVector3& location, const char* textString) override + { + wi::renderer::DebugTextParams params; + params.position.x = location.x(); + params.position.y = location.y(); + params.position.z = location.z(); + params.scaling = 0.6f; + params.flags |= wi::renderer::DebugTextParams::CAMERA_FACING; + params.flags |= wi::renderer::DebugTextParams::CAMERA_SCALING; + wi::renderer::DrawDebugText(textString, params); + } + void setDebugMode(int debugMode) override + { + } + int getDebugMode() const override + { + int retval = 0; + retval |= DBG_DrawWireframe; + retval |= DBG_DrawText; + return retval; + } + }; + DebugDraw debugDraw; + + struct PhysicsScene { - } - void reportErrorWarning(const char* warningString) override + btVector3 gravity = btVector3(0, -10, 0); + btSoftBodyRigidBodyCollisionConfiguration collisionConfiguration; + btDbvtBroadphase overlappingPairCache; + btSequentialImpulseConstraintSolver solver; + btCollisionDispatcher dispatcher = btCollisionDispatcher(&collisionConfiguration); + btSoftRigidDynamicsWorld dynamicsWorld = btSoftRigidDynamicsWorld(&dispatcher, &overlappingPairCache, &solver, &collisionConfiguration); + }; + PhysicsScene& GetPhysicsScene(Scene& scene) { - wi::backlog::post(warningString); + if (scene.physics_scene == nullptr) + { + auto physics_scene = std::make_shared(); + + physics_scene->dynamicsWorld.getSolverInfo().m_solverMode |= SOLVER_RANDMIZE_ORDER; + physics_scene->dynamicsWorld.getDispatchInfo().m_enableSatConvex = true; + physics_scene->dynamicsWorld.getSolverInfo().m_splitImpulse = true; + physics_scene->dynamicsWorld.setGravity(physics_scene->gravity); + physics_scene->dynamicsWorld.setDebugDrawer(&debugDraw); + + btSoftBodyWorldInfo& softWorldInfo = physics_scene->dynamicsWorld.getWorldInfo(); + softWorldInfo.air_density = btScalar(1.2f); + softWorldInfo.water_density = 0; + softWorldInfo.water_offset = 0; + softWorldInfo.water_normal = btVector3(0, 0, 0); + softWorldInfo.m_gravity.setValue(physics_scene->gravity.x(), physics_scene->gravity.y(), physics_scene->gravity.z()); + softWorldInfo.m_sparsesdf.Initialize(); + + scene.physics_scene = physics_scene; + } + return *(PhysicsScene*)scene.physics_scene.get(); } - void draw3dText(const btVector3& location, const char* textString) override + + struct RigidBody { - wi::renderer::DebugTextParams params; - params.position.x = location.x(); - params.position.y = location.y(); - params.position.z = location.z(); - params.scaling = 0.6f; - params.flags |= wi::renderer::DebugTextParams::CAMERA_FACING; - params.flags |= wi::renderer::DebugTextParams::CAMERA_SCALING; - wi::renderer::DrawDebugText(textString, params); - } - void setDebugMode(int debugMode) override + std::shared_ptr physics_scene; + std::unique_ptr shape; + std::unique_ptr rigidBody; + btDefaultMotionState motionState; + btTriangleIndexVertexArray triangles; + ~RigidBody() + { + if (physics_scene == nullptr) + return; + btSoftRigidDynamicsWorld& dynamicsWorld = ((PhysicsScene*)physics_scene.get())->dynamicsWorld; + dynamicsWorld.removeRigidBody(rigidBody.get()); + } + }; + struct SoftBody { - } - int getDebugMode() const override + std::shared_ptr physics_scene; + std::unique_ptr softBody; + ~SoftBody() + { + if (physics_scene == nullptr) + return; + btSoftRigidDynamicsWorld& dynamicsWorld = ((PhysicsScene*)physics_scene.get())->dynamicsWorld; + dynamicsWorld.removeSoftBody(softBody.get()); + } + }; + + RigidBody& GetRigidBody(wi::scene::RigidBodyPhysicsComponent& physicscomponent) { - int retval = 0; - retval |= DBG_DrawWireframe; - retval |= DBG_DrawText; - return retval; + if (physicscomponent.physicsobject == nullptr) + { + physicscomponent.physicsobject = std::make_shared(); + } + return *(RigidBody*)physicscomponent.physicsobject.get(); } - }; - DebugDraw debugDraw; + SoftBody& GetSoftBody(wi::scene::SoftBodyPhysicsComponent& physicscomponent) + { + if (physicscomponent.physicsobject == nullptr) + { + physicscomponent.physicsobject = std::make_shared(); + } + return *(SoftBody*)physicscomponent.physicsobject.get(); + } + } + using namespace bullet; void Initialize() { wi::Timer timer; - dynamicsWorld.getSolverInfo().m_solverMode |= SOLVER_RANDMIZE_ORDER; - dynamicsWorld.getDispatchInfo().m_enableSatConvex = true; - dynamicsWorld.getSolverInfo().m_splitImpulse = true; - dynamicsWorld.setGravity(gravity); - dynamicsWorld.setDebugDrawer(&debugDraw); - - btSoftBodyWorldInfo& softWorldInfo = dynamicsWorld.getWorldInfo(); - softWorldInfo.air_density = btScalar(1.2f); - softWorldInfo.water_density = 0; - softWorldInfo.water_offset = 0; - softWorldInfo.water_normal = btVector3(0, 0, 0); - softWorldInfo.m_gravity.setValue(gravity.x(), gravity.y(), gravity.z()); - softWorldInfo.m_sparsesdf.Initialize(); - wi::backlog::post("wi::physics Initialized [Bullet] (" + std::to_string((int)std::round(timer.elapsed())) + " ms)"); } @@ -108,38 +170,39 @@ namespace wi::physics int GetAccuracy() { return ACCURACY; } void SetAccuracy(int value) { ACCURACY = value; } - void AddRigidBody(Entity entity, wi::scene::RigidBodyPhysicsComponent& physicscomponent, const wi::scene::TransformComponent& transform, const wi::scene::MeshComponent* mesh) + void AddRigidBody( + wi::scene::Scene& scene, + Entity entity, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, + const wi::scene::TransformComponent& transform, + const wi::scene::MeshComponent* mesh + ) { - btCollisionShape* shape = nullptr; + RigidBody& physicsobject = GetRigidBody(physicscomponent); switch (physicscomponent.shape) { case RigidBodyPhysicsComponent::CollisionShape::BOX: - { - shape = new btBoxShape(btVector3(physicscomponent.box.halfextents.x, physicscomponent.box.halfextents.y, physicscomponent.box.halfextents.z)); - } - break; - + physicsobject.shape = std::make_unique(btVector3(physicscomponent.box.halfextents.x, physicscomponent.box.halfextents.y, physicscomponent.box.halfextents.z)); + break; case RigidBodyPhysicsComponent::CollisionShape::SPHERE: - { - shape = new btSphereShape(btScalar(physicscomponent.sphere.radius)); - } - break; - + physicsobject.shape = std::make_unique(btScalar(physicscomponent.sphere.radius)); + break; case RigidBodyPhysicsComponent::CollisionShape::CAPSULE: - shape = new btCapsuleShape(btScalar(physicscomponent.capsule.radius), btScalar(physicscomponent.capsule.height)); + physicsobject.shape = std::make_unique(btScalar(physicscomponent.capsule.radius), btScalar(physicscomponent.capsule.height)); break; case RigidBodyPhysicsComponent::CollisionShape::CONVEX_HULL: if(mesh != nullptr) { - shape = new btConvexHullShape(); + physicsobject.shape = std::make_unique(); + btConvexHullShape* convexHull = (btConvexHullShape*)physicsobject.shape.get(); for (auto& pos : mesh->vertex_positions) { - ((btConvexHullShape*)shape)->addPoint(btVector3(pos.x, pos.y, pos.z)); + convexHull->addPoint(btVector3(pos.x, pos.y, pos.z)); } btVector3 S(transform.scale_local.x, transform.scale_local.y, transform.scale_local.z); - shape->setLocalScaling(S); + physicsobject.shape->setLocalScaling(S); } else { @@ -166,20 +229,19 @@ namespace wi::physics totalTriangles += int(subset.indexCount / 3); } - btTriangleIndexVertexArray* indexVertexArrays = new btTriangleIndexVertexArray( + physicsobject.triangles = btTriangleIndexVertexArray( totalTriangles, indices, - 3 * sizeof(int), + 3 * int(sizeof(int)), int(mesh->vertex_positions.size()), (btScalar*)mesh->vertex_positions.data(), - sizeof(XMFLOAT3) + int(sizeof(XMFLOAT3)) ); bool useQuantizedAabbCompression = true; - shape = new btBvhTriangleMeshShape(indexVertexArrays, useQuantizedAabbCompression); + physicsobject.shape = std::make_unique(&physicsobject.triangles, useQuantizedAabbCompression); btVector3 S(transform.scale_local.x, transform.scale_local.y, transform.scale_local.z); - shape->setLocalScaling(S); - shape->setUserPointer(indexVertexArrays); + physicsobject.shape->setLocalScaling(S); } else { @@ -189,7 +251,12 @@ namespace wi::physics break; } - if (shape != nullptr) + if (physicsobject.shape == nullptr) + { + physicscomponent.physicsobject = nullptr; + return; + } + else { // Use default margin for now //shape->setMargin(btScalar(0.01)); @@ -206,7 +273,7 @@ namespace wi::physics btVector3 localInertia(0, 0, 0); if (isDynamic) { - shape->calculateLocalInertia(mass, localInertia); + physicsobject.shape->calculateLocalInertia(mass, localInertia); } else { @@ -218,36 +285,42 @@ namespace wi::physics shapeTransform.setIdentity(); shapeTransform.setOrigin(btVector3(transform.translation_local.x, transform.translation_local.y, transform.translation_local.z)); shapeTransform.setRotation(btQuaternion(transform.rotation_local.x, transform.rotation_local.y, transform.rotation_local.z, transform.rotation_local.w)); - btDefaultMotionState* myMotionState = new btDefaultMotionState(shapeTransform); + physicsobject.motionState = btDefaultMotionState(shapeTransform); - btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, myMotionState, shape, localInertia); + btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, &physicsobject.motionState, physicsobject.shape.get(), localInertia); //rbInfo.m_friction = physicscomponent.friction; //rbInfo.m_restitution = physicscomponent.restitution; //rbInfo.m_linearDamping = physicscomponent.damping; //rbInfo.m_angularDamping = physicscomponent.damping; - btRigidBody* rigidbody = new btRigidBody(rbInfo); - rigidbody->setUserIndex(entity); + physicsobject.rigidBody = std::make_unique(rbInfo); + physicsobject.rigidBody->setUserIndex(entity); if (physicscomponent.IsKinematic()) { - rigidbody->setCollisionFlags(rigidbody->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); + physicsobject.rigidBody->setCollisionFlags(physicsobject.rigidBody->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); } if (physicscomponent.IsDisableDeactivation()) { - rigidbody->setActivationState(DISABLE_DEACTIVATION); + physicsobject.rigidBody->setActivationState(DISABLE_DEACTIVATION); } if (physicscomponent.shape == RigidBodyPhysicsComponent::CollisionShape::TRIANGLE_MESH) { - rigidbody->setActivationState(DISABLE_DEACTIVATION); + physicsobject.rigidBody->setActivationState(DISABLE_DEACTIVATION); } - dynamicsWorld.addRigidBody(rigidbody); - physicscomponent.physicsobject = rigidbody; + physicsobject.physics_scene = scene.physics_scene; + GetPhysicsScene(scene).dynamicsWorld.addRigidBody(physicsobject.rigidBody.get()); } } - void AddSoftBody(Entity entity, wi::scene::SoftBodyPhysicsComponent& physicscomponent, const wi::scene::MeshComponent& mesh) + void AddSoftBody( + wi::scene::Scene& scene, + Entity entity, + wi::scene::SoftBodyPhysicsComponent& physicscomponent, + const wi::scene::MeshComponent& mesh + ) { + SoftBody& physicsobject = GetSoftBody(physicscomponent); physicscomponent.CreateFromMesh(mesh); XMMATRIX worldMatrix = XMLoadFloat4x4(&physicscomponent.worldMatrix); @@ -283,14 +356,67 @@ namespace wi::physics } } - // This function uses new to allocate btSoftbody internally: - btSoftBody* softbody = btSoftBodyHelpers::CreateFromTriMesh( - dynamicsWorld.getWorldInfo(), - btVerts.data(), - btInd.data(), - int(btInd.size() / 3), - false - ); + //// This function uses new to allocate btSoftbody internally: + //btSoftBody* softbody = btSoftBodyHelpers::CreateFromTriMesh( + // GetPhysicsScene(scene).dynamicsWorld.getWorldInfo(), + // btVerts.data(), + // btInd.data(), + // int(btInd.size() / 3), + // false + //); + + // Modified version of btSoftBodyHelpers::CreateFromTriMesh: + // This version does not allocate btSoftbody with new + btSoftBody* softbody = nullptr; + { + btSoftBodyWorldInfo& worldInfo = GetPhysicsScene(scene).dynamicsWorld.getWorldInfo(); + const btScalar* vertices = btVerts.data(); + const int* triangles = btInd.data(); + int ntriangles = int(btInd.size() / 3); + bool randomizeConstraints = false; + + int maxidx = 0; + int i, j, ni; + + for (i = 0, ni = ntriangles * 3; i < ni; ++i) + { + maxidx = btMax(triangles[i], maxidx); + } + ++maxidx; + btAlignedObjectArray chks; + btAlignedObjectArray vtx; + chks.resize(maxidx * maxidx, false); + vtx.resize(maxidx); + for (i = 0, j = 0, ni = maxidx * 3; i < ni; ++j, i += 3) + { + vtx[j] = btVector3(vertices[i], vertices[i + 1], vertices[i + 2]); + } + //btSoftBody* psb = new btSoftBody(&worldInfo, vtx.size(), &vtx[0], 0); + physicsobject.softBody = std::make_unique(&worldInfo, vtx.size(), &vtx[0], nullptr); + softbody = physicsobject.softBody.get(); + btSoftBody* psb = softbody; + for (i = 0, ni = ntriangles * 3; i < ni; i += 3) + { + const int idx[] = { triangles[i],triangles[i + 1],triangles[i + 2] }; +#define IDX(_x_,_y_) ((_y_)*maxidx+(_x_)) + for (int j = 2, k = 0; k < 3; j = k++) + { + if (!chks[IDX(idx[j], idx[k])]) + { + chks[IDX(idx[j], idx[k])] = true; + chks[IDX(idx[k], idx[j])] = true; + psb->appendLink(idx[j], idx[k]); + } + } +#undef IDX + psb->appendFace(idx[0], idx[1], idx[2]); + } + + if (randomizeConstraints) + { + psb->randomizeConstraints(); + } + } if (softbody) { @@ -342,8 +468,8 @@ namespace wi::physics softbody->setPose(true, true); - dynamicsWorld.addSoftBody(softbody); - physicscomponent.physicsobject = softbody; + physicsobject.physics_scene = scene.physics_scene; + GetPhysicsScene(scene).dynamicsWorld.addSoftBody(softbody); } } @@ -358,6 +484,8 @@ namespace wi::physics auto range = wi::profiler::BeginRangeCPU("Physics"); + btSoftRigidDynamicsWorld& dynamicsWorld = GetPhysicsScene(scene).dynamicsWorld; + btVector3 wind = btVector3(scene.weather.windDirection.x, scene.weather.windDirection.y, scene.weather.windDirection.z); // System will register rigidbodies to objects, and update physics engine state for kinematics: @@ -376,13 +504,13 @@ namespace wi::physics mesh = scene.meshes.GetComponent(object->meshID); } physicsLock.lock(); - AddRigidBody(entity, physicscomponent, transform, mesh); + AddRigidBody(scene, entity, physicscomponent, transform, mesh); physicsLock.unlock(); } if (physicscomponent.physicsobject != nullptr) { - btRigidBody* rigidbody = (btRigidBody*)physicscomponent.physicsobject; + btRigidBody* rigidbody = GetRigidBody(physicscomponent).rigidBody.get(); int activationState = rigidbody->getActivationState(); if (physicscomponent.IsDisableDeactivation()) @@ -444,24 +572,18 @@ namespace wi::physics if (physicscomponent._flags & SoftBodyPhysicsComponent::FORCE_RESET) { physicscomponent._flags &= ~SoftBodyPhysicsComponent::FORCE_RESET; - if (physicscomponent.physicsobject != nullptr) - { - btSoftBody* softbody = (btSoftBody*)physicscomponent.physicsobject; - delete softbody; - dynamicsWorld.removeSoftBody(softbody); - physicscomponent.physicsobject = nullptr; - } + physicscomponent.physicsobject = nullptr; } if (physicscomponent._flags & SoftBodyPhysicsComponent::SAFE_TO_REGISTER && physicscomponent.physicsobject == nullptr) { physicsLock.lock(); - AddSoftBody(entity, physicscomponent, mesh); + AddSoftBody(scene, entity, physicscomponent, mesh); physicsLock.unlock(); } if (physicscomponent.physicsobject != nullptr) { - btSoftBody* softbody = (btSoftBody*)physicscomponent.physicsobject; + btSoftBody* softbody = GetSoftBody(physicscomponent).softBody.get(); softbody->m_cfg.kDF = physicscomponent.friction; softbody->setWindVelocity(wind); @@ -508,19 +630,6 @@ namespace wi::physics if (rigidbody != nullptr) { RigidBodyPhysicsComponent* physicscomponent = scene.rigidbodies.GetComponent(entity); - if (physicscomponent == nullptr || physicscomponent->physicsobject != rigidbody) - { - btCollisionShape* shape = rigidbody->getCollisionShape(); - btTriangleIndexVertexArray* triangleinfo = (btTriangleIndexVertexArray*)shape->getUserPointer(); - delete triangleinfo; - delete shape; - btMotionState* motionstate = rigidbody->getMotionState(); - delete motionstate; - dynamicsWorld.removeRigidBody(rigidbody); - delete rigidbody; - i--; - continue; - } // Feedback non-kinematic objects to system: if (IsSimulationEnabled() && !physicscomponent->IsKinematic()) @@ -543,13 +652,6 @@ namespace wi::physics if (softbody != nullptr) { SoftBodyPhysicsComponent* physicscomponent = scene.softbodies.GetComponent(entity); - if (physicscomponent == nullptr || physicscomponent->physicsobject != softbody) - { - dynamicsWorld.removeSoftBody(softbody); - delete softbody; - i--; - continue; - } // If you need it, you can enable soft body node debug strings here: #if 0 @@ -685,62 +787,57 @@ namespace wi::physics void ApplyForce( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& force ) { if (physicscomponent.physicsobject != nullptr) { - btRigidBody* rigidbody = (btRigidBody*)physicscomponent.physicsobject; - rigidbody->applyCentralForce(btVector3(force.x, force.y, force.z)); + GetRigidBody(physicscomponent).rigidBody->applyCentralForce(btVector3(force.x, force.y, force.z)); } } void ApplyForceAt( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& force, const XMFLOAT3& at ) { if (physicscomponent.physicsobject != nullptr) { - btRigidBody* rigidbody = (btRigidBody*)physicscomponent.physicsobject; - rigidbody->applyForce(btVector3(force.x, force.y, force.z), btVector3(at.x, at.y, at.z)); + GetRigidBody(physicscomponent).rigidBody->applyForce(btVector3(force.x, force.y, force.z), btVector3(at.x, at.y, at.z)); } } void ApplyImpulse( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& impulse ) { if (physicscomponent.physicsobject != nullptr) { - btRigidBody* rigidbody = (btRigidBody*)physicscomponent.physicsobject; - rigidbody->applyCentralImpulse(btVector3(impulse.x, impulse.y, impulse.z)); + GetRigidBody(physicscomponent).rigidBody->applyCentralImpulse(btVector3(impulse.x, impulse.y, impulse.z)); } } void ApplyImpulseAt( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& impulse, const XMFLOAT3& at ) { if (physicscomponent.physicsobject != nullptr) { - btRigidBody* rigidbody = (btRigidBody*)physicscomponent.physicsobject; - rigidbody->applyImpulse(btVector3(impulse.x, impulse.y, impulse.z), btVector3(at.x, at.y, at.z)); + GetRigidBody(physicscomponent).rigidBody->applyImpulse(btVector3(impulse.x, impulse.y, impulse.z), btVector3(at.x, at.y, at.z)); } } void ApplyTorque( - const wi::scene::RigidBodyPhysicsComponent& physicscomponent, + wi::scene::RigidBodyPhysicsComponent& physicscomponent, const XMFLOAT3& torque ) { if (physicscomponent.physicsobject != nullptr) { - btRigidBody* rigidbody = (btRigidBody*)physicscomponent.physicsobject; - rigidbody->applyTorque(btVector3(torque.x, torque.y, torque.z)); + GetRigidBody(physicscomponent).rigidBody->applyTorque(btVector3(torque.x, torque.y, torque.z)); } } } diff --git a/WickedEngine/wiRenderPath3D_BindLua.h b/WickedEngine/wiRenderPath3D_BindLua.h index 9e18d70c3..fb6d7ea91 100644 --- a/WickedEngine/wiRenderPath3D_BindLua.h +++ b/WickedEngine/wiRenderPath3D_BindLua.h @@ -3,6 +3,7 @@ #include "wiLuna.h" #include "wiRenderPath3D.h" #include "wiRenderPath2D_BindLua.h" +#include "wiScene_BindLua.h" namespace wi::lua { @@ -23,6 +24,8 @@ namespace wi::lua } RenderPath3D_BindLua(lua_State* L) { + renderpath.scene = wi::lua::scene::GetGlobalScene(); + renderpath.camera = wi::lua::scene::GetGlobalCamera(); this->component = &renderpath; } diff --git a/WickedEngine/wiRenderer_BindLua.cpp b/WickedEngine/wiRenderer_BindLua.cpp index 0c51bd9a3..bb5a53e6f 100644 --- a/WickedEngine/wiRenderer_BindLua.cpp +++ b/WickedEngine/wiRenderer_BindLua.cpp @@ -360,7 +360,7 @@ namespace wi::lua::renderer { XMFLOAT3 pos; XMStoreFloat3(&pos, XMLoadFloat4(v)); - wi::scene::GetScene().PutWaterRipple(wi::lua::GetScriptPath() + name, pos); + GetGlobalScene()->PutWaterRipple(wi::lua::GetScriptPath() + name, pos); } else wi::lua::SError(L, "PutWaterRipple(String imagename, Vector position) argument is not a Vector!"); @@ -375,7 +375,7 @@ namespace wi::lua::renderer Scene_BindLua* scene = Luna::lightcheck(L, 1); if (scene == nullptr) { - wi::renderer::ClearWorld(wi::scene::GetScene()); + wi::renderer::ClearWorld(*GetGlobalScene()); } else { diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 1044ddb69..049173684 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -763,7 +763,7 @@ namespace wi::scene uint32_t mesh_lod = 0; // Non-serialized attributes: - void* physicsobject = nullptr; // You can set to null to recreate the physics object the next time phsyics system will be running. + std::shared_ptr physicsobject = nullptr; // You can set to null to recreate the physics object the next time phsyics system will be running. inline void SetDisableDeactivation(bool value) { if (value) { _flags |= DISABLE_DEACTIVATION; } else { _flags &= ~DISABLE_DEACTIVATION; } } inline void SetKinematic(bool value) { if (value) { _flags |= KINEMATIC; } else { _flags &= ~KINEMATIC; } } @@ -793,7 +793,7 @@ namespace wi::scene wi::vector weights; // weight per physics vertex controlling the mass. (0: disable weight (no physics, only animation), 1: default weight) // Non-serialized attributes: - void* physicsobject = nullptr; + std::shared_ptr physicsobject = nullptr; // You can set to null to recreate the physics object the next time phsyics system will be running. XMFLOAT4X4 worldMatrix = wi::math::IDENTITY_MATRIX; wi::vector vertex_positions_simulation; // graphics vertices after simulation (world space) wi::vectorvertex_tangents_tmp; @@ -1338,7 +1338,7 @@ namespace wi::scene }; uint32_t flags = EMPTY; - + std::shared_ptr physics_scene; wi::SpinLock locker; wi::primitive::AABB bounds; wi::vector parallel_bounds; diff --git a/WickedEngine/wiScene_BindLua.cpp b/WickedEngine/wiScene_BindLua.cpp index d74a3cc4d..426e806eb 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -12,6 +12,26 @@ using namespace wi::lua::primitive; namespace wi::lua::scene { +static wi::scene::Scene* globalscene = &wi::scene::GetScene(); +static wi::scene::CameraComponent* globalcam = &wi::scene::GetCamera(); + +void SetGlobalScene(wi::scene::Scene* scene) +{ + globalscene = scene; +} +void SetGlobalCamera(wi::scene::CameraComponent* camera) +{ + globalcam = camera; +} +wi::scene::Scene* GetGlobalScene() +{ + return globalscene; +} +wi::scene::CameraComponent* GetGlobalCamera() +{ + return globalcam; +} + int CreateEntity_BindLua(lua_State* L) { Entity entity = CreateEntity(); @@ -21,12 +41,12 @@ int CreateEntity_BindLua(lua_State* L) int GetCamera(lua_State* L) { - Luna::push(L, new CameraComponent_BindLua(&wi::scene::GetCamera())); + Luna::push(L, new CameraComponent_BindLua(GetGlobalCamera())); return 1; } int GetScene(lua_State* L) { - Luna::push(L, new Scene_BindLua(&wi::scene::GetScene())); + Luna::push(L, new Scene_BindLua(GetGlobalScene())); return 1; } int LoadModel(lua_State* L) @@ -83,7 +103,9 @@ int LoadModel(lua_State* L) wi::lua::SError(L, "LoadModel(string fileName, opt Matrix transform) argument is not a matrix!"); } } - Entity root = wi::scene::LoadModel(fileName, transform, true); + Scene scene; + Entity root = wi::scene::LoadModel(scene, fileName, transform, true); + GetGlobalScene()->Merge(scene); wi::lua::SSetLongLong(L, root); return 1; } @@ -104,7 +126,7 @@ int Pick(lua_State* L) { uint32_t renderTypeMask = wi::enums::RENDERTYPE_OPAQUE; uint32_t layerMask = 0xFFFFFFFF; - Scene* scene = &wi::scene::GetScene(); + Scene* scene = GetGlobalScene(); if (argc > 1) { renderTypeMask = (uint32_t)wi::lua::SGetInt(L, 2); @@ -154,7 +176,7 @@ int SceneIntersectSphere(lua_State* L) { uint32_t renderTypeMask = wi::enums::RENDERTYPE_OPAQUE; uint32_t layerMask = 0xFFFFFFFF; - Scene* scene = &wi::scene::GetScene(); + Scene* scene = GetGlobalScene(); if (argc > 1) { renderTypeMask = (uint32_t)wi::lua::SGetInt(L, 2); @@ -204,7 +226,7 @@ int SceneIntersectCapsule(lua_State* L) { uint32_t renderTypeMask = wi::enums::RENDERTYPE_OPAQUE; uint32_t layerMask = 0xFFFFFFFF; - Scene* scene = &wi::scene::GetScene(); + Scene* scene = GetGlobalScene(); if (argc > 1) { renderTypeMask = (uint32_t)wi::lua::SGetInt(L, 2); @@ -359,7 +381,6 @@ Luna::PropertyType Scene_BindLua::properties[] = { { NULL, NULL } }; - int Scene_BindLua::Update(lua_State* L) { int argc = wi::lua::SGetArgCount(L); diff --git a/WickedEngine/wiScene_BindLua.h b/WickedEngine/wiScene_BindLua.h index 28d8e45ae..12b2a4472 100644 --- a/WickedEngine/wiScene_BindLua.h +++ b/WickedEngine/wiScene_BindLua.h @@ -5,6 +5,15 @@ namespace wi::lua::scene { + // If the application doesn't use the global scene, but manages it manually, + // Then this can be used to override the global GetScene() in lua scripts with a custom scene + void SetGlobalScene(wi::scene::Scene* scene); + // If the application doesn't use the global camera, but manages it manually, + // Then this can be used to override the global GetCamera() in lua scripts with a custom camera + void SetGlobalCamera(wi::scene::CameraComponent* camera); + wi::scene::Scene* GetGlobalScene(); + wi::scene::CameraComponent* GetGlobalCamera(); + void Bind(); class Scene_BindLua diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 7b89c0218..1c08d6824 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 70; // minor bug fixes, alterations, refactors, updates - const int revision = 10; + const int revision = 11; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);