diff --git a/Content/Documentation/ScriptingAPI-Documentation.md b/Content/Documentation/ScriptingAPI-Documentation.md
index c6acc25c2..1e3174611 100644
--- a/Content/Documentation/ScriptingAPI-Documentation.md
+++ b/Content/Documentation/ScriptingAPI-Documentation.md
@@ -900,10 +900,15 @@ This is used to describe a drawable area
- GetLogicalHeight() : float -- height in dpi scaled units
## High Level Interface
-### MainComponent
-This is the main entry point and manages the lifetime of the application. Even though it is called a component, it is not part of the entity-component system
-- [outer]main : MainComponent
-- [void-constructor]MainComponent()
+This section must only be used from standalone lua scripts, and must not be used from a ScriptComponent.
+This is because ScriptComponent is always running inside scene.Update(), and paths can not be switched at that time safely.
+On the other hand, a standalone lua script can define its own update logic and render path and cahnge application behaviour.
+
+### Application
+This is the main entry point and manages the lifetime of the application.
+- [outer]application : Application
+- [deprecated][outer]main : Application
+- [void-constructor]Application()
- GetContent() : Resource? result
- GetActivePath() : RenderPath? result
- SetActivePath(RenderPath path, opt float fadeSeconds,fadeColorR,fadeColorG,fadeColorB)
diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt
index f669c7da2..7f2533df8 100644
--- a/Editor/CMakeLists.txt
+++ b/Editor/CMakeLists.txt
@@ -38,7 +38,7 @@ set (SOURCE_FILES
ArmatureWindow.cpp
OptionsWindow.cpp
ComponentsWindow.cpp
- TerrainGenerator.cpp
+ TerrainWindow.cpp
xatlas.cpp
)
diff --git a/Editor/ComponentsWindow.cpp b/Editor/ComponentsWindow.cpp
index 304d970bc..4820f2086 100644
--- a/Editor/ComponentsWindow.cpp
+++ b/Editor/ComponentsWindow.cpp
@@ -40,6 +40,7 @@ void ComponentsWindow::Create(EditorComponent* _editor)
cameraComponentWnd.Create(editor);
expressionWnd.Create(editor);
armatureWnd.Create(editor);
+ terrainWnd.Create(editor);
newComponentCombo.Create("Add: ");
@@ -310,6 +311,7 @@ void ComponentsWindow::Create(EditorComponent* _editor)
AddWidget(&cameraComponentWnd);
AddWidget(&expressionWnd);
AddWidget(&armatureWnd);
+ AddWidget(&terrainWnd);
materialWnd.SetVisible(false);
weatherWnd.SetVisible(false);
@@ -336,6 +338,7 @@ void ComponentsWindow::Create(EditorComponent* _editor)
cameraComponentWnd.SetVisible(false);
expressionWnd.SetVisible(false);
armatureWnd.SetVisible(false);
+ terrainWnd.SetVisible(false);
SetSize(editor->optionsWnd.GetSize());
}
@@ -703,4 +706,17 @@ void ComponentsWindow::ResizeLayout()
{
armatureWnd.SetVisible(false);
}
+
+ if (scene.terrains.Contains(terrainWnd.entity))
+ {
+ terrainWnd.SetVisible(true);
+ terrainWnd.SetPos(pos);
+ terrainWnd.SetSize(XMFLOAT2(width, terrainWnd.GetScale().y));
+ pos.y += terrainWnd.GetSize().y;
+ pos.y += padding;
+ }
+ else
+ {
+ terrainWnd.SetVisible(false);
+ }
}
diff --git a/Editor/ComponentsWindow.h b/Editor/ComponentsWindow.h
index fb689bb79..edc0ab487 100644
--- a/Editor/ComponentsWindow.h
+++ b/Editor/ComponentsWindow.h
@@ -25,6 +25,7 @@
#include "CameraComponentWindow.h"
#include "ExpressionWindow.h"
#include "ArmatureWindow.h"
+#include "TerrainWindow.h"
class EditorComponent;
@@ -63,4 +64,5 @@ public:
CameraComponentWindow cameraComponentWnd;
ExpressionWindow expressionWnd;
ArmatureWindow armatureWnd;
+ TerrainWindow terrainWnd;
};
diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp
index 309a8996e..e454a72ab 100644
--- a/Editor/Editor.cpp
+++ b/Editor/Editor.cpp
@@ -317,9 +317,7 @@ void EditorComponent::Load()
closeButton.SetColor(wi::Color(255, 200, 150, 255), wi::gui::WIDGETSTATE::FOCUS);
closeButton.OnClick([&](wi::gui::EventArgs args) {
- optionsWnd.terragen.Generation_Cancel();
- optionsWnd.terragen.terrainEntity = INVALID_ENTITY;
- optionsWnd.terragen.SetCollapsed(true);
+ componentsWnd.terrainWnd.terrain_preset = {};
translator.selected.clear();
wi::scene::Scene& scene = GetCurrentScene();
@@ -349,6 +347,7 @@ void EditorComponent::Load()
componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY);
componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY);
componentsWnd.armatureWnd.SetEntity(INVALID_ENTITY);
+ componentsWnd.terrainWnd.SetEntity(INVALID_ENTITY);
optionsWnd.RefreshEntityTree();
ResetHistory();
@@ -434,6 +433,12 @@ void EditorComponent::Load()
ss += "You can find a program configuration file at Editor/config.ini\n";
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 += "You can use some command line arguments (without any prefix):\n";
+ ss += "\t- Default to DirectX12 graphics device: dx12\n";
+ ss += "\t- Default to Vulkan graphics device: vulkan\n";
+ ss += "\t- Enable graphics device debug mode: debugdevice\n";
+ ss += "\t- Enable graphics device GPU-based validation: gpuvalidation\n";
+ ss += "\t- Make window always active, even when in background: alwaysactive\n";
ss += "\nFor questions, bug reports, feedback, requests, please open an issue at:\n";
ss += "https://github.com/turanszkij/WickedEngine/issues\n";
ss += "\n\n";
@@ -453,7 +458,6 @@ void EditorComponent::Load()
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) {
- optionsWnd.terragen.Generation_Cancel();
wi::platform::Exit();
});
GetGUI().AddWidget(&exitButton);
@@ -486,10 +490,6 @@ void EditorComponent::Load()
optionsWnd.themeCombo.SetSelected(3);
}
- static wi::eventhandler::Handle handle = wi::eventhandler::Subscribe(TerrainGenerator::EVENT_THEME_RESET, [=](uint64_t) {
- optionsWnd.themeCombo.SetSelected(optionsWnd.themeCombo.GetSelected());
- });
-
RenderPath2D::Load();
}
void EditorComponent::Start()
@@ -516,7 +516,6 @@ void EditorComponent::Update(float dt)
EditorScene& editorscene = GetCurrentEditorScene();
CameraComponent& camera = editorscene.camera;
- optionsWnd.terragen.scene = &scene;
translator.scene = &scene;
if (scene.forces.Contains(grass_interaction_entity))
@@ -1342,6 +1341,7 @@ void EditorComponent::Update(float dt)
componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY);
componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY);
componentsWnd.armatureWnd.SetEntity(INVALID_ENTITY);
+ componentsWnd.terrainWnd.SetEntity(INVALID_ENTITY);
}
else
{
@@ -1381,6 +1381,7 @@ void EditorComponent::Update(float dt)
componentsWnd.cameraComponentWnd.SetEntity(picked.entity);
componentsWnd.expressionWnd.SetEntity(picked.entity);
componentsWnd.armatureWnd.SetEntity(picked.entity);
+ componentsWnd.terrainWnd.SetEntity(picked.entity);
if (picked.subsetIndex >= 0)
{
@@ -1495,8 +1496,6 @@ void EditorComponent::Update(float dt)
optionsWnd.graphicsWnd.pathTraceStatisticsLabel.SetText(ss);
}
- optionsWnd.terragen.Generation_Update(camera);
-
wi::profiler::EndRange(profrange);
RenderPath2D::Update(dt);
@@ -2686,7 +2685,6 @@ void EditorComponent::Save(const std::string& filename)
wi::resourcemanager::Mode embed_mode = (wi::resourcemanager::Mode)optionsWnd.saveModeComboBox.GetItemUserData(optionsWnd.saveModeComboBox.GetSelected());
wi::resourcemanager::SetMode(embed_mode);
- optionsWnd.terragen.BakeVirtualTexturesToFiles();
scene.Serialize(archive);
if (dump_to_header)
diff --git a/Editor/Editor_SOURCE.vcxitems b/Editor/Editor_SOURCE.vcxitems
index 4ce698489..8dbe8f2e2 100644
--- a/Editor/Editor_SOURCE.vcxitems
+++ b/Editor/Editor_SOURCE.vcxitems
@@ -116,7 +116,7 @@
Create
Create
-
+
@@ -169,7 +169,7 @@
-
+
diff --git a/Editor/Editor_SOURCE.vcxitems.filters b/Editor/Editor_SOURCE.vcxitems.filters
index dc0c25a2b..d76dbcbcf 100644
--- a/Editor/Editor_SOURCE.vcxitems.filters
+++ b/Editor/Editor_SOURCE.vcxitems.filters
@@ -72,7 +72,7 @@
meshoptimizer
-
+
@@ -117,7 +117,7 @@
meshoptimizer
-
+
diff --git a/Editor/GraphicsWindow.cpp b/Editor/GraphicsWindow.cpp
index 951e0a114..f2a4de103 100644
--- a/Editor/GraphicsWindow.cpp
+++ b/Editor/GraphicsWindow.cpp
@@ -199,6 +199,16 @@ void GraphicsWindow::Create(EditorComponent* _editor)
});
AddWidget(&ddgiRayCountSlider);
+ ddgiSmoothBackfaceSlider.Create(0, 1, 0, 1000, "DDGI Smoothen: ");
+ ddgiSmoothBackfaceSlider.SetTooltip("Adjust the amount of smooth backface test.");
+ ddgiSmoothBackfaceSlider.SetSize(XMFLOAT2(wid, itemheight));
+ ddgiSmoothBackfaceSlider.SetPos(XMFLOAT2(x, y += step));
+ ddgiSmoothBackfaceSlider.OnSlide([=](wi::gui::EventArgs args) {
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ scene.ddgi.smooth_backface = args.fValue;
+ });
+ AddWidget(&ddgiSmoothBackfaceSlider);
+
ddgiX.Create("");
ddgiX.SetTooltip("Probe count in X dimension.");
ddgiX.SetDescription("DDGI Probes: ");
@@ -1641,6 +1651,7 @@ void GraphicsWindow::ResizeLayout()
ddgiY.SetVisible(false);
ddgiX.SetVisible(false);
ddgiRayCountSlider.SetVisible(false);
+ ddgiSmoothBackfaceSlider.SetVisible(false);
voxelRadianceDebugCheckBox.SetVisible(false);
voxelRadianceCheckBox.SetVisible(false);
voxelRadianceSecondaryBounceCheckBox.SetVisible(false);
@@ -1665,6 +1676,8 @@ void GraphicsWindow::ResizeLayout()
ddgiY.SetVisible(true);
ddgiX.SetVisible(true);
ddgiRayCountSlider.SetVisible(true);
+ ddgiSmoothBackfaceSlider.SetVisible(true);
+ ddgiSmoothBackfaceSlider.SetValue(editor->GetCurrentScene().ddgi.smooth_backface);
voxelRadianceDebugCheckBox.SetVisible(true);
voxelRadianceCheckBox.SetVisible(true);
voxelRadianceSecondaryBounceCheckBox.SetVisible(true);
@@ -1689,6 +1702,7 @@ void GraphicsWindow::ResizeLayout()
ddgiY.SetPos(XMFLOAT2(ddgiZ.GetPos().x - ddgiY.GetSize().x - padding, ddgiZ.GetPos().y));
ddgiX.SetPos(XMFLOAT2(ddgiY.GetPos().x - ddgiX.GetSize().x - padding, ddgiY.GetPos().y));
add(ddgiRayCountSlider);
+ add(ddgiSmoothBackfaceSlider);
y += jump;
diff --git a/Editor/GraphicsWindow.h b/Editor/GraphicsWindow.h
index 1f2d1cf0d..7fc79a707 100644
--- a/Editor/GraphicsWindow.h
+++ b/Editor/GraphicsWindow.h
@@ -27,6 +27,7 @@ public:
wi::gui::TextInputField ddgiY;
wi::gui::TextInputField ddgiZ;
wi::gui::Slider ddgiRayCountSlider;
+ wi::gui::Slider ddgiSmoothBackfaceSlider;
wi::gui::CheckBox voxelRadianceCheckBox;
wi::gui::CheckBox voxelRadianceDebugCheckBox;
wi::gui::CheckBox voxelRadianceSecondaryBounceCheckBox;
diff --git a/Editor/ObjectWindow.cpp b/Editor/ObjectWindow.cpp
index 489bb2b1f..ea84fef74 100644
--- a/Editor/ObjectWindow.cpp
+++ b/Editor/ObjectWindow.cpp
@@ -561,15 +561,16 @@ void ObjectWindow::Create(EditorComponent* _editor)
void ObjectWindow::SetEntity(Entity entity)
{
+ Scene& scene = editor->GetCurrentScene();
+ const ObjectComponent* object = scene.objects.GetComponent(entity);
+ if (object == nullptr)
+ entity = INVALID_ENTITY;
+
if (this->entity == entity)
return;
this->entity = entity;
- Scene& scene = editor->GetCurrentScene();
-
- const ObjectComponent* object = scene.objects.GetComponent(entity);
-
if (object != nullptr)
{
SetEnabled(true);
diff --git a/Editor/OptionsWindow.cpp b/Editor/OptionsWindow.cpp
index 82f4b7dd4..31b0fd7e8 100644
--- a/Editor/OptionsWindow.cpp
+++ b/Editor/OptionsWindow.cpp
@@ -153,6 +153,7 @@ void OptionsWindow::Create(EditorComponent* _editor)
newCombo.AddItem("Animation " ICON_ANIMATION, 15);
newCombo.AddItem("Script " ICON_SCRIPT, 16);
newCombo.AddItem("Collider " ICON_COLLIDER, 17);
+ newCombo.AddItem("Terrain " ICON_TERRAIN, 18);
newCombo.OnSelect([&](wi::gui::EventArgs args) {
newCombo.SetSelectedWithoutCallback(-1);
const EditorComponent::EditorScene& editorscene = editor->GetCurrentEditorScene();
@@ -263,6 +264,12 @@ void OptionsWindow::Create(EditorComponent* _editor)
scene.transforms.Create(pick.entity);
scene.names.Create(pick.entity) = "collider";
break;
+ case 18:
+ editor->componentsWnd.terrainWnd.SetupAssets();
+ pick.entity = CreateEntity();
+ scene.terrains.Create(pick.entity) = editor->componentsWnd.terrainWnd.terrain_preset;
+ scene.names.Create(pick.entity) = "terrain";
+ break;
default:
break;
}
@@ -308,6 +315,7 @@ void OptionsWindow::Create(EditorComponent* _editor)
filterCombo.AddItem("Armature " ICON_ARMATURE, (uint64_t)Filter::Armature);
filterCombo.AddItem("Script " ICON_SCRIPT, (uint64_t)Filter::Script);
filterCombo.AddItem("Expression " ICON_EXPRESSION, (uint64_t)Filter::Expression);
+ filterCombo.AddItem("Terrain " ICON_TERRAIN, (uint64_t)Filter::Terrain);
filterCombo.SetTooltip("Apply filtering to the Entities");
filterCombo.OnSelect([&](wi::gui::EventArgs args) {
filter = (Filter)args.userdata;
@@ -402,144 +410,6 @@ void OptionsWindow::Create(EditorComponent* _editor)
AddWidget(&saveModeComboBox);
-
- terragen.Create();
- terragen.OnCollapse([&](wi::gui::EventArgs args) {
-
- if (terragen.terrainEntity == INVALID_ENTITY)
- {
- // Customize terrain generator before it's initialized:
- terragen.material_Base.SetRoughness(1);
- terragen.material_Base.SetReflectance(0.005f);
- terragen.material_Slope.SetRoughness(0.1f);
- terragen.material_LowAltitude.SetRoughness(1);
- terragen.material_HighAltitude.SetRoughness(1);
- terragen.material_Base.textures[MaterialComponent::BASECOLORMAP].name = "terrain/base.jpg";
- terragen.material_Base.textures[MaterialComponent::NORMALMAP].name = "terrain/base_nor.jpg";
- terragen.material_Slope.textures[MaterialComponent::BASECOLORMAP].name = "terrain/slope.jpg";
- terragen.material_Slope.textures[MaterialComponent::NORMALMAP].name = "terrain/slope_nor.jpg";
- terragen.material_LowAltitude.textures[MaterialComponent::BASECOLORMAP].name = "terrain/low_altitude.jpg";
- terragen.material_LowAltitude.textures[MaterialComponent::NORMALMAP].name = "terrain/low_altitude_nor.jpg";
- terragen.material_HighAltitude.textures[MaterialComponent::BASECOLORMAP].name = "terrain/high_altitude.jpg";
- terragen.material_HighAltitude.textures[MaterialComponent::NORMALMAP].name = "terrain/high_altitude_nor.jpg";
- terragen.material_GrassParticle.textures[MaterialComponent::BASECOLORMAP].name = "terrain/grassparticle.png";
- terragen.material_GrassParticle.alphaRef = 0.75f;
- terragen.grass_properties.length = 5;
- terragen.grass_properties.frameCount = 2;
- terragen.grass_properties.framesX = 1;
- terragen.grass_properties.framesY = 2;
- terragen.grass_properties.frameStart = 0;
- terragen.material_Base.CreateRenderData();
- terragen.material_Slope.CreateRenderData();
- terragen.material_LowAltitude.CreateRenderData();
- terragen.material_HighAltitude.CreateRenderData();
- terragen.material_GrassParticle.CreateRenderData();
- // Tree prop:
- {
- Scene props_scene;
- wi::scene::LoadModel(props_scene, "terrain/tree.wiscene");
- TerrainGenerator::Prop& prop = terragen.props.emplace_back();
- prop.name = "tree";
- prop.min_count_per_chunk = 0;
- prop.max_count_per_chunk = 10;
- prop.region = 0;
- prop.region_power = 2;
- prop.noise_frequency = 0.1f;
- prop.noise_power = 1;
- prop.threshold = 0.4f;
- prop.min_size = 2.0f;
- prop.max_size = 8.0f;
- prop.min_y_offset = -0.5f;
- prop.max_y_offset = -0.5f;
- prop.mesh_entity = props_scene.Entity_FindByName("tree_mesh");
- props_scene.impostors.Create(prop.mesh_entity).swapInDistance = 200;
- Entity object_entity = props_scene.Entity_FindByName("tree_object");
- ObjectComponent* object = props_scene.objects.GetComponent(object_entity);
- if (object != nullptr)
- {
- prop.object = *object;
- prop.object.lod_distance_multiplier = 0.05f;
- //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
- editor->GetCurrentScene().Merge(props_scene);
- }
- // Rock prop:
- {
- Scene props_scene;
- wi::scene::LoadModel(props_scene, "terrain/rock.wiscene");
- TerrainGenerator::Prop& prop = terragen.props.emplace_back();
- prop.name = "rock";
- prop.min_count_per_chunk = 0;
- prop.max_count_per_chunk = 8;
- prop.region = 0;
- prop.region_power = 1;
- prop.noise_frequency = 0.005f;
- prop.noise_power = 2;
- prop.threshold = 0.5f;
- prop.min_size = 0.02f;
- prop.max_size = 4.0f;
- prop.min_y_offset = -2;
- prop.max_y_offset = 0.5f;
- prop.mesh_entity = props_scene.Entity_FindByName("rock_mesh");
- Entity object_entity = props_scene.Entity_FindByName("rock_object");
- ObjectComponent* object = props_scene.objects.GetComponent(object_entity);
- if (object != nullptr)
- {
- prop.object = *object;
- prop.object.lod_distance_multiplier = 0.02f;
- prop.object.cascadeMask = 1; // they won't be rendered into the largest shadow cascade
- 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
- editor->GetCurrentScene().Merge(props_scene);
- }
- // Bush prop:
- {
- Scene props_scene;
- wi::scene::LoadModel(props_scene, "terrain/bush.wiscene");
- TerrainGenerator::Prop& prop = terragen.props.emplace_back();
- prop.name = "bush";
- prop.min_count_per_chunk = 0;
- prop.max_count_per_chunk = 10;
- prop.region = 0;
- prop.region_power = 4;
- prop.noise_frequency = 0.01f;
- prop.noise_power = 4;
- prop.threshold = 0.1f;
- prop.min_size = 0.1f;
- prop.max_size = 1.5f;
- prop.min_y_offset = -1;
- prop.max_y_offset = 0;
- prop.mesh_entity = props_scene.Entity_FindByName("bush_mesh");
- Entity object_entity = props_scene.Entity_FindByName("bush_object");
- ObjectComponent* object = props_scene.objects.GetComponent(object_entity);
- if (object != nullptr)
- {
- prop.object = *object;
- prop.object.lod_distance_multiplier = 0.05f;
- prop.object.cascadeMask = 1; // they won't be rendered into the largest shadow cascade
- 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
- editor->GetCurrentScene().Merge(props_scene);
- }
-
- terragen.init();
- RefreshEntityTree();
- }
-
- if (!terragen.IsCollapsed() && !editor->GetCurrentScene().transforms.Contains(terragen.terrainEntity))
- {
- terragen.Generation_Restart();
- RefreshEntityTree();
- }
-
- });
- AddWidget(&terragen);
-
-
-
enum class Theme
{
Dark,
@@ -749,11 +619,6 @@ void OptionsWindow::ResizeLayout()
pos.y += paintToolWnd.GetSize().y;
pos.y += padding;
- terragen.SetPos(pos);
- terragen.SetSize(XMFLOAT2(width, terragen.GetScale().y));
- pos.y += terragen.GetSize().y;
- pos.y += padding;
-
x_off = 45;
newCombo.SetPos(XMFLOAT2(pos.x + x_off, pos.y));
@@ -796,6 +661,10 @@ void OptionsWindow::PushToEntityTree(wi::ecs::Entity entity, int level)
{
item.name += ICON_TRANSFORM " ";
}
+ if (scene.terrains.Contains(entity))
+ {
+ item.name += ICON_TERRAIN " ";
+ }
if (scene.meshes.Contains(entity))
{
item.name += ICON_MESH " ";
@@ -889,10 +758,6 @@ void OptionsWindow::PushToEntityTree(wi::ecs::Entity entity, int level)
{
item.name += ICON_EXPRESSION " ";
}
- if (entity == terragen.terrainEntity)
- {
- item.name += ICON_TERRAIN " ";
- }
bool bone_found = false;
for (size_t i = 0; i < scene.armatures.GetCount() && !bone_found; ++i)
{
@@ -960,6 +825,17 @@ void OptionsWindow::RefreshEntityTree()
}
}
+ // Add any left over entities that might not have had a hierarchy:
+
+ if (has_flag(filter, Filter::Terrain))
+ {
+ // Any transform left that is not part of a hierarchy:
+ for (size_t i = 0; i < scene.terrains.GetCount(); ++i)
+ {
+ PushToEntityTree(scene.terrains.GetEntity(i), 0);
+ }
+ }
+
if (has_flag(filter, Filter::Transform))
{
// Any transform left that is not part of a hierarchy:
@@ -969,8 +845,6 @@ void OptionsWindow::RefreshEntityTree()
}
}
- // Add any left over entities that might not have had a hierarchy or transform:
-
if (has_flag(filter, Filter::Light))
{
for (size_t i = 0; i < scene.lights.GetCount(); ++i)
diff --git a/Editor/OptionsWindow.h b/Editor/OptionsWindow.h
index 7178eeab9..a3018d306 100644
--- a/Editor/OptionsWindow.h
+++ b/Editor/OptionsWindow.h
@@ -4,7 +4,6 @@
#include "CameraWindow.h"
#include "MaterialPickerWindow.h"
#include "PaintToolWindow.h"
-#include "TerrainGenerator.h"
class EditorComponent;
@@ -33,7 +32,6 @@ public:
CameraWindow cameraWnd;
MaterialPickerWindow materialPickerWnd;
PaintToolWindow paintToolWnd;
- TerrainGenerator terragen;
enum class Filter : uint64_t
{
@@ -56,6 +54,7 @@ public:
Collider = 1 << 16,
Script = 1 << 17,
Expression = 1 << 18,
+ Terrain = 1 << 19,
All = ~0ull,
} filter = Filter::All;
diff --git a/Editor/TerrainGenerator.cpp b/Editor/TerrainGenerator.cpp
deleted file mode 100644
index 9eeb245dc..000000000
--- a/Editor/TerrainGenerator.cpp
+++ /dev/null
@@ -1,1547 +0,0 @@
-#include "stdafx.h"
-#include "TerrainGenerator.h"
-
-#include "Utility/stb_image.h"
-
-using namespace wi::ecs;
-using namespace wi::scene;
-using namespace wi::graphics;
-
-
-struct PerlinModifier : public TerrainGenerator::Modifier
-{
- wi::gui::Slider octavesSlider;
- wi::noise::Perlin perlin_noise;
-
- PerlinModifier() : Modifier("Perlin Noise")
- {
- octavesSlider.Create(1, 8, 6, 7, "Octaves: ");
- octavesSlider.SetTooltip("Octave count for the perlin noise");
- octavesSlider.SetSize(XMFLOAT2(100, 20));
- AddWidget(&octavesSlider);
-
- SetSize(XMFLOAT2(200, 140));
- }
-
- void ResizeLayout() override
- {
- Modifier::ResizeLayout();
- const float padding = 4;
- const float width = GetWidgetAreaSize().x;
- float y = padding;
-
- auto add = [&](wi::gui::Widget& widget) {
- const float margin_left = 100;
- const float margin_right = 50;
- widget.SetPos(XMFLOAT2(margin_left, y));
- widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
- y += widget.GetSize().y;
- y += padding;
- };
-
- add(blendCombo);
- add(blendSlider);
- add(frequencySlider);
-
- add(octavesSlider);
- }
-
- void Seed(uint32_t seed) override
- {
- perlin_noise.init(seed);
- }
- void SetCallback(std::function func) override
- {
- Modifier::SetCallback(func);
- octavesSlider.OnSlide(func);
- }
- void Apply(const XMFLOAT2& world_pos, float& height) override
- {
- XMFLOAT2 p = world_pos;
- p.x *= frequencySlider.GetValue();
- p.y *= frequencySlider.GetValue();
- Blend(height, perlin_noise.compute(p.x, p.y, 0, (int)octavesSlider.GetValue()) * 0.5f + 0.5f);
- }
-};
-struct VoronoiModifier : public TerrainGenerator::Modifier
-{
- wi::gui::Slider fadeSlider;
- wi::gui::Slider shapeSlider;
- wi::gui::Slider falloffSlider;
- wi::gui::Slider perturbationSlider;
- wi::noise::Perlin perlin_noise;
- float seed = 0;
-
- VoronoiModifier() : Modifier("Voronoi Noise")
- {
- fadeSlider.Create(0, 100, 2.59f, 10000, "Fade: ");
- fadeSlider.SetTooltip("Fade out voronoi regions by distance from cell's center");
- fadeSlider.SetSize(XMFLOAT2(100, 20));
- AddWidget(&fadeSlider);
-
- shapeSlider.Create(0, 1, 0.7f, 10000, "Shape: ");
- shapeSlider.SetTooltip("How much the voronoi shape will be kept");
- shapeSlider.SetSize(XMFLOAT2(100, 20));
- AddWidget(&shapeSlider);
-
- falloffSlider.Create(0, 8, 6, 10000, "Falloff: ");
- falloffSlider.SetTooltip("Controls the falloff of the voronoi distance fade effect");
- falloffSlider.SetSize(XMFLOAT2(100, 20));
- AddWidget(&falloffSlider);
-
- perturbationSlider.Create(0, 1, 0.1f, 10000, "Perturbation: ");
- perturbationSlider.SetTooltip("Controls the random look of voronoi region edges");
- perturbationSlider.SetSize(XMFLOAT2(100, 20));
- AddWidget(&perturbationSlider);
-
- SetSize(XMFLOAT2(200, 200));
- }
-
- void ResizeLayout() override
- {
- Modifier::ResizeLayout();
- const float padding = 4;
- const float width = GetWidgetAreaSize().x;
- float y = padding;
-
- auto add = [&](wi::gui::Widget& widget) {
- const float margin_left = 100;
- const float margin_right = 50;
- widget.SetPos(XMFLOAT2(margin_left, y));
- widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
- y += widget.GetSize().y;
- y += padding;
- };
-
- add(blendCombo);
- add(blendSlider);
- add(frequencySlider);
-
- add(fadeSlider);
- add(shapeSlider);
- add(falloffSlider);
- add(perturbationSlider);
- }
-
- void Seed(uint32_t seed) override
- {
- perlin_noise.init(seed);
- this->seed = (float)seed;
- }
- void SetCallback(std::function func) override
- {
- Modifier::SetCallback(func);
- fadeSlider.OnSlide(func);
- shapeSlider.OnSlide(func);
- falloffSlider.OnSlide(func);
- perturbationSlider.OnSlide(func);
- }
- void Apply(const XMFLOAT2& world_pos, float& height) override
- {
- XMFLOAT2 p = world_pos;
- p.x *= frequencySlider.GetValue();
- p.y *= frequencySlider.GetValue();
- if (perturbationSlider.GetValue() > 0)
- {
- const float angle = perlin_noise.compute(p.x, p.y, 0, 6) * XM_2PI;
- p.x += std::sin(angle) * perturbationSlider.GetValue();
- p.y += std::cos(angle) * perturbationSlider.GetValue();
- }
- wi::noise::voronoi::Result res = wi::noise::voronoi::compute(p.x, p.y, seed);
- float weight = std::pow(1 - wi::math::saturate((res.distance - shapeSlider.GetValue()) * fadeSlider.GetValue()), std::max(0.0001f, falloffSlider.GetValue()));
- Blend(height, weight);
- }
-};
-struct HeightmapModifier : public TerrainGenerator::Modifier
-{
- wi::gui::Slider scaleSlider;
- wi::gui::Button loadButton;
-
- wi::vector data;
- int width = 0;
- int height = 0;
-
- HeightmapModifier() : Modifier("Heightmap")
- {
- blendSlider.SetValue(1);
- frequencySlider.SetValue(1);
-
- scaleSlider.Create(0, 1, 0.1f, 1000, "Scale: ");
- scaleSlider.SetSize(XMFLOAT2(100, 20));
- AddWidget(&scaleSlider);
-
- loadButton.Create("Load Heightmap...");
- loadButton.SetTooltip("Load a heightmap texture, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be placed in the world center.");
- AddWidget(&loadButton);
-
- SetSize(XMFLOAT2(200, 180));
- }
-
- void ResizeLayout() override
- {
- Modifier::ResizeLayout();
- const float padding = 4;
- const float width = GetWidgetAreaSize().x;
- float y = padding;
-
- auto add = [&](wi::gui::Widget& widget) {
- const float margin_left = 100;
- const float margin_right = 50;
- widget.SetPos(XMFLOAT2(margin_left, y));
- widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
- y += widget.GetSize().y;
- y += padding;
- };
-
- add(blendCombo);
- add(blendSlider);
- add(frequencySlider);
-
- add(scaleSlider);
- add(loadButton);
- }
-
- void SetCallback(std::function func) override
- {
- Modifier::SetCallback(func);
-
- scaleSlider.OnSlide(func);
-
- loadButton.OnClick([=](wi::gui::EventArgs args) {
-
- wi::helper::FileDialogParams params;
- params.type = wi::helper::FileDialogParams::OPEN;
- params.description = "Texture";
- params.extensions = wi::resourcemanager::GetSupportedImageExtensions();
- wi::helper::FileDialog(params, [=](std::string fileName) {
- wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) {
-
- data.clear();
- width = 0;
- height = 0;
- int bpp = 0;
- stbi_uc* rgba = stbi_load(fileName.c_str(), &width, &height, &bpp, 1);
- if (rgba != nullptr)
- {
- data.resize(width * height);
- for (int i = 0; i < width * height; ++i)
- {
- data[i] = rgba[i];
- }
- stbi_image_free(rgba);
-
- func(args); // callback after heightmap load confirmation
- }
- });
- });
- });
- }
- void Apply(const XMFLOAT2& world_pos, float& height) override
- {
- XMFLOAT2 p = world_pos;
- p.x *= frequencySlider.GetValue();
- p.y *= frequencySlider.GetValue();
- XMFLOAT2 pixel = XMFLOAT2(p.x + width * 0.5f, p.y + this->height * 0.5f);
- if (pixel.x >= 0 && pixel.x < width && pixel.y >= 0 && pixel.y < this->height)
- {
- const int idx = int(pixel.x) + int(pixel.y) * width;
- Blend(height, ((float)data[idx] / 255.0f) * scaleSlider.GetValue());
- }
- }
-};
-
-void TerrainGenerator::AddModifier(Modifier* modifier)
-{
- modifiers.emplace_back().reset(modifier);
- auto generate_callback = [=](wi::gui::EventArgs args) {
- Generation_Restart();
- };
- modifier->SetCallback(generate_callback);
- AddWidget(modifier);
-
- modifier->OnClose([=](wi::gui::EventArgs args) {
- // Can't delete modifier in itself, so add to a deferred deletion queue:
- modifiers_to_remove.push_back(modifier);
- });
-
- wi::eventhandler::FireEvent(EVENT_THEME_RESET, 0);
-}
-
-
-
-enum PRESET
-{
- PRESET_HILLS,
- PRESET_ISLANDS,
- PRESET_MOUNTAINS,
- PRESET_ARCTIC,
-};
-
-void TerrainGenerator::Create()
-{
- RemoveWidgets();
- ClearTransform();
-
- wi::gui::Window::Create("Terrain Generator", wi::gui::Window::WindowControls::COLLAPSE);
- SetSize(XMFLOAT2(420, 840));
-
- float x = 140;
- float y = 0;
- float step = 25;
- float hei = 20;
- float wid = 120;
-
- centerToCamCheckBox.Create("Center to Cam: ");
- centerToCamCheckBox.SetTooltip("Automatically generate chunks around camera. This sets the center chunk to camera position.");
- centerToCamCheckBox.SetSize(XMFLOAT2(hei, hei));
- centerToCamCheckBox.SetPos(XMFLOAT2(x, y));
- centerToCamCheckBox.SetCheck(true);
- AddWidget(¢erToCamCheckBox);
-
- removalCheckBox.Create("Removal: ");
- removalCheckBox.SetTooltip("Automatically remove chunks that are farther than generation distance around center chunk.");
- removalCheckBox.SetSize(XMFLOAT2(hei, hei));
- removalCheckBox.SetPos(XMFLOAT2(x + 100, y));
- removalCheckBox.SetCheck(true);
- AddWidget(&removalCheckBox);
-
- grassCheckBox.Create("Grass: ");
- grassCheckBox.SetTooltip("Specify whether grass generation is enabled.");
- grassCheckBox.SetSize(XMFLOAT2(hei, hei));
- grassCheckBox.SetPos(XMFLOAT2(x, y += step));
- grassCheckBox.SetCheck(true);
- AddWidget(&grassCheckBox);
-
- lodSlider.Create(0.0001f, 0.01f, 0.005f, 10000, "Mesh LOD Distance: ");
- lodSlider.SetTooltip("Set the LOD (Level Of Detail) distance multiplier.\nLow values increase LOD detail in distance");
- lodSlider.SetSize(XMFLOAT2(wid, hei));
- lodSlider.SetPos(XMFLOAT2(x, y += step));
- lodSlider.OnSlide([this](wi::gui::EventArgs args) {
- for (auto& it : chunks)
- {
- const ChunkData& chunk_data = it.second;
- if (chunk_data.entity != INVALID_ENTITY)
- {
- ObjectComponent* object = scene->objects.GetComponent(chunk_data.entity);
- if (object != nullptr)
- {
- object->lod_distance_multiplier = args.fValue;
- }
- }
- }
- });
- AddWidget(&lodSlider);
-
- texlodSlider.Create(0.001f, 0.05f, 0.01f, 10000, "Tex LOD Distance: ");
- texlodSlider.SetTooltip("Set the LOD (Level Of Detail) distance multiplier for virtual textures.\nLow values increase LOD detail in distance");
- texlodSlider.SetSize(XMFLOAT2(wid, hei));
- texlodSlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&texlodSlider);
-
- generationSlider.Create(0, 16, 12, 16, "Generation Distance: ");
- generationSlider.SetTooltip("How far out chunks will be generated (value is in number of chunks)");
- generationSlider.SetSize(XMFLOAT2(wid, hei));
- generationSlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&generationSlider);
-
- propSlider.Create(0, 16, 10, 16, "Prop Distance: ");
- propSlider.SetTooltip("How far out props will be generated (value is in number of chunks)");
- propSlider.SetSize(XMFLOAT2(wid, hei));
- propSlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&propSlider);
-
- propDensitySlider.Create(0, 10, 1, 1000, "Prop Density: ");
- propDensitySlider.SetTooltip("Modifies overall prop density.");
- propDensitySlider.SetSize(XMFLOAT2(wid, hei));
- propDensitySlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&propDensitySlider);
-
- grassDensitySlider.Create(0, 4, 1, 1000, "Grass Density: ");
- grassDensitySlider.SetTooltip("Modifies overall grass density.");
- grassDensitySlider.SetSize(XMFLOAT2(wid, hei));
- grassDensitySlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&grassDensitySlider);
-
- presetCombo.Create("Preset: ");
- presetCombo.SetTooltip("Select a terrain preset");
- presetCombo.SetSize(XMFLOAT2(wid, hei));
- presetCombo.SetPos(XMFLOAT2(x, y += step));
- presetCombo.AddItem("Hills", PRESET_HILLS);
- presetCombo.AddItem("Islands", PRESET_ISLANDS);
- presetCombo.AddItem("Mountains", PRESET_MOUNTAINS);
- presetCombo.AddItem("Arctic", PRESET_ARCTIC);
- presetCombo.OnSelect([this](wi::gui::EventArgs args) {
-
- Generation_Cancel();
- for (auto& modifier : modifiers)
- {
- RemoveWidget(modifier.get());
- }
- modifiers.clear();
- AddModifier(new PerlinModifier);
- AddModifier(new VoronoiModifier);
- PerlinModifier& perlin = *(PerlinModifier*)modifiers[0].get();
- VoronoiModifier& voronoi = *(VoronoiModifier*)modifiers[1].get();
- perlin.blendCombo.SetSelectedByUserdata((uint64_t)Modifier::BlendMode::Additive);
- voronoi.blendCombo.SetSelectedByUserdata((uint64_t)Modifier::BlendMode::Multiply);
-
- switch (args.userdata)
- {
- default:
- case PRESET_HILLS:
- seedSlider.SetValue(5333);
- bottomLevelSlider.SetValue(-60);
- topLevelSlider.SetValue(380);
- perlin.blendSlider.SetValue(0.5f);
- perlin.frequencySlider.SetValue(0.0008f);
- perlin.octavesSlider.SetValue(6);
- voronoi.blendSlider.SetValue(0.5f);
- voronoi.frequencySlider.SetValue(0.001f);
- voronoi.fadeSlider.SetValue(2.59f);
- voronoi.shapeSlider.SetValue(0.7f);
- voronoi.falloffSlider.SetValue(6);
- voronoi.perturbationSlider.SetValue(0.1f);
- region1Slider.SetValue(1);
- region2Slider.SetValue(2);
- region3Slider.SetValue(8);
- break;
- case PRESET_ISLANDS:
- seedSlider.SetValue(4691);
- bottomLevelSlider.SetValue(-79);
- topLevelSlider.SetValue(520);
- perlin.blendSlider.SetValue(0.5f);
- perlin.frequencySlider.SetValue(0.000991f);
- perlin.octavesSlider.SetValue(6);
- voronoi.blendSlider.SetValue(0.5f);
- voronoi.frequencySlider.SetValue(0.000317f);
- voronoi.fadeSlider.SetValue(8.2f);
- voronoi.shapeSlider.SetValue(0.126f);
- voronoi.falloffSlider.SetValue(1.392f);
- voronoi.perturbationSlider.SetValue(0.126f);
- region1Slider.SetValue(8);
- region2Slider.SetValue(0.7f);
- region3Slider.SetValue(8);
- break;
- case PRESET_MOUNTAINS:
- seedSlider.SetValue(8863);
- bottomLevelSlider.SetValue(0);
- topLevelSlider.SetValue(2960);
- perlin.blendSlider.SetValue(0.5f);
- perlin.frequencySlider.SetValue(0.00279f);
- perlin.octavesSlider.SetValue(8);
- voronoi.blendSlider.SetValue(0.5f);
- voronoi.frequencySlider.SetValue(0.000496f);
- voronoi.fadeSlider.SetValue(5.2f);
- voronoi.shapeSlider.SetValue(0.412f);
- voronoi.falloffSlider.SetValue(1.456f);
- voronoi.perturbationSlider.SetValue(0.092f);
- region1Slider.SetValue(1);
- region2Slider.SetValue(1);
- region3Slider.SetValue(0.8f);
- break;
- case PRESET_ARCTIC:
- seedSlider.SetValue(2124);
- bottomLevelSlider.SetValue(-50);
- topLevelSlider.SetValue(40);
- perlin.blendSlider.SetValue(1);
- perlin.frequencySlider.SetValue(0.002f);
- perlin.octavesSlider.SetValue(4);
- voronoi.blendSlider.SetValue(1);
- voronoi.frequencySlider.SetValue(0.004f);
- voronoi.fadeSlider.SetValue(1.8f);
- voronoi.shapeSlider.SetValue(0.518f);
- voronoi.falloffSlider.SetValue(0.2f);
- voronoi.perturbationSlider.SetValue(0.298f);
- region1Slider.SetValue(8);
- region2Slider.SetValue(8);
- region3Slider.SetValue(0);
- break;
- }
- Generation_Restart();
- });
- AddWidget(&presetCombo);
-
-
- addModifierCombo.Create("Add Modifier: ");
- addModifierCombo.selected_font.anim.typewriter.looped = true;
- addModifierCombo.selected_font.anim.typewriter.time = 2;
- addModifierCombo.selected_font.anim.typewriter.character_start = 1;
- addModifierCombo.SetTooltip("Add a new modifier that will affect terrain generation.");
- addModifierCombo.SetSize(XMFLOAT2(wid, hei));
- addModifierCombo.SetPos(XMFLOAT2(x, y += step));
- addModifierCombo.SetInvalidSelectionText("...");
- addModifierCombo.AddItem("Perlin Noise");
- addModifierCombo.AddItem("Voronoi Noise");
- addModifierCombo.AddItem("Heightmap Image");
- addModifierCombo.OnSelect([this](wi::gui::EventArgs args) {
-
- addModifierCombo.SetSelectedWithoutCallback(-1);
- Generation_Cancel();
- switch (args.iValue)
- {
- default:
- break;
- case 0:
- AddModifier(new PerlinModifier);
- break;
- case 1:
- AddModifier(new VoronoiModifier);
- break;
- case 2:
- AddModifier(new HeightmapModifier);
- break;
- }
- Generation_Restart();
-
- });
- AddWidget(&addModifierCombo);
-
- scaleSlider.Create(1, 10, 1, 9, "Chunk Scale: ");
- scaleSlider.SetTooltip("Size of one chunk in horizontal directions.\nLarger chunk scale will cover larger distance, but will have less detail per unit.");
- scaleSlider.SetSize(XMFLOAT2(wid, hei));
- scaleSlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&scaleSlider);
-
- seedSlider.Create(1, 12345, 3926, 12344, "Seed: ");
- seedSlider.SetTooltip("Seed for terrain randomness");
- seedSlider.SetSize(XMFLOAT2(wid, hei));
- seedSlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&seedSlider);
-
- bottomLevelSlider.Create(-100, 0, -60, 10000, "Bottom Level: ");
- bottomLevelSlider.SetTooltip("Terrain mesh grid lowest level");
- bottomLevelSlider.SetSize(XMFLOAT2(wid, hei));
- bottomLevelSlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&bottomLevelSlider);
-
- topLevelSlider.Create(0, 5000, 380, 10000, "Top Level: ");
- topLevelSlider.SetTooltip("Terrain mesh grid topmost level");
- topLevelSlider.SetSize(XMFLOAT2(wid, hei));
- topLevelSlider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&topLevelSlider);
-
- saveHeightmapButton.Create("Save Heightmap...");
- saveHeightmapButton.SetTooltip("Save a heightmap texture from the currently generated terrain, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be normalized into 8bit PNG format which can result in precision loss!");
- saveHeightmapButton.SetSize(XMFLOAT2(wid, hei));
- saveHeightmapButton.SetPos(XMFLOAT2(x, y += step));
- AddWidget(&saveHeightmapButton);
-
- region1Slider.Create(0, 8, 1, 10000, "Slope Region: ");
- region1Slider.SetTooltip("The region's falloff power");
- region1Slider.SetSize(XMFLOAT2(wid, hei));
- region1Slider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(®ion1Slider);
-
- region2Slider.Create(0, 8, 2, 10000, "Low Altitude Region: ");
- region2Slider.SetTooltip("The region's falloff power");
- region2Slider.SetSize(XMFLOAT2(wid, hei));
- region2Slider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(®ion2Slider);
-
- region3Slider.Create(0, 8, 8, 10000, "High Altitude Region: ");
- region3Slider.SetTooltip("The region's falloff power");
- region3Slider.SetSize(XMFLOAT2(wid, hei));
- region3Slider.SetPos(XMFLOAT2(x, y += step));
- AddWidget(®ion3Slider);
-
-
- auto generate_callback = [=](wi::gui::EventArgs args) {
- Generation_Restart();
- };
- scaleSlider.OnSlide(generate_callback);
- seedSlider.OnSlide(generate_callback);
- bottomLevelSlider.OnSlide(generate_callback);
- topLevelSlider.OnSlide(generate_callback);
- region1Slider.OnSlide(generate_callback);
- region2Slider.OnSlide(generate_callback);
- region3Slider.OnSlide(generate_callback);
-
- saveHeightmapButton.OnClick([=](wi::gui::EventArgs args) {
-
- wi::helper::FileDialogParams params;
- params.type = wi::helper::FileDialogParams::SAVE;
- params.description = "PNG";
- params.extensions = { "PNG" };
- wi::helper::FileDialog(params, [=](std::string fileName) {
- wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) {
-
- wi::primitive::AABB aabb;
- for (auto& chunk : chunks)
- {
- const wi::primitive::AABB* object_aabb = scene->aabb_objects.GetComponent(chunk.second.entity);
- if (object_aabb != nullptr)
- {
- aabb = wi::primitive::AABB::Merge(aabb, *object_aabb);
- }
- }
-
- wi::vector data;
- int width = int(aabb.getHalfWidth().x * 2 + 1);
- int height = int(aabb.getHalfWidth().z * 2 + 1);
- data.resize(width * height);
- std::fill(data.begin(), data.end(), 0u);
-
- for (auto& chunk : chunks)
- {
- const ObjectComponent* object = scene->objects.GetComponent(chunk.second.entity);
- if (object != nullptr)
- {
- const MeshComponent* mesh = scene->meshes.GetComponent(object->meshID);
- if (mesh != nullptr)
- {
- const XMMATRIX W = XMLoadFloat4x4(&object->worldMatrix);
- for (auto& x : mesh->vertex_positions)
- {
- XMVECTOR P = XMLoadFloat3(&x);
- P = XMVector3Transform(P, W);
- XMFLOAT3 p;
- XMStoreFloat3(&p, P);
- p.x -= aabb._min.x;
- p.z -= aabb._min.z;
- int coord = int(p.x) + int(p.z) * width;
- data[coord] = uint8_t(wi::math::InverseLerp(aabb._min.y, aabb._max.y, p.y) * 255u);
- }
- }
- }
- }
-
- wi::graphics::TextureDesc desc;
- desc.width = uint32_t(width);
- desc.height = uint32_t(height);
- desc.format = wi::graphics::Format::R8_UNORM;
- bool success = wi::helper::saveTextureToFile(data, desc, wi::helper::ReplaceExtension(fileName, "PNG"));
- assert(success);
-
- });
- });
- });
-
-
- SetCollapsed(true);
-}
-
-void TerrainGenerator::init()
-{
- terrainEntity = CreateEntity();
-
- indices.clear();
- lods.clear();
- lods.resize(max_lod);
- for (int lod = 0; lod < max_lod; ++lod)
- {
- lods[lod].indexOffset = (uint32_t)indices.size();
-
- if (lod == 0)
- {
- for (int x = 0; x < chunk_width - 1; x++)
- {
- for (int z = 0; z < chunk_width - 1; z++)
- {
- int lowerLeft = x + z * chunk_width;
- int lowerRight = (x + 1) + z * chunk_width;
- int topLeft = x + (z + 1) * chunk_width;
- int topRight = (x + 1) + (z + 1) * chunk_width;
-
- indices.push_back(topLeft);
- indices.push_back(lowerLeft);
- indices.push_back(lowerRight);
-
- indices.push_back(topLeft);
- indices.push_back(lowerRight);
- indices.push_back(topRight);
- }
- }
- }
- else
- {
- const int step = 1 << lod;
- // inner grid:
- for (int x = 1; x < chunk_width - 2; x += step)
- {
- for (int z = 1; z < chunk_width - 2; z += step)
- {
- int lowerLeft = x + z * chunk_width;
- int lowerRight = (x + step) + z * chunk_width;
- int topLeft = x + (z + step) * chunk_width;
- int topRight = (x + step) + (z + step) * chunk_width;
-
- indices.push_back(topLeft);
- indices.push_back(lowerLeft);
- indices.push_back(lowerRight);
-
- indices.push_back(topLeft);
- indices.push_back(lowerRight);
- indices.push_back(topRight);
- }
- }
- // bottom border:
- for (int x = 0; x < chunk_width - 1; ++x)
- {
- const int z = 0;
- int current = x + z * chunk_width;
- int neighbor = x + 1 + z * chunk_width;
- int connection = 1 + ((x + (step + 1) / 2 - 1) / step) * step + (z + 1) * chunk_width;
-
- indices.push_back(current);
- indices.push_back(neighbor);
- indices.push_back(connection);
-
- if (((x - 1) % (step)) == step / 2) // halfway fill triangle
- {
- int connection1 = 1 + (((x - 1) + (step + 1) / 2 - 1) / step) * step + (z + 1) * chunk_width;
-
- indices.push_back(current);
- indices.push_back(connection);
- indices.push_back(connection1);
- }
- }
- // top border:
- for (int x = 0; x < chunk_width - 1; ++x)
- {
- const int z = chunk_width - 1;
- int current = x + z * chunk_width;
- int neighbor = x + 1 + z * chunk_width;
- int connection = 1 + ((x + (step + 1) / 2 - 1) / step) * step + (z - 1) * chunk_width;
-
- indices.push_back(current);
- indices.push_back(connection);
- indices.push_back(neighbor);
-
- if (((x - 1) % (step)) == step / 2) // halfway fill triangle
- {
- int connection1 = 1 + (((x - 1) + (step + 1) / 2 - 1) / step) * step + (z - 1) * chunk_width;
-
- indices.push_back(current);
- indices.push_back(connection1);
- indices.push_back(connection);
- }
- }
- // left border:
- for (int z = 0; z < chunk_width - 1; ++z)
- {
- const int x = 0;
- int current = x + z * chunk_width;
- int neighbor = x + (z + 1) * chunk_width;
- int connection = x + 1 + (((z + (step + 1) / 2 - 1) / step) * step + 1) * chunk_width;
-
- indices.push_back(current);
- indices.push_back(connection);
- indices.push_back(neighbor);
-
- if (((z - 1) % (step)) == step / 2) // halfway fill triangle
- {
- int connection1 = x + 1 + ((((z - 1) + (step + 1) / 2 - 1) / step) * step + 1) * chunk_width;
-
- indices.push_back(current);
- indices.push_back(connection1);
- indices.push_back(connection);
- }
- }
- // right border:
- for (int z = 0; z < chunk_width - 1; ++z)
- {
- const int x = chunk_width - 1;
- int current = x + z * chunk_width;
- int neighbor = x + (z + 1) * chunk_width;
- int connection = x - 1 + (((z + (step + 1) / 2 - 1) / step) * step + 1) * chunk_width;
-
- indices.push_back(current);
- indices.push_back(neighbor);
- indices.push_back(connection);
-
- if (((z - 1) % (step)) == step / 2) // halfway fill triangle
- {
- int connection1 = x - 1 + ((((z - 1) + (step + 1) / 2 - 1) / step) * step + 1) * chunk_width;
-
- indices.push_back(current);
- indices.push_back(connection);
- indices.push_back(connection1);
- }
- }
- }
-
- lods[lod].indexCount = (uint32_t)indices.size() - lods[lod].indexOffset;
- }
-
- presetCombo.SetSelectedByUserdata(PRESET_HILLS);
-}
-
-void TerrainGenerator::Generation_Restart()
-{
- Generation_Cancel();
- generation_scene.Clear();
-
- chunks.clear();
-
- scene->Entity_Remove(terrainEntity);
- scene->transforms.Create(terrainEntity);
- scene->names.Create(terrainEntity) = "terrain";
-
- uint32_t seed = (uint32_t)seedSlider.GetValue();
- perlin_noise.init(seed);
- for (auto& modifier : modifiers)
- {
- modifier->Seed(seed);
- }
-
- // Add some nice weather and lighting if there is none yet:
- if (scene->weathers.GetCount() == 0)
- {
- Entity weatherEntity = CreateEntity();
- WeatherComponent& weather = scene->weathers.Create(weatherEntity);
- scene->names.Create(weatherEntity) = "terrainWeather";
- scene->Component_Attach(weatherEntity, terrainEntity);
- weather.ambient = XMFLOAT3(0.2f, 0.2f, 0.2f);
- weather.SetRealisticSky(true);
- weather.SetVolumetricClouds(true);
- weather.volumetricCloudParameters.CoverageAmount = 0.65f;
- weather.volumetricCloudParameters.CoverageMinimum = 0.15f;
- if (presetCombo.GetItemUserData(presetCombo.GetSelected()) == PRESET_ISLANDS)
- {
- weather.SetOceanEnabled(true);
- }
- else
- {
- scene->ocean = {};
- }
- weather.oceanParameters.waterHeight = -40;
- weather.oceanParameters.wave_amplitude = 120;
- weather.fogStart = 300;
- weather.fogEnd = 100000;
- weather.SetHeightFog(true);
- weather.fogHeightStart = 0;
- weather.fogHeightEnd = 100;
- weather.windDirection = XMFLOAT3(0.05f, 0.05f, 0.05f);
- weather.windSpeed = 4;
- weather.stars = 0.6f;
- }
- if (scene->lights.GetCount() == 0)
- {
- Entity sunEntity = scene->Entity_CreateLight("terrainSun");
- scene->Component_Attach(sunEntity, terrainEntity);
- LightComponent& light = *scene->lights.GetComponent(sunEntity);
- light.SetType(LightComponent::LightType::DIRECTIONAL);
- light.intensity = 16;
- light.SetCastShadow(true);
- //light.SetVolumetricsEnabled(true);
- TransformComponent& transform = *scene->transforms.GetComponent(sunEntity);
- transform.RotateRollPitchYaw(XMFLOAT3(XM_PIDIV4, 0, XM_PIDIV4));
- transform.Translate(XMFLOAT3(0, 2, 0));
- }
-}
-
-void TerrainGenerator::Generation_Update(const wi::scene::CameraComponent& camera)
-{
- // The generation task is always cancelled every frame so we are sure that generation is not running at this point
- Generation_Cancel();
-
- // Check whether any modifiers were "closed", and we will really remove them here if so:
- if (!modifiers_to_remove.empty())
- {
- for (auto& modifier : modifiers_to_remove)
- {
- RemoveWidget(modifier);
- for (auto it = modifiers.begin(); it != modifiers.end(); ++it)
- {
- if (it->get() == modifier)
- {
- modifiers.erase(it);
- break;
- }
- }
- }
- Generation_Restart();
- modifiers_to_remove.clear();
- }
-
- if (terrainEntity == INVALID_ENTITY || !scene->transforms.Contains(terrainEntity))
- {
- chunks.clear();
- return;
- }
-
- // What was generated, will be merged in to the main scene
- scene->Merge(generation_scene);
-
- const float chunk_scale = scaleSlider.GetValue();
- const float chunk_scale_rcp = 1.0f / chunk_scale;
-
- if (centerToCamCheckBox.GetCheck())
- {
- center_chunk.x = (int)std::floor((camera.Eye.x + chunk_half_width) * chunk_width_rcp * chunk_scale_rcp);
- center_chunk.z = (int)std::floor((camera.Eye.z + chunk_half_width) * chunk_width_rcp * chunk_scale_rcp);
- }
-
- const int removal_threshold = (int)generationSlider.GetValue() + 2;
- const float texlodMultiplier = texlodSlider.GetValue();
- GraphicsDevice* device = GetDevice();
- virtual_texture_updates.clear();
- virtual_texture_barriers_begin.clear();
- virtual_texture_barriers_end.clear();
-
- // Check whether there are any materials that would write to virtual textures:
- uint32_t max_texture_resolution = 0;
- bool virtual_texture_available[MaterialComponent::TEXTURESLOT_COUNT] = {};
- virtual_texture_available[MaterialComponent::SURFACEMAP] = true; // this is always needed to bake individual material properties
- MaterialComponent* virtual_materials[4] = {
- &material_Base,
- &material_Slope,
- &material_LowAltitude,
- &material_HighAltitude,
- };
- for (auto& material : virtual_materials)
- {
- for (int i = 0; i < MaterialComponent::TEXTURESLOT_COUNT; ++i)
- {
- switch (i)
- {
- case MaterialComponent::BASECOLORMAP:
- case MaterialComponent::NORMALMAP:
- case MaterialComponent::SURFACEMAP:
- if (material->textures[i].resource.IsValid())
- {
- virtual_texture_available[i] = true;
- const TextureDesc& desc = material->textures[i].resource.GetTexture().GetDesc();
- max_texture_resolution = std::max(max_texture_resolution, desc.width);
- max_texture_resolution = std::max(max_texture_resolution, desc.height);
- }
- break;
- default:
- break;
- }
- }
- }
-
- for (auto it = chunks.begin(); it != chunks.end();)
- {
- const Chunk& chunk = it->first;
- ChunkData& chunk_data = it->second;
- const int dist = std::max(std::abs(center_chunk.x - chunk.x), std::abs(center_chunk.z - chunk.z));
-
- // chunk removal:
- if (removalCheckBox.GetCheck())
- {
- if (dist > removal_threshold)
- {
- scene->Entity_Remove(it->second.entity);
- it = chunks.erase(it);
- continue; // don't increment iterator
- }
- else
- {
- // Grass patch removal:
- if (chunk_data.grass.meshID != INVALID_ENTITY && (dist > 1 || !grassCheckBox.GetCheck()))
- {
- scene->Entity_Remove(chunk_data.grass_entity);
- chunk_data.grass_entity = INVALID_ENTITY; // grass can be generated here by generation thread...
- }
-
- // Prop removal:
- if (chunk_data.props_entity != INVALID_ENTITY && (dist > int(propSlider.GetValue()) || std::abs(chunk_data.prop_density_current - propDensitySlider.GetValue()) > std::numeric_limits::epsilon()))
- {
- scene->Entity_Remove(chunk_data.props_entity);
- chunk_data.props_entity = INVALID_ENTITY; // prop can be generated here by generation thread...
- }
- }
- }
-
- // Grass density modification:
- if (chunk_data.grass_entity != INVALID_ENTITY && std::abs(chunk_data.grass_density_current - grassDensitySlider.GetValue()) > std::numeric_limits::epsilon())
- {
- wi::HairParticleSystem* grass = scene->hairs.GetComponent(chunk_data.grass_entity);
- if (grass != nullptr)
- {
- chunk_data.grass_density_current = grassDensitySlider.GetValue();
- grass->strandCount = uint32_t(chunk_data.grass.strandCount * chunk_data.grass_density_current);
- }
- }
-
- // Collect virtual texture update requests:
- if (max_texture_resolution > 0)
- {
- uint32_t texture_lod = 0;
- const float distsq = wi::math::DistanceSquared(camera.Eye, chunk_data.sphere.center);
- const float radius = chunk_data.sphere.radius;
- const float radiussq = radius * radius;
- if (distsq < radiussq)
- {
- texture_lod = 0;
- }
- else
- {
- const float dist = std::sqrt(distsq);
- const float dist_to_sphere = dist - radius;
- texture_lod = uint32_t(dist_to_sphere * texlodMultiplier);
- }
-
- chunk_data.required_texture_resolution = uint32_t(max_texture_resolution / std::pow(2.0f, (float)std::max(0u, texture_lod)));
- chunk_data.required_texture_resolution = std::max(8u, chunk_data.required_texture_resolution);
- MaterialComponent* material = scene->materials.GetComponent(chunk_data.entity);
-
- if (material != nullptr)
- {
- bool need_update = false;
- for (int i = 0; i < MaterialComponent::TEXTURESLOT_COUNT; ++i)
- {
- if (virtual_texture_available[i])
- {
- uint32_t current_resolution = 0;
- if (material->textures[i].resource.IsValid())
- {
- current_resolution = material->textures[i].resource.GetTexture().GetDesc().width;
- }
-
- if (current_resolution != chunk_data.required_texture_resolution)
- {
- need_update = true;
- TextureDesc desc;
- desc.width = chunk_data.required_texture_resolution;
- desc.height = chunk_data.required_texture_resolution;
- desc.format = Format::R8G8B8A8_UNORM;
- desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
- Texture texture;
- bool success = device->CreateTexture(&desc, nullptr, &texture);
- assert(success);
-
- material->textures[i].resource.SetTexture(texture);
- virtual_texture_barriers_begin.push_back(GPUBarrier::Image(&material->textures[i].resource.GetTexture(), desc.layout, ResourceState::UNORDERED_ACCESS));
- virtual_texture_barriers_end.push_back(GPUBarrier::Image(&material->textures[i].resource.GetTexture(), ResourceState::UNORDERED_ACCESS, desc.layout));
- }
- }
- }
-
- if (need_update)
- {
- virtual_texture_updates.push_back(chunk);
- }
-
- }
- }
-
- it++;
- }
-
- // Execute batched virtual texture updates:
- if (!virtual_texture_updates.empty())
- {
- CommandList cmd = device->BeginCommandList();
- device->EventBegin("TerrainVirtualTextureUpdate", cmd);
- auto range = wi::profiler::BeginRangeGPU("TerrainVirtualTextureUpdate", cmd);
- device->Barrier(virtual_texture_barriers_begin.data(), (uint32_t)virtual_texture_barriers_begin.size(), cmd);
-
- device->BindComputeShader(wi::renderer::GetShader(wi::enums::CSTYPE_TERRAIN_VIRTUALTEXTURE_UPDATE), cmd);
-
- ShaderMaterial materials[4];
- material_Base.WriteShaderMaterial(&materials[0]);
- material_Slope.WriteShaderMaterial(&materials[1]);
- material_LowAltitude.WriteShaderMaterial(&materials[2]);
- material_HighAltitude.WriteShaderMaterial(&materials[3]);
- device->BindDynamicConstantBuffer(materials, 10, cmd);
-
- for (auto& chunk : virtual_texture_updates)
- {
- auto it = chunks.find(chunk);
- if (it == chunks.end())
- continue;
- ChunkData& chunk_data = it->second;
-
- const GPUResource* res[] = {
- &chunk_data.region_weights_texture,
- };
- device->BindResources(res, 0, arraysize(res), cmd);
-
- MaterialComponent* material = scene->materials.GetComponent(chunk_data.entity);
- if (material != nullptr)
- {
- // Shrink the uvs to avoid wrap sampling across edge by object rendering shaders:
- float virtual_texture_resolution_rcp = 1.0f / float(chunk_data.required_texture_resolution);
- material->texMulAdd.x = float(chunk_data.required_texture_resolution - 1) * virtual_texture_resolution_rcp;
- material->texMulAdd.y = float(chunk_data.required_texture_resolution - 1) * virtual_texture_resolution_rcp;
- material->texMulAdd.z = 0.5f * virtual_texture_resolution_rcp;
- material->texMulAdd.w = 0.5f * virtual_texture_resolution_rcp;
-
- for (int i = 0; i < MaterialComponent::TEXTURESLOT_COUNT; ++i)
- {
- if (virtual_texture_available[i])
- {
- device->BindUAV(material->textures[i].GetGPUResource(), i, cmd);
- }
- }
- }
-
- device->Dispatch(chunk_data.required_texture_resolution / 8u, chunk_data.required_texture_resolution / 8u, 1, cmd);
- }
-
- device->Barrier(virtual_texture_barriers_end.data(), (uint32_t)virtual_texture_barriers_end.size(), cmd);
- wi::profiler::EndRange(range);
- device->EventEnd(cmd);
- }
-
- // Start the generation on a background thread and keep it running until the next frame
- wi::jobsystem::Execute(generation_workload, [=](wi::jobsystem::JobArgs args) {
-
- wi::Timer timer;
- const float lodMultiplier = lodSlider.GetValue();
- const int generation = (int)generationSlider.GetValue();
- const int prop_generation = (int)propSlider.GetValue();
- const float bottomLevel = bottomLevelSlider.GetValue();
- const float topLevel = topLevelSlider.GetValue();
- const uint32_t seed = (uint32_t)seedSlider.GetValue();
- const float region1 = region1Slider.GetValue();
- const float region2 = region2Slider.GetValue();
- const float region3 = region3Slider.GetValue();
- bool generated_something = false;
-
- auto request_chunk = [&](int offset_x, int offset_z)
- {
- Chunk chunk = center_chunk;
- chunk.x += offset_x;
- chunk.z += offset_z;
- auto it = chunks.find(chunk);
- if (it == chunks.end() || it->second.entity == INVALID_ENTITY)
- {
- // Generate a new chunk:
- ChunkData& chunk_data = chunks[chunk];
-
- chunk_data.entity = generation_scene.Entity_CreateObject("chunk_" + std::to_string(chunk.x) + "_" + std::to_string(chunk.z));
- ObjectComponent& object = *generation_scene.objects.GetComponent(chunk_data.entity);
- object.lod_distance_multiplier = lodMultiplier;
- generation_scene.Component_Attach(chunk_data.entity, terrainEntity);
-
- TransformComponent& transform = *generation_scene.transforms.GetComponent(chunk_data.entity);
- transform.ClearTransform();
- chunk_data.position = XMFLOAT3(float(chunk.x * (chunk_width - 1)) * chunk_scale, 0, float(chunk.z * (chunk_width - 1)) * chunk_scale);
- transform.Translate(chunk_data.position);
- transform.UpdateTransform();
-
- MaterialComponent& material = generation_scene.materials.Create(chunk_data.entity);
- // material params will be 1 because they will be created from only texture maps
- // because region materials are blended together into one texture
- material.SetRoughness(1);
- material.SetMetalness(1);
- material.SetReflectance(1);
-
- MeshComponent& mesh = generation_scene.meshes.Create(chunk_data.entity);
- object.meshID = chunk_data.entity;
- mesh.indices = indices;
- for (auto& lod : lods)
- {
- mesh.subsets.emplace_back();
- mesh.subsets.back().materialID = chunk_data.entity;
- mesh.subsets.back().indexCount = lod.indexCount;
- mesh.subsets.back().indexOffset = lod.indexOffset;
- }
- mesh.subsets_per_lod = 1;
- mesh.vertex_positions.resize(vertexCount);
- mesh.vertex_normals.resize(vertexCount);
- mesh.vertex_uvset_0.resize(vertexCount);
-
- chunk_data.mesh_vertex_positions = mesh.vertex_positions.data();
-
- wi::HairParticleSystem grass = grass_properties;
- grass.vertex_lengths.resize(vertexCount);
- std::atomic grass_valid_vertex_count{ 0 };
-
- // Do a parallel for loop over all the chunk's vertices and compute their properties:
- wi::jobsystem::context ctx;
- wi::jobsystem::Dispatch(ctx, vertexCount, chunk_width, [&](wi::jobsystem::JobArgs args) {
- uint32_t index = args.jobIndex;
- const float x = (float(index % chunk_width) - chunk_half_width) * chunk_scale;
- const float z = (float(index / chunk_width) - chunk_half_width) * chunk_scale;
- XMVECTOR corners[3];
- XMFLOAT2 corner_offsets[3] = {
- XMFLOAT2(0, 0),
- XMFLOAT2(1, 0),
- XMFLOAT2(0, 1),
- };
- for (int i = 0; i < arraysize(corners); ++i)
- {
- float height = 0;
- const XMFLOAT2 world_pos = XMFLOAT2(chunk_data.position.x + x + corner_offsets[i].x, chunk_data.position.z + z + corner_offsets[i].y);
- for (auto& modifier : modifiers)
- {
- modifier->Apply(world_pos, height);
- }
- height = wi::math::Lerp(bottomLevel, topLevel, height);
- corners[i] = XMVectorSet(world_pos.x, height, world_pos.y, 0);
- }
- const float height = XMVectorGetY(corners[0]);
- const XMVECTOR T = XMVectorSubtract(corners[2], corners[1]);
- const XMVECTOR B = XMVectorSubtract(corners[1], corners[0]);
- const XMVECTOR N = XMVector3Normalize(XMVector3Cross(T, B));
- XMFLOAT3 normal;
- XMStoreFloat3(&normal, N);
-
- const float region_base = 1;
- const float region_slope = std::pow(1.0f - wi::math::saturate(normal.y), region1);
- const float region_low_altitude = bottomLevel == 0 ? 0 : std::pow(wi::math::saturate(wi::math::InverseLerp(0, bottomLevel, height)), region2);
- const float region_high_altitude = topLevel == 0 ? 0 : std::pow(wi::math::saturate(wi::math::InverseLerp(0, topLevel, height)), region3);
-
- XMFLOAT4 materialBlendWeights(region_base, 0, 0, 0);
- materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 1, 0, 0), region_slope);
- materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 0, 1, 0), region_low_altitude);
- materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 0, 0, 1), region_high_altitude);
- const float weight_norm = 1.0f / (materialBlendWeights.x + materialBlendWeights.y + materialBlendWeights.z + materialBlendWeights.w);
- materialBlendWeights.x *= weight_norm;
- materialBlendWeights.y *= weight_norm;
- materialBlendWeights.z *= weight_norm;
- materialBlendWeights.w *= weight_norm;
-
- chunk_data.region_weights[index] = wi::Color::fromFloat4(materialBlendWeights);
-
- mesh.vertex_positions[index] = XMFLOAT3(x, height, z);
- mesh.vertex_normals[index] = normal;
- const XMFLOAT2 uv = XMFLOAT2(x * chunk_scale_rcp * chunk_width_rcp + 0.5f, z * chunk_scale_rcp * chunk_width_rcp + 0.5f);
- mesh.vertex_uvset_0[index] = uv;
-
- XMFLOAT3 vertex_pos(chunk_data.position.x + x, height, chunk_data.position.z + z);
-
- const float grass_noise_frequency = 0.1f;
- const float grass_noise = perlin_noise.compute(vertex_pos.x * grass_noise_frequency, vertex_pos.y * grass_noise_frequency, vertex_pos.z * grass_noise_frequency) * 0.5f + 0.5f;
- const float region_grass = std::pow(materialBlendWeights.x * (1 - materialBlendWeights.w), 8.0f) * grass_noise;
- if (region_grass > 0.1f)
- {
- grass_valid_vertex_count.fetch_add(1);
- grass.vertex_lengths[index] = region_grass;
- }
- else
- {
- grass.vertex_lengths[index] = 0;
- }
- });
- wi::jobsystem::Wait(ctx); // wait until chunk's vertex buffer is fully generated
-
- wi::jobsystem::Execute(ctx, [&](wi::jobsystem::JobArgs args) {
- mesh.CreateRenderData();
- chunk_data.sphere.center = mesh.aabb.getCenter();
- chunk_data.sphere.center.x += chunk_data.position.x;
- chunk_data.sphere.center.y += chunk_data.position.y;
- chunk_data.sphere.center.z += chunk_data.position.z;
- chunk_data.sphere.radius = mesh.aabb.getRadius();
- });
-
- // If there were any vertices in this chunk that could be valid for grass, store the grass particle system:
- if (grass_valid_vertex_count.load() > 0)
- {
- chunk_data.grass = std::move(grass); // the grass will be added to the scene later, only when the chunk is close to the camera (center chunk's neighbors)
- chunk_data.grass.meshID = chunk_data.entity;
- chunk_data.grass.strandCount = uint32_t(grass_valid_vertex_count.load() * 3 * chunk_scale * chunk_scale); // chunk_scale * chunk_scale : grass density increases with squared amount with chunk scale (x*z)
- chunk_data.grass.viewDistance = chunk_width * chunk_scale;
- }
-
- // Create the blend weights texture for virtual texture update:
- {
- TextureDesc desc;
- desc.width = (uint32_t)chunk_width;
- desc.height = (uint32_t)chunk_width;
- desc.format = Format::R8G8B8A8_UNORM;
- desc.bind_flags = BindFlag::SHADER_RESOURCE;
- SubresourceData data;
- data.data_ptr = chunk_data.region_weights;
- data.row_pitch = chunk_width * sizeof(chunk_data.region_weights[0]);
- bool success = device->CreateTexture(&desc, &data, &chunk_data.region_weights_texture);
- assert(success);
- }
-
- wi::jobsystem::Wait(ctx); // wait until mesh.CreateRenderData() async task finishes
- generated_something = true;
- }
-
- const int dist = std::max(std::abs(center_chunk.x - chunk.x), std::abs(center_chunk.z - chunk.z));
-
- // Grass patch placement:
- if (dist <= 1 && grassCheckBox.GetCheck())
- {
- it = chunks.find(chunk);
- if (it != chunks.end() && it->second.entity != INVALID_ENTITY)
- {
- ChunkData& chunk_data = it->second;
- if (chunk_data.grass_entity == INVALID_ENTITY && chunk_data.grass.meshID != INVALID_ENTITY)
- {
- // add patch for this chunk
- chunk_data.grass_entity = CreateEntity();
- wi::HairParticleSystem& grass = generation_scene.hairs.Create(chunk_data.grass_entity);
- grass = chunk_data.grass;
- chunk_data.grass_density_current = grassDensitySlider.GetValue();
- grass.strandCount = uint32_t(grass.strandCount * chunk_data.grass_density_current);
- generation_scene.materials.Create(chunk_data.grass_entity) = material_GrassParticle;
- generation_scene.transforms.Create(chunk_data.grass_entity);
- generation_scene.names.Create(chunk_data.grass_entity) = "grass";
- generation_scene.Component_Attach(chunk_data.grass_entity, chunk_data.entity, true);
- generated_something = true;
- }
- }
- }
-
- // Prop placement:
- if (dist <= prop_generation)
- {
- it = chunks.find(chunk);
- if (it != chunks.end() && it->second.entity != INVALID_ENTITY)
- {
- ChunkData& chunk_data = it->second;
-
- if (chunk_data.props_entity == INVALID_ENTITY)
- {
- chunk_data.props_entity = CreateEntity();
- generation_scene.transforms.Create(chunk_data.props_entity);
- generation_scene.names.Create(chunk_data.props_entity) = "props";
- generation_scene.Component_Attach(chunk_data.props_entity, chunk_data.entity, true);
- chunk_data.prop_density_current = propDensitySlider.GetValue();
-
- chunk_data.prop_rand.seed((uint32_t)chunk.compute_hash() ^ seed);
- for (auto& prop : props)
- {
- std::uniform_int_distribution gen_distr(
- uint32_t(prop.min_count_per_chunk * chunk_data.prop_density_current),
- uint32_t(prop.max_count_per_chunk * chunk_data.prop_density_current)
- );
- int gen_count = gen_distr(chunk_data.prop_rand);
- for (int i = 0; i < gen_count; ++i)
- {
- std::uniform_real_distribution float_distr(0.0f, 1.0f);
- std::uniform_int_distribution ind_distr(0, lods[0].indexCount / 3 - 1);
- uint32_t tri = ind_distr(chunk_data.prop_rand); // random triangle on the chunk mesh
- uint32_t ind0 = indices[tri * 3 + 0];
- uint32_t ind1 = indices[tri * 3 + 1];
- uint32_t ind2 = indices[tri * 3 + 2];
- const XMFLOAT3& pos0 = chunk_data.mesh_vertex_positions[ind0];
- const XMFLOAT3& pos1 = chunk_data.mesh_vertex_positions[ind1];
- const XMFLOAT3& pos2 = chunk_data.mesh_vertex_positions[ind2];
- const XMFLOAT4 region0 = chunk_data.region_weights[ind0];
- const XMFLOAT4 region1 = chunk_data.region_weights[ind1];
- const XMFLOAT4 region2 = chunk_data.region_weights[ind2];
- // random barycentric coords on the triangle:
- float f = float_distr(chunk_data.prop_rand);
- float g = float_distr(chunk_data.prop_rand);
- if (f + g > 1)
- {
- f = 1 - f;
- g = 1 - g;
- }
- XMFLOAT3 vertex_pos;
- vertex_pos.x = pos0.x + f * (pos1.x - pos0.x) + g * (pos2.x - pos0.x);
- vertex_pos.y = pos0.y + f * (pos1.y - pos0.y) + g * (pos2.y - pos0.y);
- vertex_pos.z = pos0.z + f * (pos1.z - pos0.z) + g * (pos2.z - pos0.z);
- XMFLOAT4 region;
- region.x = region0.x + f * (region1.x - region0.x) + g * (region2.x - region0.x);
- region.y = region0.y + f * (region1.y - region0.y) + g * (region2.y - region0.y);
- region.z = region0.z + f * (region1.z - region0.z) + g * (region2.z - region0.z);
- region.w = region0.w + f * (region1.w - region0.w) + g * (region2.w - region0.w);
-
- const float noise = std::pow(perlin_noise.compute((vertex_pos.x + chunk_data.position.x) * prop.noise_frequency, vertex_pos.y * prop.noise_frequency, (vertex_pos.z + chunk_data.position.z) * prop.noise_frequency) * 0.5f + 0.5f, prop.noise_power);
- const float chance = std::pow(((float*)®ion)[prop.region], prop.region_power) * noise;
- if (chance > prop.threshold)
- {
- Entity entity = generation_scene.Entity_CreateObject(prop.name + std::to_string(i));
- ObjectComponent* object = generation_scene.objects.GetComponent(entity);
- *object = prop.object;
- TransformComponent* transform = generation_scene.transforms.GetComponent(entity);
- XMFLOAT3 offset = vertex_pos;
- offset.y += wi::math::Lerp(prop.min_y_offset, prop.max_y_offset, float_distr(chunk_data.prop_rand));
- transform->Translate(offset);
- const float scaling = wi::math::Lerp(prop.min_size, prop.max_size, float_distr(chunk_data.prop_rand));
- transform->Scale(XMFLOAT3(scaling, scaling, scaling));
- transform->RotateRollPitchYaw(XMFLOAT3(0, XM_2PI * float_distr(chunk_data.prop_rand), 0));
- transform->UpdateTransform();
- generation_scene.Component_Attach(entity, chunk_data.props_entity, true);
- generated_something = true;
- }
- }
- }
- }
- }
- }
-
- if (generated_something && timer.elapsed_milliseconds() > generation_time_budget_milliseconds)
- {
- generation_cancelled.store(true);
- }
-
- };
-
- // generate center chunk first:
- request_chunk(0, 0);
- if (generation_cancelled.load()) return;
-
- // then generate neighbor chunks in outward spiral:
- for (int growth = 0; growth < generation; ++growth)
- {
- const int side = 2 * (growth + 1);
- int x = -growth - 1;
- int z = -growth - 1;
- for (int i = 0; i < side; ++i)
- {
- request_chunk(x, z);
- if (generation_cancelled.load()) return;
- x++;
- }
- for (int i = 0; i < side; ++i)
- {
- request_chunk(x, z);
- if (generation_cancelled.load()) return;
- z++;
- }
- for (int i = 0; i < side; ++i)
- {
- request_chunk(x, z);
- if (generation_cancelled.load()) return;
- x--;
- }
- for (int i = 0; i < side; ++i)
- {
- request_chunk(x, z);
- if (generation_cancelled.load()) return;
- z--;
- }
- }
-
- });
-
-}
-
-void TerrainGenerator::Generation_Cancel()
-{
- generation_cancelled.store(true); // tell the generation thread that work must be stopped
- wi::jobsystem::Wait(generation_workload); // waits until generation thread exits
- generation_cancelled.store(false); // the next generation can run
-}
-
-void TerrainGenerator::BakeVirtualTexturesToFiles()
-{
- if (terrainEntity == INVALID_ENTITY)
- {
- return;
- }
-
- wi::jobsystem::context ctx;
-
- static const std::string extension = "PNG";
-
- for (auto it = chunks.begin(); it != chunks.end(); it++)
- {
- const Chunk& chunk = it->first;
- ChunkData& chunk_data = it->second;
- MaterialComponent* material = scene->materials.GetComponent(chunk_data.entity);
- if (material != nullptr)
- {
- for (int i = 0; i < MaterialComponent::TEXTURESLOT_COUNT; ++i)
- {
- auto& tex = material->textures[i];
- switch (i)
- {
- case MaterialComponent::BASECOLORMAP:
- case MaterialComponent::SURFACEMAP:
- case MaterialComponent::NORMALMAP:
- if (tex.name.empty() && tex.GetGPUResource() != nullptr)
- {
- wi::vector filedata;
- if (wi::helper::saveTextureToMemory(tex.resource.GetTexture(), filedata))
- {
- tex.resource.SetFileData(std::move(filedata));
- wi::jobsystem::Execute(ctx, [i, &tex, chunk](wi::jobsystem::JobArgs args) {
- wi::vector filedata_ktx2;
- if (wi::helper::saveTextureToMemoryFile(tex.resource.GetFileData(), tex.resource.GetTexture().desc, extension, filedata_ktx2))
- {
- tex.name = std::to_string(chunk.x) + "_" + std::to_string(chunk.z);
- switch (i)
- {
- case MaterialComponent::BASECOLORMAP:
- tex.name += "_basecolormap";
- break;
- case MaterialComponent::SURFACEMAP:
- tex.name += "_surfacemap";
- break;
- case MaterialComponent::NORMALMAP:
- tex.name += "_normalmap";
- break;
- default:
- break;
- }
- tex.name += "." + extension;
- tex.resource = wi::resourcemanager::Load(tex.name, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA, filedata_ktx2.data(), filedata_ktx2.size());
- }
- });
- }
- }
- break;
- default:
- break;
- }
- }
- }
- }
-
- wi::helper::messageBox("Baking terrain virtual textures, this could take a while!", "Attention!");
-
- wi::jobsystem::Wait(ctx);
-
- for (auto it = chunks.begin(); it != chunks.end(); it++)
- {
- const Chunk& chunk = it->first;
- ChunkData& chunk_data = it->second;
- MaterialComponent* material = scene->materials.GetComponent(chunk_data.entity);
- if (material != nullptr)
- {
- material->CreateRenderData();
- }
- }
-}
-
-
-void TerrainGenerator::ResizeLayout()
-{
- wi::gui::Window::ResizeLayout();
- const float padding = 4;
- const float width = GetWidgetAreaSize().x;
- float y = padding;
-
- auto add = [&] (wi::gui::Widget& widget) {
- const float margin_left = 150;
- const float margin_right = 45;
- widget.SetPos(XMFLOAT2(margin_left, y));
- widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
- y += widget.GetSize().y;
- y += padding;
- };
- auto add_checkbox = [&](wi::gui::CheckBox& widget) {
- const float margin_right = 45;
- widget.SetPos(XMFLOAT2(width - margin_right - widget.GetSize().x, y));
- y += widget.GetSize().y;
- y += padding;
- };
- auto add_window = [&](wi::gui::Window& widget) {
- const float margin_left = padding;
- const float margin_right = padding;
- widget.SetPos(XMFLOAT2(margin_left, y));
- widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
- y += widget.GetSize().y;
- y += padding;
- };
-
- add_checkbox(centerToCamCheckBox);
- add_checkbox(removalCheckBox);
- add_checkbox(grassCheckBox);
- add(lodSlider);
- add(texlodSlider);
- add(generationSlider);
- add(propSlider);
- add(propDensitySlider);
- add(grassDensitySlider);
- add(presetCombo);
- add(scaleSlider);
- add(seedSlider);
- add(bottomLevelSlider);
- add(topLevelSlider);
- add(region1Slider);
- add(region2Slider);
- add(region3Slider);
- add(saveHeightmapButton);
- add(addModifierCombo);
-
- for (auto& modifier : modifiers)
- {
- add_window(*modifier);
- }
-
-}
diff --git a/Editor/TerrainGenerator.h b/Editor/TerrainGenerator.h
deleted file mode 100644
index aa6d73e82..000000000
--- a/Editor/TerrainGenerator.h
+++ /dev/null
@@ -1,211 +0,0 @@
-#pragma once
-#include "WickedEngine.h"
-
-#include
-#include
-#include
-
-struct Chunk
-{
- int x, z;
- constexpr bool operator==(const Chunk& other) const
- {
- return (x == other.x) && (z == other.z);
- }
- inline size_t compute_hash() const
- {
- return ((std::hash()(x) ^ (std::hash()(z) << 1)) >> 1);
- }
-};
-namespace std
-{
- template <>
- struct hash
- {
- inline size_t operator()(const Chunk& chunk) const
- {
- return chunk.compute_hash();
- }
- };
-}
-
-inline static const int chunk_width = 64 + 3; // + 3: filler vertices for lod apron and grid perimeter
-inline static const float chunk_half_width = (chunk_width - 1) * 0.5f;
-inline static const float chunk_width_rcp = 1.0f / (chunk_width - 1);
-inline static const uint32_t vertexCount = chunk_width * chunk_width;
-inline static const int max_lod = (int)std::log2(chunk_width - 3) + 1;
-struct ChunkData
-{
- wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY;
- wi::ecs::Entity grass_entity = wi::ecs::INVALID_ENTITY;
- wi::ecs::Entity props_entity = wi::ecs::INVALID_ENTITY;
- const XMFLOAT3* mesh_vertex_positions = nullptr;
- float prop_density_current = 1;
- wi::HairParticleSystem grass;
- float grass_density_current = 1;
- std::mt19937 prop_rand;
- wi::Color region_weights[vertexCount] = {};
- wi::graphics::Texture region_weights_texture;
- uint32_t required_texture_resolution = 0;
- wi::primitive::Sphere sphere;
- XMFLOAT3 position = XMFLOAT3(0, 0, 0);
-};
-
-struct TerrainGenerator : public wi::gui::Window
-{
- static constexpr int EVENT_THEME_RESET = 12345;
- wi::ecs::Entity terrainEntity = wi::ecs::INVALID_ENTITY;
- wi::scene::Scene* scene = nullptr;
- wi::scene::MaterialComponent material_Base;
- wi::scene::MaterialComponent material_Slope;
- wi::scene::MaterialComponent material_LowAltitude;
- wi::scene::MaterialComponent material_HighAltitude;
- wi::scene::MaterialComponent material_GrassParticle;
- wi::HairParticleSystem grass_properties;
- wi::unordered_map chunks;
- wi::vector indices;
- struct LOD
- {
- uint32_t indexOffset = 0;
- uint32_t indexCount = 0;
- };
- wi::vector lods;
- Chunk center_chunk = {};
- wi::noise::Perlin perlin_noise;
-
- struct Prop
- {
- std::string name = "prop";
- wi::ecs::Entity mesh_entity = wi::ecs::INVALID_ENTITY;
- wi::scene::ObjectComponent object;
- int min_count_per_chunk = 0; // a chunk will try to generate min this many props of this type
- int max_count_per_chunk = 10; // a chunk will try to generate max this many props of this type
- int region = 0; // region selection in range [0,3] (0: base/grass, 1: slopes, 2: low altitude (bottom level-0), 3: high altitude (0-top level))
- float region_power = 1; // region weight affection power factor
- float noise_frequency = 1; // perlin noise's frequency for placement factor
- float noise_power = 1; // perlin noise's power
- float threshold = 0.5f; // the chance of placement (higher is less chance)
- float min_size = 1; // scaling randomization range min
- float max_size = 1; // scaling randomization range max
- float min_y_offset = 0; // min randomized offset on Y axis
- float max_y_offset = 0; // max randomized offset on Y axis
- };
- wi::vector props;
-
- // For generating scene on a background thread:
- wi::scene::Scene generation_scene; // The background generation thread can safely add things to this, it will be merged into the main scene when it is safe to do so
- wi::jobsystem::context generation_workload;
- std::atomic_bool generation_cancelled;
- float generation_time_budget_milliseconds = 12; // after this much time, the generation thread will exit. This can help avoid a very long running, resource consuming and slow cancellation generation
-
- // Virtual texture updates will be batched like:
- // 1) Execute all barriers (dst: UNORDERED_ACCESS)
- // 2) Execute all compute shaders
- // 3) Execute all barriers (dst: SHADER_RESOURCE)
- wi::vector virtual_texture_updates;
- wi::vector virtual_texture_barriers_begin;
- wi::vector virtual_texture_barriers_end;
-
- wi::gui::CheckBox centerToCamCheckBox;
- wi::gui::CheckBox removalCheckBox;
- wi::gui::CheckBox grassCheckBox;
- wi::gui::Slider lodSlider;
- wi::gui::Slider texlodSlider;
- wi::gui::Slider generationSlider;
- wi::gui::Slider propSlider;
- wi::gui::Slider propDensitySlider;
- wi::gui::Slider grassDensitySlider;
- wi::gui::ComboBox presetCombo;
- wi::gui::Slider scaleSlider;
- wi::gui::Slider seedSlider;
- wi::gui::Slider bottomLevelSlider;
- wi::gui::Slider topLevelSlider;
- wi::gui::Button saveHeightmapButton;
- wi::gui::ComboBox addModifierCombo;
-
- wi::gui::Slider region1Slider;
- wi::gui::Slider region2Slider;
- wi::gui::Slider region3Slider;
-
- struct Modifier : public wi::gui::Window
- {
- wi::gui::ComboBox blendCombo;
- wi::gui::Slider blendSlider;
- wi::gui::Slider frequencySlider;
-
- enum class BlendMode
- {
- Normal,
- Multiply,
- Additive,
- };
-
- Modifier(const std::string& name)
- {
- wi::gui::Window::Create(name, wi::gui::Window::WindowControls::CLOSE_AND_COLLAPSE);
- SetSize(XMFLOAT2(200, 100));
-
- blendCombo.Create("Blend Mode: ");
- blendCombo.SetSize(XMFLOAT2(100, 20));
- blendCombo.AddItem("Normal", (uint64_t)BlendMode::Normal);
- blendCombo.AddItem("Additive", (uint64_t)BlendMode::Additive);
- blendCombo.AddItem("Multiply", (uint64_t)BlendMode::Multiply);
- AddWidget(&blendCombo);
-
- blendSlider.Create(0, 1, 0.5f, 10000, "Blend: ");
- blendSlider.SetSize(XMFLOAT2(100, 20));
- blendSlider.SetTooltip("Blending amount");
- AddWidget(&blendSlider);
-
- frequencySlider.Create(0.0001f, 0.01f, 0.0008f, 10000, "Frequency: ");
- frequencySlider.SetSize(XMFLOAT2(100, 20));
- frequencySlider.SetTooltip("Frequency for the tiling");
- AddWidget(&frequencySlider);
- }
-
- virtual void Seed(uint32_t seed) {}
- virtual void SetCallback(std::function func)
- {
- blendCombo.OnSelect(func);
- blendSlider.OnSlide(func);
- frequencySlider.OnSlide(func);
- }
- virtual void Apply(const XMFLOAT2& world_pos, float& height) = 0;
- void Blend(float& height, float value)
- {
- switch ((BlendMode)blendCombo.GetItemUserData(blendCombo.GetSelected()))
- {
- default:
- case BlendMode::Normal:
- height = wi::math::Lerp(height, value, blendSlider.GetValue());
- break;
- case BlendMode::Multiply:
- height *= value * blendSlider.GetValue();
- break;
- case BlendMode::Additive:
- height += value * blendSlider.GetValue();
- break;
- }
- }
- };
- wi::vector> modifiers;
- wi::vector modifiers_to_remove;
- void AddModifier(Modifier* modifier);
-
- void Create();
-
- // This needs to be called at least once before using the terrain generator
- void init();
-
- // Restarts the terrain generation from scratch
- // This will remove previously existing terrain
- void Generation_Restart();
- // This will run the actual generation tasks, call it once per frame
- void Generation_Update(const wi::scene::CameraComponent& camera);
- // Tells the generation thread that it should be cancelled and blocks until that is confirmed
- void Generation_Cancel();
- // The virtual textures will be compressed and saved into resources. They can be serialized from there
- void BakeVirtualTexturesToFiles();
-
- void ResizeLayout() override;
-};
diff --git a/Editor/TerrainWindow.cpp b/Editor/TerrainWindow.cpp
new file mode 100644
index 000000000..d7a2f7ffa
--- /dev/null
+++ b/Editor/TerrainWindow.cpp
@@ -0,0 +1,1012 @@
+#include "stdafx.h"
+#include "TerrainWindow.h"
+#include "Editor.h"
+
+#include "Utility/stb_image.h"
+
+using namespace wi::ecs;
+using namespace wi::scene;
+
+ModifierWindow::ModifierWindow(const std::string& name)
+{
+ wi::gui::Window::Create(name, wi::gui::Window::WindowControls::CLOSE_AND_COLLAPSE);
+ SetSize(XMFLOAT2(200, 100));
+
+ blendCombo.Create("Blend: ");
+ blendCombo.SetSize(XMFLOAT2(100, 20));
+ blendCombo.AddItem("Normal", (uint64_t)wi::terrain::Modifier::BlendMode::Normal);
+ blendCombo.AddItem("Additive", (uint64_t)wi::terrain::Modifier::BlendMode::Additive);
+ blendCombo.AddItem("Multiply", (uint64_t)wi::terrain::Modifier::BlendMode::Multiply);
+ blendCombo.OnSelect([=](wi::gui::EventArgs args) {
+ modifier->blend = (wi::terrain::Modifier::BlendMode)args.userdata;
+ generation_callback();
+ });
+ AddWidget(&blendCombo);
+
+ weightSlider.Create(0, 1, 0.5f, 10000, "Weight: ");
+ weightSlider.SetSize(XMFLOAT2(100, 20));
+ weightSlider.SetTooltip("Blending amount");
+ weightSlider.OnSlide([=](wi::gui::EventArgs args) {
+ modifier->weight = args.fValue;
+ generation_callback();
+ });
+ AddWidget(&weightSlider);
+
+ frequencySlider.Create(0.0001f, 0.01f, 0.0008f, 10000, "Frequency: ");
+ frequencySlider.SetSize(XMFLOAT2(100, 20));
+ frequencySlider.SetTooltip("Frequency for the tiling");
+ frequencySlider.OnSlide([=](wi::gui::EventArgs args) {
+ modifier->frequency = args.fValue;
+ generation_callback();
+ });
+ AddWidget(&frequencySlider);
+}
+void ModifierWindow::Bind(wi::terrain::Modifier* ptr)
+{
+ modifier = ptr;
+ modifier->blend = (wi::terrain::Modifier::BlendMode)blendCombo.GetItemUserData(blendCombo.GetSelected());
+ modifier->weight = weightSlider.GetValue();
+ modifier->frequency = frequencySlider.GetValue();
+}
+void ModifierWindow::From(wi::terrain::Modifier* ptr)
+{
+ modifier = ptr;
+ blendCombo.SetSelectedByUserdataWithoutCallback((uint64_t)ptr->blend);
+ weightSlider.SetValue(ptr->weight);
+ frequencySlider.SetValue(ptr->frequency);
+}
+
+PerlinModifierWindow::PerlinModifierWindow() : ModifierWindow("Perlin Noise")
+{
+ octavesSlider.Create(1, 8, 6, 7, "Octaves: ");
+ octavesSlider.SetTooltip("Octave count for the perlin noise");
+ octavesSlider.SetSize(XMFLOAT2(100, 20));
+ octavesSlider.OnSlide([=](wi::gui::EventArgs args) {
+ ((wi::terrain::PerlinModifier*)modifier)->octaves = args.iValue;
+ generation_callback();
+ });
+ AddWidget(&octavesSlider);
+
+ SetSize(XMFLOAT2(200, 140));
+}
+void PerlinModifierWindow::ResizeLayout()
+{
+ ModifierWindow::ResizeLayout();
+ const float padding = 4;
+ const float width = GetWidgetAreaSize().x;
+ float y = padding;
+
+ auto add = [&](wi::gui::Widget& widget) {
+ const float margin_left = 100;
+ const float margin_right = 50;
+ widget.SetPos(XMFLOAT2(margin_left, y));
+ widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
+ y += widget.GetSize().y;
+ y += padding;
+ };
+
+ add(blendCombo);
+ add(weightSlider);
+ add(frequencySlider);
+
+ add(octavesSlider);
+}
+void PerlinModifierWindow::Bind(wi::terrain::PerlinModifier* ptr)
+{
+ ModifierWindow::Bind(ptr);
+ ptr->octaves = (int)octavesSlider.GetValue();
+}
+void PerlinModifierWindow::From(wi::terrain::PerlinModifier* ptr)
+{
+ ModifierWindow::From(ptr);
+ octavesSlider.SetValue((float)ptr->octaves);
+}
+
+VoronoiModifierWindow::VoronoiModifierWindow() : ModifierWindow("Voronoi Noise")
+{
+ fadeSlider.Create(0, 100, 2.59f, 10000, "Fade: ");
+ fadeSlider.SetTooltip("Fade out voronoi regions by distance from cell's center");
+ fadeSlider.SetSize(XMFLOAT2(100, 20));
+ fadeSlider.OnSlide([=](wi::gui::EventArgs args) {
+ ((wi::terrain::VoronoiModifier*)modifier)->fade = args.fValue;
+ generation_callback();
+ });
+ AddWidget(&fadeSlider);
+
+ shapeSlider.Create(0, 1, 0.7f, 10000, "Shape: ");
+ shapeSlider.SetTooltip("How much the voronoi shape will be kept");
+ shapeSlider.SetSize(XMFLOAT2(100, 20));
+ shapeSlider.OnSlide([=](wi::gui::EventArgs args) {
+ ((wi::terrain::VoronoiModifier*)modifier)->shape = args.fValue;
+ generation_callback();
+ });
+ AddWidget(&shapeSlider);
+
+ falloffSlider.Create(0, 8, 6, 10000, "Falloff: ");
+ falloffSlider.SetTooltip("Controls the falloff of the voronoi distance fade effect");
+ falloffSlider.SetSize(XMFLOAT2(100, 20));
+ falloffSlider.OnSlide([=](wi::gui::EventArgs args) {
+ ((wi::terrain::VoronoiModifier*)modifier)->falloff = args.fValue;
+ generation_callback();
+ });
+ AddWidget(&falloffSlider);
+
+ perturbationSlider.Create(0, 1, 0.1f, 10000, "Perturbation: ");
+ perturbationSlider.SetTooltip("Controls the random look of voronoi region edges");
+ perturbationSlider.SetSize(XMFLOAT2(100, 20));
+ perturbationSlider.OnSlide([=](wi::gui::EventArgs args) {
+ ((wi::terrain::VoronoiModifier*)modifier)->perturbation = args.fValue;
+ generation_callback();
+ });
+ AddWidget(&perturbationSlider);
+
+ SetSize(XMFLOAT2(200, 200));
+}
+void VoronoiModifierWindow::ResizeLayout()
+{
+ ModifierWindow::ResizeLayout();
+ const float padding = 4;
+ const float width = GetWidgetAreaSize().x;
+ float y = padding;
+
+ auto add = [&](wi::gui::Widget& widget) {
+ const float margin_left = 100;
+ const float margin_right = 50;
+ widget.SetPos(XMFLOAT2(margin_left, y));
+ widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
+ y += widget.GetSize().y;
+ y += padding;
+ };
+
+ add(blendCombo);
+ add(weightSlider);
+ add(frequencySlider);
+
+ add(fadeSlider);
+ add(shapeSlider);
+ add(falloffSlider);
+ add(perturbationSlider);
+}
+void VoronoiModifierWindow::Bind(wi::terrain::VoronoiModifier* ptr)
+{
+ ModifierWindow::Bind(ptr);
+ ptr->fade = fadeSlider.GetValue();
+ ptr->shape = shapeSlider.GetValue();
+ ptr->falloff = falloffSlider.GetValue();
+ ptr->perturbation = perturbationSlider.GetValue();
+}
+void VoronoiModifierWindow::From(wi::terrain::VoronoiModifier* ptr)
+{
+ ModifierWindow::From(ptr);
+ fadeSlider.SetValue(ptr->fade);
+ shapeSlider.SetValue(ptr->shape);
+ falloffSlider.SetValue(ptr->falloff);
+ perturbationSlider.SetValue(ptr->perturbation);
+}
+
+HeightmapModifierWindow::HeightmapModifierWindow() : ModifierWindow("Heightmap")
+{
+ weightSlider.SetValue(1);
+ frequencySlider.SetValue(1);
+
+ scaleSlider.Create(0, 1, 0.1f, 1000, "Scale: ");
+ scaleSlider.SetSize(XMFLOAT2(100, 20));
+ scaleSlider.OnSlide([=](wi::gui::EventArgs args) {
+ ((wi::terrain::HeightmapModifier*)modifier)->scale = args.fValue;
+ generation_callback();
+ });
+ AddWidget(&scaleSlider);
+
+ loadButton.Create("Load Heightmap...");
+ loadButton.SetTooltip("Load a heightmap texture, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be placed in the world center.");
+ loadButton.OnClick([=](wi::gui::EventArgs args) {
+
+ wi::helper::FileDialogParams params;
+ params.type = wi::helper::FileDialogParams::OPEN;
+ params.description = "Texture";
+ params.extensions = wi::resourcemanager::GetSupportedImageExtensions();
+ wi::helper::FileDialog(params, [=](std::string fileName) {
+ wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) {
+
+ auto* heightmap_modifier = (wi::terrain::HeightmapModifier*)modifier;
+ heightmap_modifier->data.clear();
+ heightmap_modifier->width = 0;
+ heightmap_modifier->height = 0;
+ int bpp = 0;
+ stbi_uc* rgba = stbi_load(fileName.c_str(), &heightmap_modifier->width, &heightmap_modifier->height, &bpp, 1);
+ if (rgba != nullptr)
+ {
+ heightmap_modifier->data.resize(heightmap_modifier->width * heightmap_modifier->height);
+ for (int i = 0; i < heightmap_modifier->width * heightmap_modifier->height; ++i)
+ {
+ heightmap_modifier->data[i] = rgba[i];
+ }
+ stbi_image_free(rgba);
+
+ generation_callback(); // callback after heightmap load confirmation
+ }
+ });
+ });
+ });
+ AddWidget(&loadButton);
+
+ SetSize(XMFLOAT2(200, 180));
+}
+void HeightmapModifierWindow::ResizeLayout()
+{
+ ModifierWindow::ResizeLayout();
+ const float padding = 4;
+ const float width = GetWidgetAreaSize().x;
+ float y = padding;
+
+ auto add = [&](wi::gui::Widget& widget) {
+ const float margin_left = 100;
+ const float margin_right = 50;
+ widget.SetPos(XMFLOAT2(margin_left, y));
+ widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
+ y += widget.GetSize().y;
+ y += padding;
+ };
+
+ add(blendCombo);
+ add(weightSlider);
+ add(frequencySlider);
+
+ add(scaleSlider);
+ add(loadButton);
+}
+void HeightmapModifierWindow::Bind(wi::terrain::HeightmapModifier* ptr)
+{
+ ModifierWindow::Bind(ptr);
+ ptr->scale = scaleSlider.GetValue();
+}
+void HeightmapModifierWindow::From(wi::terrain::HeightmapModifier* ptr)
+{
+ ModifierWindow::From(ptr);
+ scaleSlider.SetValue(ptr->scale);
+}
+
+
+void TerrainWindow::Create(EditorComponent* _editor)
+{
+ editor = _editor;
+ RemoveWidgets();
+ ClearTransform();
+
+ wi::gui::Window::Create(ICON_TERRAIN " Terrain", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE);
+ SetSize(XMFLOAT2(420, 860));
+
+ closeButton.SetTooltip("Delete Terrain.\nThis will bake generated virtual textures to static textures which could take a while!");
+ OnClose([=](wi::gui::EventArgs args) {
+
+ wi::Archive& archive = editor->AdvanceHistory();
+ archive << EditorComponent::HISTORYOP_COMPONENT_DATA;
+ editor->RecordEntity(archive, entity);
+
+ editor->GetCurrentScene().terrains.Remove(entity);
+
+ editor->RecordEntity(archive, entity);
+
+ editor->optionsWnd.RefreshEntityTree();
+ });
+
+ float x = 140;
+ float y = 0;
+ float step = 25;
+ float hei = 20;
+ float wid = 120;
+
+ centerToCamCheckBox.Create("Center to Cam: ");
+ centerToCamCheckBox.SetTooltip("Automatically generate chunks around camera. This sets the center chunk to camera position.");
+ centerToCamCheckBox.SetSize(XMFLOAT2(hei, hei));
+ centerToCamCheckBox.SetPos(XMFLOAT2(x, y));
+ centerToCamCheckBox.SetCheck(true);
+ centerToCamCheckBox.OnClick([=](wi::gui::EventArgs args) {
+ terrain->SetCenterToCamEnabled(args.bValue);
+ });
+ AddWidget(¢erToCamCheckBox);
+
+ removalCheckBox.Create("Removal: ");
+ removalCheckBox.SetTooltip("Automatically remove chunks that are farther than generation distance around center chunk.");
+ removalCheckBox.SetSize(XMFLOAT2(hei, hei));
+ removalCheckBox.SetPos(XMFLOAT2(x + 100, y));
+ removalCheckBox.SetCheck(true);
+ removalCheckBox.OnClick([=](wi::gui::EventArgs args) {
+ terrain->SetRemovalEnabled(args.bValue);
+ });
+ AddWidget(&removalCheckBox);
+
+ grassCheckBox.Create("Grass: ");
+ grassCheckBox.SetTooltip("Specify whether grass generation is enabled.");
+ grassCheckBox.SetSize(XMFLOAT2(hei, hei));
+ grassCheckBox.SetPos(XMFLOAT2(x, y += step));
+ grassCheckBox.SetCheck(true);
+ grassCheckBox.OnClick([=](wi::gui::EventArgs args) {
+ terrain->SetGrassEnabled(args.bValue);
+ });
+ AddWidget(&grassCheckBox);
+
+ lodSlider.Create(0.0001f, 0.01f, 0.005f, 10000, "Mesh LOD Distance: ");
+ lodSlider.SetTooltip("Set the LOD (Level Of Detail) distance multiplier.\nLow values increase LOD detail in distance");
+ lodSlider.SetSize(XMFLOAT2(wid, hei));
+ lodSlider.SetPos(XMFLOAT2(x, y += step));
+ lodSlider.OnSlide([this](wi::gui::EventArgs args) {
+ for (auto& it : terrain->chunks)
+ {
+ const wi::terrain::ChunkData& chunk_data = it.second;
+ if (chunk_data.entity != INVALID_ENTITY)
+ {
+ ObjectComponent* object = terrain->scene->objects.GetComponent(chunk_data.entity);
+ if (object != nullptr)
+ {
+ object->lod_distance_multiplier = args.fValue;
+ }
+ }
+ }
+ terrain->lod_multiplier = args.fValue;
+ });
+ AddWidget(&lodSlider);
+
+ texlodSlider.Create(0.001f, 0.05f, 0.01f, 10000, "Tex LOD Distance: ");
+ texlodSlider.SetTooltip("Set the LOD (Level Of Detail) distance multiplier for virtual textures.\nLow values increase LOD detail in distance");
+ texlodSlider.SetSize(XMFLOAT2(wid, hei));
+ texlodSlider.SetPos(XMFLOAT2(x, y += step));
+ texlodSlider.OnSlide([this](wi::gui::EventArgs args) {
+ terrain->texlod = args.fValue;
+ });
+ AddWidget(&texlodSlider);
+
+ generationSlider.Create(0, 16, 12, 16, "Generation Distance: ");
+ generationSlider.SetTooltip("How far out chunks will be generated (value is in number of chunks)");
+ generationSlider.SetSize(XMFLOAT2(wid, hei));
+ generationSlider.SetPos(XMFLOAT2(x, y += step));
+ generationSlider.OnSlide([this](wi::gui::EventArgs args) {
+ terrain->generation = args.iValue;
+ });
+ AddWidget(&generationSlider);
+
+ propSlider.Create(0, 16, 10, 16, "Prop Distance: ");
+ propSlider.SetTooltip("How far out props will be generated (value is in number of chunks)");
+ propSlider.SetSize(XMFLOAT2(wid, hei));
+ propSlider.SetPos(XMFLOAT2(x, y += step));
+ propSlider.OnSlide([this](wi::gui::EventArgs args) {
+ terrain->prop_generation = args.iValue;
+ });
+ AddWidget(&propSlider);
+
+ propDensitySlider.Create(0, 10, 1, 1000, "Prop Density: ");
+ propDensitySlider.SetTooltip("Modifies overall prop density.");
+ propDensitySlider.SetSize(XMFLOAT2(wid, hei));
+ propDensitySlider.SetPos(XMFLOAT2(x, y += step));
+ propDensitySlider.OnSlide([this](wi::gui::EventArgs args) {
+ terrain->prop_density = args.fValue;
+ });
+ AddWidget(&propDensitySlider);
+
+ grassDensitySlider.Create(0, 4, 1, 1000, "Grass Density: ");
+ grassDensitySlider.SetTooltip("Modifies overall grass density.");
+ grassDensitySlider.SetSize(XMFLOAT2(wid, hei));
+ grassDensitySlider.SetPos(XMFLOAT2(x, y += step));
+ grassDensitySlider.OnSlide([this](wi::gui::EventArgs args) {
+ terrain->grass_density = args.fValue;
+ });
+ AddWidget(&grassDensitySlider);
+
+ auto generate_callback = [&]() {
+
+ terrain->SetCenterToCamEnabled(centerToCamCheckBox.GetCheck());
+ terrain->SetRemovalEnabled(removalCheckBox.GetCheck());
+ terrain->SetGrassEnabled(grassCheckBox.GetCheck());
+ terrain->lod_multiplier = lodSlider.GetValue();
+ terrain->texlod = texlodSlider.GetValue();
+ terrain->generation = (int)generationSlider.GetValue();
+ terrain->prop_generation = (int)propSlider.GetValue();
+ terrain->prop_density = propDensitySlider.GetValue();
+ terrain->grass_density = grassDensitySlider.GetValue();
+ terrain->chunk_scale = scaleSlider.GetValue();
+ terrain->seed = (uint32_t)seedSlider.GetValue();
+ terrain->bottomLevel = bottomLevelSlider.GetValue();
+ terrain->topLevel = topLevelSlider.GetValue();
+ terrain->region1 = region1Slider.GetValue();
+ terrain->region2 = region2Slider.GetValue();
+ terrain->region3 = region3Slider.GetValue();
+
+ terrain->SetGenerationStarted(false);
+ };
+
+ presetCombo.Create("Preset: ");
+ presetCombo.SetTooltip("Select a terrain preset");
+ presetCombo.SetSize(XMFLOAT2(wid, hei));
+ presetCombo.SetPos(XMFLOAT2(x, y += step));
+ presetCombo.AddItem("Hills", PRESET_HILLS);
+ presetCombo.AddItem("Islands", PRESET_ISLANDS);
+ presetCombo.AddItem("Mountains", PRESET_MOUNTAINS);
+ presetCombo.AddItem("Arctic", PRESET_ARCTIC);
+ presetCombo.OnSelect([=](wi::gui::EventArgs args) {
+
+ terrain->Generation_Cancel();
+ for (auto& modifier : modifiers)
+ {
+ RemoveWidget(modifier.get());
+ }
+ modifiers.clear();
+ modifiers_to_remove.clear();
+ terrain->modifiers.clear();
+ terrain->modifiers_to_remove.clear();
+ PerlinModifierWindow* perlin = new PerlinModifierWindow;
+ VoronoiModifierWindow* voronoi = new VoronoiModifierWindow;
+ perlin->blendCombo.SetSelectedByUserdataWithoutCallback((uint64_t)wi::terrain::Modifier::BlendMode::Additive);
+ voronoi->blendCombo.SetSelectedByUserdataWithoutCallback((uint64_t)wi::terrain::Modifier::BlendMode::Multiply);
+
+ switch (args.userdata)
+ {
+ default:
+ case PRESET_HILLS:
+ terrain->weather.SetOceanEnabled(false);
+ seedSlider.SetValue(5333);
+ bottomLevelSlider.SetValue(-60);
+ topLevelSlider.SetValue(380);
+ perlin->weightSlider.SetValue(0.5f);
+ perlin->frequencySlider.SetValue(0.0008f);
+ perlin->octavesSlider.SetValue(6);
+ voronoi->weightSlider.SetValue(0.5f);
+ voronoi->frequencySlider.SetValue(0.001f);
+ voronoi->fadeSlider.SetValue(2.59f);
+ voronoi->shapeSlider.SetValue(0.7f);
+ voronoi->falloffSlider.SetValue(6);
+ voronoi->perturbationSlider.SetValue(0.1f);
+ region1Slider.SetValue(1);
+ region2Slider.SetValue(2);
+ region3Slider.SetValue(8);
+ break;
+ case PRESET_ISLANDS:
+ terrain->weather.SetOceanEnabled(true);
+ seedSlider.SetValue(4691);
+ bottomLevelSlider.SetValue(-79);
+ topLevelSlider.SetValue(520);
+ perlin->weightSlider.SetValue(0.5f);
+ perlin->frequencySlider.SetValue(0.000991f);
+ perlin->octavesSlider.SetValue(6);
+ voronoi->weightSlider.SetValue(0.5f);
+ voronoi->frequencySlider.SetValue(0.000317f);
+ voronoi->fadeSlider.SetValue(8.2f);
+ voronoi->shapeSlider.SetValue(0.126f);
+ voronoi->falloffSlider.SetValue(1.392f);
+ voronoi->perturbationSlider.SetValue(0.126f);
+ region1Slider.SetValue(8);
+ region2Slider.SetValue(0.7f);
+ region3Slider.SetValue(8);
+ break;
+ case PRESET_MOUNTAINS:
+ terrain->weather.SetOceanEnabled(false);
+ seedSlider.SetValue(8863);
+ bottomLevelSlider.SetValue(0);
+ topLevelSlider.SetValue(2960);
+ perlin->weightSlider.SetValue(0.5f);
+ perlin->frequencySlider.SetValue(0.00279f);
+ perlin->octavesSlider.SetValue(8);
+ voronoi->weightSlider.SetValue(0.5f);
+ voronoi->frequencySlider.SetValue(0.000496f);
+ voronoi->fadeSlider.SetValue(5.2f);
+ voronoi->shapeSlider.SetValue(0.412f);
+ voronoi->falloffSlider.SetValue(1.456f);
+ voronoi->perturbationSlider.SetValue(0.092f);
+ region1Slider.SetValue(1);
+ region2Slider.SetValue(1);
+ region3Slider.SetValue(0.8f);
+ break;
+ case PRESET_ARCTIC:
+ terrain->weather.SetOceanEnabled(false);
+ seedSlider.SetValue(2124);
+ bottomLevelSlider.SetValue(-50);
+ topLevelSlider.SetValue(40);
+ perlin->weightSlider.SetValue(1);
+ perlin->frequencySlider.SetValue(0.002f);
+ perlin->octavesSlider.SetValue(4);
+ voronoi->weightSlider.SetValue(1);
+ voronoi->frequencySlider.SetValue(0.004f);
+ voronoi->fadeSlider.SetValue(1.8f);
+ voronoi->shapeSlider.SetValue(0.518f);
+ voronoi->falloffSlider.SetValue(0.2f);
+ voronoi->perturbationSlider.SetValue(0.298f);
+ region1Slider.SetValue(8);
+ region2Slider.SetValue(8);
+ region3Slider.SetValue(0);
+ break;
+ }
+
+ std::shared_ptr terrain_perlin = std::make_shared();
+ terrain->modifiers.emplace_back() = terrain_perlin;
+ std::shared_ptr terrain_voronoi = std::make_shared();
+ terrain->modifiers.emplace_back() = terrain_voronoi;
+
+ perlin->Bind(terrain_perlin.get());
+ voronoi->Bind(terrain_voronoi.get());
+ AddModifier(perlin);
+ AddModifier(voronoi);
+
+ generate_callback();
+
+ editor->optionsWnd.RefreshEntityTree();
+
+ });
+ AddWidget(&presetCombo);
+
+
+ addModifierCombo.Create("Add Modifier: ");
+ addModifierCombo.selected_font.anim.typewriter.looped = true;
+ addModifierCombo.selected_font.anim.typewriter.time = 2;
+ addModifierCombo.selected_font.anim.typewriter.character_start = 1;
+ addModifierCombo.SetTooltip("Add a new modifier that will affect terrain generation.");
+ addModifierCombo.SetSize(XMFLOAT2(wid, hei));
+ addModifierCombo.SetPos(XMFLOAT2(x, y += step));
+ addModifierCombo.SetInvalidSelectionText("...");
+ addModifierCombo.AddItem("Perlin Noise");
+ addModifierCombo.AddItem("Voronoi Noise");
+ addModifierCombo.AddItem("Heightmap Image");
+ addModifierCombo.OnSelect([=](wi::gui::EventArgs args) {
+
+ addModifierCombo.SetSelectedWithoutCallback(-1);
+ terrain->Generation_Cancel();
+ switch (args.iValue)
+ {
+ default:
+ break;
+ case 0:
+ {
+ PerlinModifierWindow* ptr = new PerlinModifierWindow;
+ std::shared_ptr modifier = std::make_shared();
+ terrain->modifiers.push_back(modifier);
+ ptr->Bind(modifier.get());
+ AddModifier(ptr);
+ }
+ break;
+ case 1:
+ {
+ VoronoiModifierWindow* ptr = new VoronoiModifierWindow;
+ std::shared_ptr modifier = std::make_shared();
+ terrain->modifiers.push_back(modifier);
+ ptr->Bind(modifier.get());
+ AddModifier(ptr);
+ }
+ break;
+ case 2:
+ {
+ HeightmapModifierWindow* ptr = new HeightmapModifierWindow;
+ std::shared_ptr modifier = std::make_shared();
+ terrain->modifiers.push_back(modifier);
+ ptr->Bind(modifier.get());
+ AddModifier(ptr);
+ }
+ break;
+ }
+ generate_callback();
+
+ });
+ AddWidget(&addModifierCombo);
+
+ scaleSlider.Create(1, 10, 1, 9, "Chunk Scale: ");
+ scaleSlider.SetTooltip("Size of one chunk in horizontal directions.\nLarger chunk scale will cover larger distance, but will have less detail per unit.");
+ scaleSlider.SetSize(XMFLOAT2(wid, hei));
+ scaleSlider.SetPos(XMFLOAT2(x, y += step));
+ scaleSlider.OnSlide([=](wi::gui::EventArgs args) {
+ terrain->chunk_scale = args.fValue;
+ generate_callback();
+ });
+ AddWidget(&scaleSlider);
+
+ seedSlider.Create(1, 12345, 3926, 12344, "Seed: ");
+ seedSlider.SetTooltip("Seed for terrain randomness");
+ seedSlider.SetSize(XMFLOAT2(wid, hei));
+ seedSlider.SetPos(XMFLOAT2(x, y += step));
+ seedSlider.OnSlide([=](wi::gui::EventArgs args) {
+ terrain->seed = (uint32_t)args.iValue;
+ generate_callback();
+ });
+ AddWidget(&seedSlider);
+
+ bottomLevelSlider.Create(-100, 0, -60, 10000, "Bottom Level: ");
+ bottomLevelSlider.SetTooltip("Terrain mesh grid lowest level");
+ bottomLevelSlider.SetSize(XMFLOAT2(wid, hei));
+ bottomLevelSlider.SetPos(XMFLOAT2(x, y += step));
+ bottomLevelSlider.OnSlide([=](wi::gui::EventArgs args) {
+ terrain->bottomLevel = args.fValue;
+ generate_callback();
+ });
+ AddWidget(&bottomLevelSlider);
+
+ topLevelSlider.Create(0, 5000, 380, 10000, "Top Level: ");
+ topLevelSlider.SetTooltip("Terrain mesh grid topmost level");
+ topLevelSlider.SetSize(XMFLOAT2(wid, hei));
+ topLevelSlider.SetPos(XMFLOAT2(x, y += step));
+ topLevelSlider.OnSlide([=](wi::gui::EventArgs args) {
+ terrain->topLevel = args.fValue;
+ generate_callback();
+ });
+ AddWidget(&topLevelSlider);
+
+ saveHeightmapButton.Create("Save Heightmap...");
+ saveHeightmapButton.SetTooltip("Save a heightmap texture from the currently generated terrain, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be normalized into 8bit PNG format which can result in precision loss!");
+ saveHeightmapButton.SetSize(XMFLOAT2(wid, hei));
+ saveHeightmapButton.SetPos(XMFLOAT2(x, y += step));
+ AddWidget(&saveHeightmapButton);
+
+ region1Slider.Create(0, 8, 1, 10000, "Slope Region: ");
+ region1Slider.SetTooltip("The region's falloff power");
+ region1Slider.SetSize(XMFLOAT2(wid, hei));
+ region1Slider.SetPos(XMFLOAT2(x, y += step));
+ region1Slider.OnSlide([=](wi::gui::EventArgs args) {
+ terrain->region1 = args.fValue;
+ generate_callback();
+ });
+ AddWidget(®ion1Slider);
+
+ region2Slider.Create(0, 8, 2, 10000, "Low Altitude Region: ");
+ region2Slider.SetTooltip("The region's falloff power");
+ region2Slider.SetSize(XMFLOAT2(wid, hei));
+ region2Slider.SetPos(XMFLOAT2(x, y += step));
+ region2Slider.OnSlide([=](wi::gui::EventArgs args) {
+ terrain->region2 = args.fValue;
+ generate_callback();
+ });
+ AddWidget(®ion2Slider);
+
+ region3Slider.Create(0, 8, 8, 10000, "High Altitude Region: ");
+ region3Slider.SetTooltip("The region's falloff power");
+ region3Slider.SetSize(XMFLOAT2(wid, hei));
+ region3Slider.SetPos(XMFLOAT2(x, y += step));
+ region3Slider.OnSlide([=](wi::gui::EventArgs args) {
+ terrain->region3 = args.fValue;
+ generate_callback();
+ });
+ AddWidget(®ion3Slider);
+
+ saveHeightmapButton.OnClick([=](wi::gui::EventArgs args) {
+
+ wi::helper::FileDialogParams params;
+ params.type = wi::helper::FileDialogParams::SAVE;
+ params.description = "PNG";
+ params.extensions = { "PNG" };
+ wi::helper::FileDialog(params, [=](std::string fileName) {
+ wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) {
+
+ wi::primitive::AABB aabb;
+ for (auto& chunk : terrain->chunks)
+ {
+ const wi::primitive::AABB* object_aabb = terrain->scene->aabb_objects.GetComponent(chunk.second.entity);
+ if (object_aabb != nullptr)
+ {
+ aabb = wi::primitive::AABB::Merge(aabb, *object_aabb);
+ }
+ }
+
+ wi::vector data;
+ int width = int(aabb.getHalfWidth().x * 2 + 1);
+ int height = int(aabb.getHalfWidth().z * 2 + 1);
+ data.resize(width * height);
+ std::fill(data.begin(), data.end(), 0u);
+
+ for (auto& chunk : terrain->chunks)
+ {
+ const ObjectComponent* object = terrain->scene->objects.GetComponent(chunk.second.entity);
+ if (object != nullptr)
+ {
+ const MeshComponent* mesh = terrain->scene->meshes.GetComponent(object->meshID);
+ if (mesh != nullptr)
+ {
+ const XMMATRIX W = XMLoadFloat4x4(&object->worldMatrix);
+ for (auto& x : mesh->vertex_positions)
+ {
+ XMVECTOR P = XMLoadFloat3(&x);
+ P = XMVector3Transform(P, W);
+ XMFLOAT3 p;
+ XMStoreFloat3(&p, P);
+ p.x -= aabb._min.x;
+ p.z -= aabb._min.z;
+ int coord = int(p.x) + int(p.z) * width;
+ data[coord] = uint8_t(wi::math::InverseLerp(aabb._min.y, aabb._max.y, p.y) * 255u);
+ }
+ }
+ }
+ }
+
+ wi::graphics::TextureDesc desc;
+ desc.width = uint32_t(width);
+ desc.height = uint32_t(height);
+ desc.format = wi::graphics::Format::R8_UNORM;
+ bool success = wi::helper::saveTextureToFile(data, desc, wi::helper::ReplaceExtension(fileName, "PNG"));
+ assert(success);
+
+ });
+ });
+ });
+
+ SetCollapsed(true);
+}
+void TerrainWindow::SetEntity(Entity entity)
+{
+ wi::scene::Scene& scene = editor->GetCurrentScene();
+ terrain = scene.terrains.GetComponent(entity);
+ if (terrain == nullptr)
+ {
+ entity = INVALID_ENTITY;
+ terrain = &terrain_preset;
+ }
+
+ if (this->entity == entity)
+ return;
+
+ this->entity = entity;
+
+ for (auto& x : modifiers)
+ {
+ modifiers_to_remove.push_back(x.get());
+ }
+
+
+ centerToCamCheckBox.SetCheck(terrain->IsCenterToCamEnabled());
+ removalCheckBox.SetCheck(terrain->IsRemovalEnabled());
+ grassCheckBox.SetCheck(terrain->IsGrassEnabled());
+ lodSlider.SetValue(terrain->lod_multiplier);
+ texlodSlider.SetValue(terrain->texlod);
+ generationSlider.SetValue((float)terrain->generation);
+ propSlider.SetValue((float)terrain->prop_generation);
+ propDensitySlider.SetValue(terrain->prop_density);
+ scaleSlider.SetValue(terrain->chunk_scale);
+ seedSlider.SetValue((float)terrain->seed);
+ bottomLevelSlider.SetValue(terrain->bottomLevel);
+ topLevelSlider.SetValue(terrain->topLevel);
+ region1Slider.SetValue(terrain->region1);
+ region2Slider.SetValue(terrain->region2);
+ region3Slider.SetValue(terrain->region3);
+
+ for (auto& x : terrain->modifiers)
+ {
+ switch (x->type)
+ {
+ default:
+ case wi::terrain::Modifier::Type::Perlin:
+ {
+ PerlinModifierWindow* modifier = new PerlinModifierWindow;
+ modifier->From((wi::terrain::PerlinModifier*)x.get());
+ AddModifier(modifier);
+ }
+ break;
+ case wi::terrain::Modifier::Type::Voronoi:
+ {
+ VoronoiModifierWindow* modifier = new VoronoiModifierWindow;
+ modifier->From((wi::terrain::VoronoiModifier*)x.get());
+ AddModifier(modifier);
+ }
+ break;
+ case wi::terrain::Modifier::Type::Heightmap:
+ {
+ HeightmapModifierWindow* modifier = new HeightmapModifierWindow;
+ modifier->From((wi::terrain::HeightmapModifier*)x.get());
+ AddModifier(modifier);
+ }
+ break;
+ }
+ }
+}
+void TerrainWindow::AddModifier(ModifierWindow* modifier_window)
+{
+ modifier_window->generation_callback = [=]() {
+ terrain->Generation_Restart();
+ };
+ modifiers.emplace_back().reset(modifier_window);
+ AddWidget(modifier_window);
+
+ modifier_window->OnClose([=](wi::gui::EventArgs args) {
+ // Can't delete modifier in itself, so add to a deferred deletion queue:
+ terrain->modifiers_to_remove.push_back(modifier_window->modifier);
+ modifiers_to_remove.push_back(modifier_window);
+ });
+
+ editor->optionsWnd.themeCombo.SetSelected(editor->optionsWnd.themeCombo.GetSelected()); // theme refresh
+}
+void TerrainWindow::SetupAssets()
+{
+ if (!terrain_preset.props.empty())
+ return;
+
+ // Customize terrain generator before it's initialized:
+ terrain_preset.material_Base.SetRoughness(1);
+ terrain_preset.material_Base.SetReflectance(0.005f);
+ terrain_preset.material_Slope.SetRoughness(0.1f);
+ terrain_preset.material_LowAltitude.SetRoughness(1);
+ terrain_preset.material_HighAltitude.SetRoughness(1);
+ terrain_preset.material_Base.textures[MaterialComponent::BASECOLORMAP].name = "terrain/base.jpg";
+ terrain_preset.material_Base.textures[MaterialComponent::NORMALMAP].name = "terrain/base_nor.jpg";
+ terrain_preset.material_Slope.textures[MaterialComponent::BASECOLORMAP].name = "terrain/slope.jpg";
+ terrain_preset.material_Slope.textures[MaterialComponent::NORMALMAP].name = "terrain/slope_nor.jpg";
+ terrain_preset.material_LowAltitude.textures[MaterialComponent::BASECOLORMAP].name = "terrain/low_altitude.jpg";
+ terrain_preset.material_LowAltitude.textures[MaterialComponent::NORMALMAP].name = "terrain/low_altitude_nor.jpg";
+ terrain_preset.material_HighAltitude.textures[MaterialComponent::BASECOLORMAP].name = "terrain/high_altitude.jpg";
+ terrain_preset.material_HighAltitude.textures[MaterialComponent::NORMALMAP].name = "terrain/high_altitude_nor.jpg";
+ terrain_preset.material_GrassParticle.textures[MaterialComponent::BASECOLORMAP].name = "terrain/grassparticle.png";
+ terrain_preset.material_GrassParticle.alphaRef = 0.75f;
+ terrain_preset.grass_properties.length = 5;
+ terrain_preset.grass_properties.frameCount = 2;
+ terrain_preset.grass_properties.framesX = 1;
+ terrain_preset.grass_properties.framesY = 2;
+ terrain_preset.grass_properties.frameStart = 0;
+ terrain_preset.material_Base.CreateRenderData();
+ terrain_preset.material_Slope.CreateRenderData();
+ terrain_preset.material_LowAltitude.CreateRenderData();
+ terrain_preset.material_HighAltitude.CreateRenderData();
+ terrain_preset.material_GrassParticle.CreateRenderData();
+ // Tree prop:
+ {
+ Scene props_scene;
+ wi::scene::LoadModel(props_scene, "terrain/tree.wiscene");
+ wi::terrain::Prop& prop = terrain_preset.props.emplace_back();
+ prop.name = "tree";
+ prop.min_count_per_chunk = 0;
+ prop.max_count_per_chunk = 10;
+ prop.region = 0;
+ prop.region_power = 2;
+ prop.noise_frequency = 0.1f;
+ prop.noise_power = 1;
+ prop.threshold = 0.4f;
+ prop.min_size = 2.0f;
+ prop.max_size = 8.0f;
+ prop.min_y_offset = -0.5f;
+ prop.max_y_offset = -0.5f;
+ prop.mesh_entity = props_scene.Entity_FindByName("tree_mesh");
+ props_scene.impostors.Create(prop.mesh_entity).swapInDistance = 200;
+ Entity object_entity = props_scene.Entity_FindByName("tree_object");
+ ObjectComponent* object = props_scene.objects.GetComponent(object_entity);
+ if (object != nullptr)
+ {
+ prop.object = *object;
+ prop.object.lod_distance_multiplier = 0.05f;
+ //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
+ editor->GetCurrentScene().Merge(props_scene);
+ }
+ // Rock prop:
+ {
+ Scene props_scene;
+ wi::scene::LoadModel(props_scene, "terrain/rock.wiscene");
+ wi::terrain::Prop& prop = terrain_preset.props.emplace_back();
+ prop.name = "rock";
+ prop.min_count_per_chunk = 0;
+ prop.max_count_per_chunk = 8;
+ prop.region = 0;
+ prop.region_power = 1;
+ prop.noise_frequency = 0.005f;
+ prop.noise_power = 2;
+ prop.threshold = 0.5f;
+ prop.min_size = 0.02f;
+ prop.max_size = 4.0f;
+ prop.min_y_offset = -2;
+ prop.max_y_offset = 0.5f;
+ prop.mesh_entity = props_scene.Entity_FindByName("rock_mesh");
+ Entity object_entity = props_scene.Entity_FindByName("rock_object");
+ ObjectComponent* object = props_scene.objects.GetComponent(object_entity);
+ if (object != nullptr)
+ {
+ prop.object = *object;
+ prop.object.lod_distance_multiplier = 0.02f;
+ prop.object.cascadeMask = 1; // they won't be rendered into the largest shadow cascade
+ 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
+ editor->GetCurrentScene().Merge(props_scene);
+ }
+ // Bush prop:
+ {
+ Scene props_scene;
+ wi::scene::LoadModel(props_scene, "terrain/bush.wiscene");
+ wi::terrain::Prop& prop = terrain_preset.props.emplace_back();
+ prop.name = "bush";
+ prop.min_count_per_chunk = 0;
+ prop.max_count_per_chunk = 10;
+ prop.region = 0;
+ prop.region_power = 4;
+ prop.noise_frequency = 0.01f;
+ prop.noise_power = 4;
+ prop.threshold = 0.1f;
+ prop.min_size = 0.1f;
+ prop.max_size = 1.5f;
+ prop.min_y_offset = -1;
+ prop.max_y_offset = 0;
+ prop.mesh_entity = props_scene.Entity_FindByName("bush_mesh");
+ Entity object_entity = props_scene.Entity_FindByName("bush_object");
+ ObjectComponent* object = props_scene.objects.GetComponent(object_entity);
+ if (object != nullptr)
+ {
+ prop.object = *object;
+ prop.object.lod_distance_multiplier = 0.05f;
+ prop.object.cascadeMask = 1; // they won't be rendered into the largest shadow cascade
+ 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
+ editor->GetCurrentScene().Merge(props_scene);
+ }
+
+ terrain = &terrain_preset;
+ presetCombo.SetSelected(0);
+}
+
+void TerrainWindow::Update(const wi::Canvas& canvas, float dt)
+{
+ // Check whether any modifiers were "closed", and we will really remove them here if so:
+ if (!modifiers_to_remove.empty())
+ {
+ for (auto& modifier : modifiers_to_remove)
+ {
+ for (auto it = modifiers.begin(); it != modifiers.end(); ++it)
+ {
+ if (it->get() == modifier)
+ {
+ modifiers.erase(it);
+ RemoveWidget(modifier);
+ break;
+ }
+ }
+ }
+ modifiers_to_remove.clear();
+ }
+
+ wi::gui::Window::Update(canvas, dt);
+}
+void TerrainWindow::ResizeLayout()
+{
+ wi::gui::Window::ResizeLayout();
+ const float padding = 4;
+ const float width = GetWidgetAreaSize().x;
+ float y = padding;
+
+ auto add = [&](wi::gui::Widget& widget) {
+ const float margin_left = 150;
+ const float margin_right = 45;
+ widget.SetPos(XMFLOAT2(margin_left, y));
+ widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
+ y += widget.GetSize().y;
+ y += padding;
+ };
+ auto add_checkbox = [&](wi::gui::CheckBox& widget) {
+ const float margin_right = 45;
+ widget.SetPos(XMFLOAT2(width - margin_right - widget.GetSize().x, y));
+ y += widget.GetSize().y;
+ y += padding;
+ };
+ auto add_window = [&](wi::gui::Window& widget) {
+ const float margin_left = padding;
+ const float margin_right = padding;
+ widget.SetPos(XMFLOAT2(margin_left, y));
+ widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
+ y += widget.GetSize().y;
+ y += padding;
+ widget.SetEnabled(true);
+ };
+
+ add_checkbox(centerToCamCheckBox);
+ add_checkbox(removalCheckBox);
+ add_checkbox(grassCheckBox);
+ add(lodSlider);
+ add(texlodSlider);
+ add(generationSlider);
+ add(propSlider);
+ add(propDensitySlider);
+ add(grassDensitySlider);
+ add(presetCombo);
+ add(scaleSlider);
+ add(seedSlider);
+ add(bottomLevelSlider);
+ add(topLevelSlider);
+ add(region1Slider);
+ add(region2Slider);
+ add(region3Slider);
+ add(saveHeightmapButton);
+ add(addModifierCombo);
+
+ for (auto& modifier : modifiers)
+ {
+ add_window(*modifier);
+ }
+
+}
diff --git a/Editor/TerrainWindow.h b/Editor/TerrainWindow.h
new file mode 100644
index 000000000..2673370a8
--- /dev/null
+++ b/Editor/TerrainWindow.h
@@ -0,0 +1,100 @@
+#pragma once
+#include "WickedEngine.h"
+
+class EditorComponent;
+
+struct ModifierWindow : public wi::gui::Window
+{
+ wi::terrain::Modifier* modifier = nullptr;
+ std::function generation_callback;
+ wi::gui::ComboBox blendCombo;
+ wi::gui::Slider weightSlider;
+ wi::gui::Slider frequencySlider;
+
+ virtual ~ModifierWindow() = default;
+
+ ModifierWindow(const std::string& name);
+ void Bind(wi::terrain::Modifier* ptr);
+ void From(wi::terrain::Modifier* ptr);
+};
+struct PerlinModifierWindow : public ModifierWindow
+{
+ wi::gui::Slider octavesSlider;
+
+ PerlinModifierWindow();
+ void ResizeLayout();
+ void Bind(wi::terrain::PerlinModifier* ptr);
+ void From(wi::terrain::PerlinModifier* ptr);
+};
+struct VoronoiModifierWindow : public ModifierWindow
+{
+ wi::gui::Slider fadeSlider;
+ wi::gui::Slider shapeSlider;
+ wi::gui::Slider falloffSlider;
+ wi::gui::Slider perturbationSlider;
+
+ VoronoiModifierWindow();
+ void ResizeLayout() override;
+ void Bind(wi::terrain::VoronoiModifier* ptr);
+ void From(wi::terrain::VoronoiModifier* ptr);
+};
+struct HeightmapModifierWindow : public ModifierWindow
+{
+ wi::gui::Slider scaleSlider;
+ wi::gui::Button loadButton;
+
+ HeightmapModifierWindow();
+ void ResizeLayout() override;
+ void Bind(wi::terrain::HeightmapModifier* ptr);
+ void From(wi::terrain::HeightmapModifier* ptr);
+};
+
+class TerrainWindow : public wi::gui::Window
+{
+public:
+ wi::gui::CheckBox centerToCamCheckBox;
+ wi::gui::CheckBox removalCheckBox;
+ wi::gui::CheckBox grassCheckBox;
+ wi::gui::Slider lodSlider;
+ wi::gui::Slider texlodSlider;
+ wi::gui::Slider generationSlider;
+ wi::gui::Slider propSlider;
+ wi::gui::Slider propDensitySlider;
+ wi::gui::Slider grassDensitySlider;
+ wi::gui::ComboBox presetCombo;
+ wi::gui::Slider scaleSlider;
+ wi::gui::Slider seedSlider;
+ wi::gui::Slider bottomLevelSlider;
+ wi::gui::Slider topLevelSlider;
+ wi::gui::Button saveHeightmapButton;
+ wi::gui::ComboBox addModifierCombo;
+
+ wi::gui::Slider region1Slider;
+ wi::gui::Slider region2Slider;
+ wi::gui::Slider region3Slider;
+
+ enum PRESET
+ {
+ PRESET_HILLS,
+ PRESET_ISLANDS,
+ PRESET_MOUNTAINS,
+ PRESET_ARCTIC,
+ };
+
+ wi::vector> modifiers;
+ wi::vector modifiers_to_remove;
+
+ void Create(EditorComponent* editor);
+
+ EditorComponent* editor = nullptr;
+ wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY;
+ wi::terrain::Terrain terrain_preset;
+ wi::terrain::Terrain* terrain = &terrain_preset;
+ void SetEntity(wi::ecs::Entity entity);
+ void AddModifier(ModifierWindow* modifier_window);
+ void SetupAssets();
+
+ void Update(const wi::Canvas& canvas, float dt) override;
+ void ResizeLayout() override;
+};
+
diff --git a/Editor/Translator.cpp b/Editor/Translator.cpp
index cd28987b4..57ab60bfa 100644
--- a/Editor/Translator.cpp
+++ b/Editor/Translator.cpp
@@ -141,6 +141,8 @@ void Translator::Update(const CameraComponent& camera, const wi::Canvas& canvas)
if (IsEnabled())
{
PreTranslate();
+ if (!has_selected_transform)
+ return;
const Ray ray = wi::renderer::GetPickRay((long)pointer.x, (long)pointer.y, canvas, camera);
const XMVECTOR rayOrigin = XMLoadFloat3(&ray.origin);
@@ -491,7 +493,7 @@ void Translator::Update(const CameraComponent& camera, const wi::Canvas& canvas)
}
void Translator::Draw(const CameraComponent& camera, CommandList cmd) const
{
- if (!IsEnabled() || selected.empty())
+ if (!IsEnabled() || selected.empty() || !has_selected_transform)
{
return;
}
@@ -1061,6 +1063,7 @@ void Translator::PreTranslate()
{
transform.ClearTransform();
}
+ has_selected_transform = false;
// Find the center of all the entities that are selected:
XMVECTOR centerV = XMVectorSet(0, 0, 0, 0);
@@ -1073,9 +1076,13 @@ void Translator::PreTranslate()
transform->UpdateTransform();
centerV = XMVectorAdd(centerV, transform->GetPositionV());
count += 1.0f;
+ has_selected_transform = true;
}
}
+ if (!has_selected_transform)
+ return;
+
// Offset translator to center position and perform attachments:
if (count > 0)
{
diff --git a/Editor/Translator.h b/Editor/Translator.h
index b2218ccab..49600ddf7 100644
--- a/Editor/Translator.h
+++ b/Editor/Translator.h
@@ -14,6 +14,7 @@ private:
XMFLOAT3 axis = XMFLOAT3(1, 0, 0);
float angle = 0;
float angle_start = 0;
+ bool has_selected_transform = false;
public:
void Update(const wi::scene::CameraComponent& camera, const wi::Canvas& canvas);
diff --git a/Editor/main_Windows.cpp b/Editor/main_Windows.cpp
index 52e570550..aeddaf8eb 100644
--- a/Editor/main_Windows.cpp
+++ b/Editor/main_Windows.cpp
@@ -69,8 +69,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
}
}
- editor.renderComponent.optionsWnd.terragen.Generation_Cancel();
-
return (int) msg.wParam;
}
diff --git a/WickedEngine/ArchiveVersionHistory.txt b/WickedEngine/ArchiveVersionHistory.txt
index fce0a7bc5..87e132b95 100644
--- a/WickedEngine/ArchiveVersionHistory.txt
+++ b/WickedEngine/ArchiveVersionHistory.txt
@@ -1,5 +1,6 @@
This file contains changelog of wi::Archive versions
+87: DDGI serialization: added grid_extents and smooth_backface
86: serialized volumetric clouds weather map, removed unused values and remapped values from VolumetricCloudParameters
85: DDGI serialization
84: component library serialization
diff --git a/WickedEngine/CMakeLists.txt b/WickedEngine/CMakeLists.txt
index 59dddb77e..62401fdcf 100644
--- a/WickedEngine/CMakeLists.txt
+++ b/WickedEngine/CMakeLists.txt
@@ -119,6 +119,7 @@ set(HEADER_FILES
wiScene.h
wiScene_BindLua.h
wiScene_Decl.h
+ wiScene_Components.h
wiSDLInput.h
wiShaderCompiler.h
wiSheenLUT.h
@@ -137,6 +138,7 @@ set(HEADER_FILES
wiVersion.h
wiXInput.h
wiConfig.h
+ wiTerrain.h
)
add_library(${TARGET_NAME} ${WICKED_LIBRARY_TYPE}
@@ -193,6 +195,7 @@ add_library(${TARGET_NAME} ${WICKED_LIBRARY_TYPE}
wiRenderer_BindLua.cpp
wiResourceManager.cpp
wiScene.cpp
+ wiScene_Components.cpp
wiScene_BindLua.cpp
wiScene_Serializers.cpp
wiSDLInput.cpp
@@ -206,6 +209,7 @@ add_library(${TARGET_NAME} ${WICKED_LIBRARY_TYPE}
wiXInput.cpp
wiShaderCompiler.cpp
wiConfig.cpp
+ wiTerrain.cpp
${HEADER_FILES}
)
add_library(WickedEngine ALIAS ${TARGET_NAME})
diff --git a/WickedEngine/WickedEngine.h b/WickedEngine/WickedEngine.h
index e4fd77eb0..401c6ab6b 100644
--- a/WickedEngine/WickedEngine.h
+++ b/WickedEngine/WickedEngine.h
@@ -70,6 +70,7 @@
#include "wiVector.h"
#include "wiNoise.h"
#include "wiConfig.h"
+#include "wiTerrain.h"
#ifdef _WIN32
#ifdef PLATFORM_UWP
diff --git a/WickedEngine/WickedEngine_SOURCE.vcxitems b/WickedEngine/WickedEngine_SOURCE.vcxitems
index 4c9631f67..fa69ada83 100644
--- a/WickedEngine/WickedEngine_SOURCE.vcxitems
+++ b/WickedEngine/WickedEngine_SOURCE.vcxitems
@@ -263,6 +263,8 @@
+
+
@@ -644,7 +646,9 @@
+
+
diff --git a/WickedEngine/WickedEngine_SOURCE.vcxitems.filters b/WickedEngine/WickedEngine_SOURCE.vcxitems.filters
index 21b63b779..8f18614f2 100644
--- a/WickedEngine/WickedEngine_SOURCE.vcxitems.filters
+++ b/WickedEngine/WickedEngine_SOURCE.vcxitems.filters
@@ -1080,6 +1080,12 @@
ENGINE\Helpers
+
+ ENGINE\Graphics
+
+
+ ENGINE\System
+
@@ -1817,6 +1823,12 @@
ENGINE\Helpers
+
+ ENGINE\Graphics
+
+
+ ENGINE\System
+
diff --git a/WickedEngine/shaders/ShaderInterop_DDGI.h b/WickedEngine/shaders/ShaderInterop_DDGI.h
index 2bc27c824..c160421b2 100644
--- a/WickedEngine/shaders/ShaderInterop_DDGI.h
+++ b/WickedEngine/shaders/ShaderInterop_DDGI.h
@@ -171,7 +171,6 @@ float3 ddgi_sample_irradiance(float3 P, float3 N)
// up to 1/n. We want to distinguish between weights that are
// low because of different factors.
-#if 0
// Smooth backface test
{
// Computed without the biasing applied to the "dir" variable.
@@ -188,11 +187,8 @@ float3 ddgi_sample_irradiance(float3 P, float3 N)
// The small offset at the end reduces the "going to zero" impact
// where this is really close to exactly opposite
- weight *= sqr(max(0.0001, (dot(true_direction_to_probe, N) + 1.0) * 0.5)) + 0.2;
+ weight *= lerp(saturate(dot(dir, N)), sqr(max(0.0001, (dot(true_direction_to_probe, N) + 1.0) * 0.5)) + 0.2, GetScene().ddgi.smooth_backface);
}
-#else
- weight *= saturate(dot(dir, N));
-#endif
// Moment visibility test
#if 1
diff --git a/WickedEngine/shaders/ShaderInterop_Renderer.h b/WickedEngine/shaders/ShaderInterop_Renderer.h
index 7e0d4bf23..42fab539f 100644
--- a/WickedEngine/shaders/ShaderInterop_Renderer.h
+++ b/WickedEngine/shaders/ShaderInterop_Renderer.h
@@ -55,7 +55,7 @@ struct ShaderScene
int offset_buffer;
float3 cell_size_rcp;
- float padding0;
+ float smooth_backface;
};
DDGI ddgi;
};
diff --git a/WickedEngine/shaders/objectPS_transparent_clearcoat.hlsl b/WickedEngine/shaders/objectPS_transparent_clearcoat.hlsl
index 72346a6b6..38619f7b8 100644
--- a/WickedEngine/shaders/objectPS_transparent_clearcoat.hlsl
+++ b/WickedEngine/shaders/objectPS_transparent_clearcoat.hlsl
@@ -1,6 +1,5 @@
#define OBJECTSHADER_COMPILE_PS
#define OBJECTSHADER_LAYOUT_COMMON
-#define SHADOW_MASK_ENABLED
#define TILEDFORWARD
#define TRANSPARENT
#define CLEARCOAT
diff --git a/WickedEngine/shaders/objectPS_transparent_cloth.hlsl b/WickedEngine/shaders/objectPS_transparent_cloth.hlsl
index 971dcec48..e9712d7d9 100644
--- a/WickedEngine/shaders/objectPS_transparent_cloth.hlsl
+++ b/WickedEngine/shaders/objectPS_transparent_cloth.hlsl
@@ -1,6 +1,5 @@
#define OBJECTSHADER_COMPILE_PS
#define OBJECTSHADER_LAYOUT_COMMON
-#define SHADOW_MASK_ENABLED
#define TILEDFORWARD
#define TRANSPARENT
#define SHEEN
diff --git a/WickedEngine/shaders/objectPS_transparent_cloth_clearcoat.hlsl b/WickedEngine/shaders/objectPS_transparent_cloth_clearcoat.hlsl
index b778f7071..d3195ddd9 100644
--- a/WickedEngine/shaders/objectPS_transparent_cloth_clearcoat.hlsl
+++ b/WickedEngine/shaders/objectPS_transparent_cloth_clearcoat.hlsl
@@ -1,6 +1,5 @@
#define OBJECTSHADER_COMPILE_PS
#define OBJECTSHADER_LAYOUT_COMMON
-#define SHADOW_MASK_ENABLED
#define TILEDFORWARD
#define TRANSPARENT
#define SHEEN
diff --git a/WickedEngine/wiArchive.cpp b/WickedEngine/wiArchive.cpp
index 99a68ad45..e0a9a2709 100644
--- a/WickedEngine/wiArchive.cpp
+++ b/WickedEngine/wiArchive.cpp
@@ -5,7 +5,7 @@ namespace wi
{
// this should always be only INCREMENTED and only if a new serialization is implemeted somewhere!
- static constexpr uint64_t __archiveVersion = 86;
+ static constexpr uint64_t __archiveVersion = 87;
// this is the version number of which below the archive is not compatible with the current version
static constexpr uint64_t __archiveVersionBarrier = 22;
diff --git a/WickedEngine/wiArchive.h b/WickedEngine/wiArchive.h
index ab472cae5..9f6589b77 100644
--- a/WickedEngine/wiArchive.h
+++ b/WickedEngine/wiArchive.h
@@ -2,6 +2,7 @@
#include "CommonInclude.h"
#include "wiMath.h"
#include "wiVector.h"
+#include "wiColor.h"
#include
@@ -194,6 +195,11 @@ namespace wi
_write(data);
return *this;
}
+ inline Archive& operator<<(const wi::Color& data)
+ {
+ _write(data.rgba);
+ return *this;
+ }
inline Archive& operator<<(const std::string& data)
{
(*this) << data.length();
@@ -334,6 +340,11 @@ namespace wi
_read(data);
return *this;
}
+ inline Archive& operator>>(wi::Color& data)
+ {
+ _read(data.rgba);
+ return *this;
+ }
inline Archive& operator>>(std::string& data)
{
uint64_t len;
diff --git a/WickedEngine/wiNoise.h b/WickedEngine/wiNoise.h
index a22a0584d..f9906c17c 100644
--- a/WickedEngine/wiNoise.h
+++ b/WickedEngine/wiNoise.h
@@ -1,6 +1,7 @@
#pragma once
#include "CommonInclude.h"
#include "wiMath.h"
+#include "wiArchive.h"
#include
@@ -9,7 +10,7 @@ namespace wi::noise
// Based on: https://github.com/Reputeless/PerlinNoise
struct Perlin
{
- uint8_t state[256];
+ uint8_t state[256] = {};
void init(uint32_t seed)
{
@@ -97,6 +98,24 @@ namespace wi::noise
}
return result;
}
+
+ void Serialize(wi::Archive& archive)
+ {
+ if (archive.IsReadMode())
+ {
+ for (auto& x : state)
+ {
+ archive >> x;
+ }
+ }
+ else
+ {
+ for (auto& x : state)
+ {
+ archive << x;
+ }
+ }
+ }
};
// Based on: https://www.shadertoy.com/view/MslGD8
diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp
index 8483be2be..cee02cce7 100644
--- a/WickedEngine/wiRenderer.cpp
+++ b/WickedEngine/wiRenderer.cpp
@@ -3654,22 +3654,14 @@ void UpdateRenderData(
bool shadow = light.IsCastingShadow() && !light.IsStatic();
- if (GetRaytracedShadowsEnabled() && shadow)
+ if (shadow)
{
+ shaderentity.shadowAtlasMulAdd.x = light.shadow_rect.w * atlas_dim_rcp.x;
+ shaderentity.shadowAtlasMulAdd.y = light.shadow_rect.h * atlas_dim_rcp.y;
+ shaderentity.shadowAtlasMulAdd.z = light.shadow_rect.x * atlas_dim_rcp.x;
+ shaderentity.shadowAtlasMulAdd.w = light.shadow_rect.y * atlas_dim_rcp.y;
shaderentity.SetIndices(matrixCounter, 0);
}
- else if(shadow)
- {
- shadow = light.IsCastingShadow() && !light.IsStatic();
- if (shadow)
- {
- shaderentity.shadowAtlasMulAdd.x = light.shadow_rect.w * atlas_dim_rcp.x;
- shaderentity.shadowAtlasMulAdd.y = light.shadow_rect.h * atlas_dim_rcp.y;
- shaderentity.shadowAtlasMulAdd.z = light.shadow_rect.x * atlas_dim_rcp.x;
- shaderentity.shadowAtlasMulAdd.w = light.shadow_rect.y * atlas_dim_rcp.y;
- shaderentity.SetIndices(matrixCounter, 0);
- }
- }
switch (light.GetType())
{
diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp
index 4d8afa032..df458b261 100644
--- a/WickedEngine/wiScene.cpp
+++ b/WickedEngine/wiScene.cpp
@@ -2,7 +2,6 @@
#include "wiTextureHelper.h"
#include "wiResourceManager.h"
#include "wiPhysics.h"
-#include "wiArchive.h"
#include "wiRenderer.h"
#include "wiJobSystem.h"
#include "wiSpinLock.h"
@@ -23,1534 +22,6 @@ using namespace wi::primitive;
namespace wi::scene
{
-
- XMFLOAT3 TransformComponent::GetPosition() const
- {
- return *((XMFLOAT3*)&world._41);
- }
- XMFLOAT4 TransformComponent::GetRotation() const
- {
- XMFLOAT4 rotation;
- XMStoreFloat4(&rotation, GetRotationV());
- return rotation;
- }
- XMFLOAT3 TransformComponent::GetScale() const
- {
- XMFLOAT3 scale;
- XMStoreFloat3(&scale, GetScaleV());
- return scale;
- }
- XMVECTOR TransformComponent::GetPositionV() const
- {
- return XMLoadFloat3((XMFLOAT3*)&world._41);
- }
- XMVECTOR TransformComponent::GetRotationV() const
- {
- XMVECTOR S, R, T;
- XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
- return R;
- }
- XMVECTOR TransformComponent::GetScaleV() const
- {
- XMVECTOR S, R, T;
- XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
- return S;
- }
- XMMATRIX TransformComponent::GetLocalMatrix() const
- {
- XMVECTOR S_local = XMLoadFloat3(&scale_local);
- XMVECTOR R_local = XMLoadFloat4(&rotation_local);
- XMVECTOR T_local = XMLoadFloat3(&translation_local);
- return
- XMMatrixScalingFromVector(S_local) *
- XMMatrixRotationQuaternion(R_local) *
- XMMatrixTranslationFromVector(T_local);
- }
- void TransformComponent::UpdateTransform()
- {
- if (IsDirty())
- {
- SetDirty(false);
-
- XMStoreFloat4x4(&world, GetLocalMatrix());
- }
- }
- void TransformComponent::UpdateTransform_Parented(const TransformComponent& parent)
- {
- XMMATRIX W = GetLocalMatrix();
- XMMATRIX W_parent = XMLoadFloat4x4(&parent.world);
- W = W * W_parent;
-
- XMStoreFloat4x4(&world, W);
- }
- void TransformComponent::ApplyTransform()
- {
- SetDirty();
-
- XMVECTOR S, R, T;
- XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
- XMStoreFloat3(&scale_local, S);
- XMStoreFloat4(&rotation_local, R);
- XMStoreFloat3(&translation_local, T);
- }
- void TransformComponent::ClearTransform()
- {
- SetDirty();
- scale_local = XMFLOAT3(1, 1, 1);
- rotation_local = XMFLOAT4(0, 0, 0, 1);
- translation_local = XMFLOAT3(0, 0, 0);
- }
- void TransformComponent::Translate(const XMFLOAT3& value)
- {
- SetDirty();
- translation_local.x += value.x;
- translation_local.y += value.y;
- translation_local.z += value.z;
- }
- void TransformComponent::Translate(const XMVECTOR& value)
- {
- XMFLOAT3 translation;
- XMStoreFloat3(&translation, value);
- Translate(translation);
- }
- void TransformComponent::RotateRollPitchYaw(const XMFLOAT3& value)
- {
- SetDirty();
-
- // This needs to be handled a bit differently
- XMVECTOR quat = XMLoadFloat4(&rotation_local);
- XMVECTOR x = XMQuaternionRotationRollPitchYaw(value.x, 0, 0);
- XMVECTOR y = XMQuaternionRotationRollPitchYaw(0, value.y, 0);
- XMVECTOR z = XMQuaternionRotationRollPitchYaw(0, 0, value.z);
-
- quat = XMQuaternionMultiply(x, quat);
- quat = XMQuaternionMultiply(quat, y);
- quat = XMQuaternionMultiply(z, quat);
- quat = XMQuaternionNormalize(quat);
-
- XMStoreFloat4(&rotation_local, quat);
- }
- void TransformComponent::Rotate(const XMFLOAT4& quaternion)
- {
- SetDirty();
-
- XMVECTOR result = XMQuaternionMultiply(XMLoadFloat4(&rotation_local), XMLoadFloat4(&quaternion));
- result = XMQuaternionNormalize(result);
- XMStoreFloat4(&rotation_local, result);
- }
- void TransformComponent::Rotate(const XMVECTOR& quaternion)
- {
- XMFLOAT4 rotation;
- XMStoreFloat4(&rotation, quaternion);
- Rotate(rotation);
- }
- void TransformComponent::Scale(const XMFLOAT3& value)
- {
- SetDirty();
- scale_local.x *= value.x;
- scale_local.y *= value.y;
- scale_local.z *= value.z;
- }
- void TransformComponent::Scale(const XMVECTOR& value)
- {
- XMFLOAT3 scale;
- XMStoreFloat3(&scale, value);
- Scale(scale);
- }
- void TransformComponent::MatrixTransform(const XMFLOAT4X4& matrix)
- {
- MatrixTransform(XMLoadFloat4x4(&matrix));
- }
- void TransformComponent::MatrixTransform(const XMMATRIX& matrix)
- {
- SetDirty();
-
- XMVECTOR S;
- XMVECTOR R;
- XMVECTOR T;
- XMMatrixDecompose(&S, &R, &T, GetLocalMatrix() * matrix);
-
- XMStoreFloat3(&scale_local, S);
- XMStoreFloat4(&rotation_local, R);
- XMStoreFloat3(&translation_local, T);
- }
- void TransformComponent::Lerp(const TransformComponent& a, const TransformComponent& b, float t)
- {
- SetDirty();
-
- XMVECTOR aS, aR, aT;
- XMMatrixDecompose(&aS, &aR, &aT, XMLoadFloat4x4(&a.world));
-
- XMVECTOR bS, bR, bT;
- XMMatrixDecompose(&bS, &bR, &bT, XMLoadFloat4x4(&b.world));
-
- XMVECTOR S = XMVectorLerp(aS, bS, t);
- XMVECTOR R = XMQuaternionSlerp(aR, bR, t);
- XMVECTOR T = XMVectorLerp(aT, bT, t);
-
- XMStoreFloat3(&scale_local, S);
- XMStoreFloat4(&rotation_local, R);
- XMStoreFloat3(&translation_local, T);
- }
- void TransformComponent::CatmullRom(const TransformComponent& a, const TransformComponent& b, const TransformComponent& c, const TransformComponent& d, float t)
- {
- SetDirty();
-
- XMVECTOR aS, aR, aT;
- XMMatrixDecompose(&aS, &aR, &aT, XMLoadFloat4x4(&a.world));
-
- XMVECTOR bS, bR, bT;
- XMMatrixDecompose(&bS, &bR, &bT, XMLoadFloat4x4(&b.world));
-
- XMVECTOR cS, cR, cT;
- XMMatrixDecompose(&cS, &cR, &cT, XMLoadFloat4x4(&c.world));
-
- XMVECTOR dS, dR, dT;
- XMMatrixDecompose(&dS, &dR, &dT, XMLoadFloat4x4(&d.world));
-
- XMVECTOR T = XMVectorCatmullRom(aT, bT, cT, dT, t);
-
- XMVECTOR setupA;
- XMVECTOR setupB;
- XMVECTOR setupC;
-
- aR = XMQuaternionNormalize(aR);
- bR = XMQuaternionNormalize(bR);
- cR = XMQuaternionNormalize(cR);
- dR = XMQuaternionNormalize(dR);
-
- XMQuaternionSquadSetup(&setupA, &setupB, &setupC, aR, bR, cR, dR);
- XMVECTOR R = XMQuaternionSquad(bR, setupA, setupB, setupC, t);
-
- XMVECTOR S = XMVectorCatmullRom(aS, bS, cS, dS, t);
-
- XMStoreFloat3(&translation_local, T);
- XMStoreFloat4(&rotation_local, R);
- XMStoreFloat3(&scale_local, S);
- }
-
- void MaterialComponent::WriteShaderMaterial(ShaderMaterial* dest) const
- {
- ShaderMaterial material;
- material.baseColor = baseColor;
- material.emissive_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(emissiveColor.x * emissiveColor.w, emissiveColor.y * emissiveColor.w, emissiveColor.z * emissiveColor.w));
- material.specular_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(specularColor.x * specularColor.w, specularColor.y * specularColor.w, specularColor.z * specularColor.w));
- material.texMulAdd = texMulAdd;
- material.roughness = roughness;
- material.reflectance = reflectance;
- material.metalness = metalness;
- material.refraction = refraction;
- material.normalMapStrength = (textures[NORMALMAP].resource.IsValid() ? normalMapStrength : 0);
- material.parallaxOcclusionMapping = parallaxOcclusionMapping;
- material.displacementMapping = displacementMapping;
- XMFLOAT4 sss = subsurfaceScattering;
- sss.x *= sss.w;
- sss.y *= sss.w;
- sss.z *= sss.w;
- XMFLOAT4 sss_inv = XMFLOAT4(
- sss_inv.x = 1.0f / ((1 + sss.x) * (1 + sss.x)),
- sss_inv.y = 1.0f / ((1 + sss.y) * (1 + sss.y)),
- sss_inv.z = 1.0f / ((1 + sss.z) * (1 + sss.z)),
- sss_inv.w = 1.0f / ((1 + sss.w) * (1 + sss.w))
- );
- material.subsurfaceScattering = sss;
- material.subsurfaceScattering_inv = sss_inv;
- material.uvset_baseColorMap = textures[BASECOLORMAP].GetUVSet();
- material.uvset_surfaceMap = textures[SURFACEMAP].GetUVSet();
- material.uvset_normalMap = textures[NORMALMAP].GetUVSet();
- material.uvset_displacementMap = textures[DISPLACEMENTMAP].GetUVSet();
- material.uvset_emissiveMap = textures[EMISSIVEMAP].GetUVSet();
- material.uvset_occlusionMap = textures[OCCLUSIONMAP].GetUVSet();
- material.uvset_transmissionMap = textures[TRANSMISSIONMAP].GetUVSet();
- material.uvset_sheenColorMap = textures[SHEENCOLORMAP].GetUVSet();
- material.uvset_sheenRoughnessMap = textures[SHEENROUGHNESSMAP].GetUVSet();
- material.uvset_clearcoatMap = textures[CLEARCOATMAP].GetUVSet();
- material.uvset_clearcoatRoughnessMap = textures[CLEARCOATROUGHNESSMAP].GetUVSet();
- material.uvset_clearcoatNormalMap = textures[CLEARCOATNORMALMAP].GetUVSet();
- material.uvset_specularMap = textures[SPECULARMAP].GetUVSet();
- material.sheenColor_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(sheenColor.x, sheenColor.y, sheenColor.z));
- material.sheenRoughness = sheenRoughness;
- material.clearcoat = clearcoat;
- material.clearcoatRoughness = clearcoatRoughness;
- material.alphaTest = 1 - alphaRef;
- material.layerMask = layerMask;
- material.transmission = transmission;
- material.shaderType = (uint)shaderType;
- material.userdata = userdata;
-
- material.options = 0;
- if (IsUsingVertexColors())
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_USE_VERTEXCOLORS;
- }
- if (IsUsingSpecularGlossinessWorkflow())
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_SPECULARGLOSSINESS_WORKFLOW;
- }
- if (IsOcclusionEnabled_Primary())
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_OCCLUSION_PRIMARY;
- }
- if (IsOcclusionEnabled_Secondary())
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_OCCLUSION_SECONDARY;
- }
- if (IsUsingWind())
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_USE_WIND;
- }
- if (IsReceiveShadow())
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_RECEIVE_SHADOW;
- }
- if (IsCastingShadow())
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_CAST_SHADOW;
- }
- if (IsDoubleSided())
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_DOUBLE_SIDED;
- }
- if (GetRenderTypes() & RENDERTYPE_TRANSPARENT)
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_TRANSPARENT;
- }
- if (userBlendMode == BLENDMODE_ADDITIVE)
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_ADDITIVE;
- }
- if (shaderType == SHADERTYPE_UNLIT)
- {
- material.options |= SHADERMATERIAL_OPTION_BIT_UNLIT;
- }
-
- GraphicsDevice* device = wi::graphics::GetDevice();
- material.texture_basecolormap_index = device->GetDescriptorIndex(textures[BASECOLORMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_surfacemap_index = device->GetDescriptorIndex(textures[SURFACEMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_emissivemap_index = device->GetDescriptorIndex(textures[EMISSIVEMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_normalmap_index = device->GetDescriptorIndex(textures[NORMALMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_displacementmap_index = device->GetDescriptorIndex(textures[DISPLACEMENTMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_occlusionmap_index = device->GetDescriptorIndex(textures[OCCLUSIONMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_transmissionmap_index = device->GetDescriptorIndex(textures[TRANSMISSIONMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_sheencolormap_index = device->GetDescriptorIndex(textures[SHEENCOLORMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_sheenroughnessmap_index = device->GetDescriptorIndex(textures[SHEENROUGHNESSMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_clearcoatmap_index = device->GetDescriptorIndex(textures[CLEARCOATMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_clearcoatroughnessmap_index = device->GetDescriptorIndex(textures[CLEARCOATROUGHNESSMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_clearcoatnormalmap_index = device->GetDescriptorIndex(textures[CLEARCOATNORMALMAP].GetGPUResource(), SubresourceType::SRV);
- material.texture_specularmap_index = device->GetDescriptorIndex(textures[SPECULARMAP].GetGPUResource(), SubresourceType::SRV);
-
- std::memcpy(dest, &material, sizeof(ShaderMaterial)); // memcpy whole structure into mapped pointer to avoid read from uncached memory
- }
- void MaterialComponent::WriteTextures(const wi::graphics::GPUResource** dest, int count) const
- {
- count = std::min(count, (int)TEXTURESLOT_COUNT);
- for (int i = 0; i < count; ++i)
- {
- dest[i] = textures[i].GetGPUResource();
- }
- }
- uint32_t MaterialComponent::GetRenderTypes() const
- {
- if (IsCustomShader() && customShaderID < (int)wi::renderer::GetCustomShaders().size())
- {
- auto& customShader = wi::renderer::GetCustomShaders()[customShaderID];
- return customShader.renderTypeFlags;
- }
- if (shaderType == SHADERTYPE_WATER)
- {
- return RENDERTYPE_TRANSPARENT | RENDERTYPE_WATER;
- }
- if (transmission > 0)
- {
- return RENDERTYPE_TRANSPARENT;
- }
- if (userBlendMode == BLENDMODE_OPAQUE)
- {
- return RENDERTYPE_OPAQUE;
- }
- return RENDERTYPE_TRANSPARENT;
- }
- void MaterialComponent::CreateRenderData()
- {
- for (auto& x : textures)
- {
- if (!x.name.empty())
- {
- x.resource = wi::resourcemanager::Load(x.name, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA);
- }
- }
- }
- uint32_t MaterialComponent::GetStencilRef() const
- {
- return wi::renderer::CombineStencilrefs(engineStencilRef, userStencilRef);
- }
-
- void MeshComponent::CreateRenderData()
- {
- GraphicsDevice* device = wi::graphics::GetDevice();
-
- generalBuffer = {};
- streamoutBuffer = {};
- ib = {};
- vb_pos_nor_wind = {};
- vb_tan = {};
- vb_uvs = {};
- vb_atl = {};
- vb_col = {};
- vb_bon = {};
- so_pos_nor_wind = {};
- so_tan = {};
- so_pre = {};
-
- if (vertex_tangents.empty() && !vertex_uvset_0.empty() && !vertex_normals.empty())
- {
- // Generate tangents if not found:
- vertex_tangents.resize(vertex_positions.size());
-
- uint32_t first_subset = 0;
- uint32_t last_subset = 0;
- GetLODSubsetRange(0, first_subset, last_subset);
- for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
- {
- const MeshComponent::MeshSubset& subset = subsets[subsetIndex];
- for (size_t i = 0; i < subset.indexCount; i += 3)
- {
- const uint32_t i0 = indices[subset.indexOffset + i + 0];
- const uint32_t i1 = indices[subset.indexOffset + i + 1];
- const uint32_t i2 = indices[subset.indexOffset + i + 2];
-
- const XMFLOAT3 v0 = vertex_positions[i0];
- const XMFLOAT3 v1 = vertex_positions[i1];
- const XMFLOAT3 v2 = vertex_positions[i2];
-
- const XMFLOAT2 u0 = vertex_uvset_0[i0];
- const XMFLOAT2 u1 = vertex_uvset_0[i1];
- const XMFLOAT2 u2 = vertex_uvset_0[i2];
-
- const XMFLOAT3 n0 = vertex_normals[i0];
- const XMFLOAT3 n1 = vertex_normals[i1];
- const XMFLOAT3 n2 = vertex_normals[i2];
-
- const XMVECTOR nor0 = XMLoadFloat3(&n0);
- const XMVECTOR nor1 = XMLoadFloat3(&n1);
- const XMVECTOR nor2 = XMLoadFloat3(&n2);
-
- const XMVECTOR facenormal = XMVector3Normalize(nor0 + nor1 + nor2);
-
- const float x1 = v1.x - v0.x;
- const float x2 = v2.x - v0.x;
- const float y1 = v1.y - v0.y;
- const float y2 = v2.y - v0.y;
- const float z1 = v1.z - v0.z;
- const float z2 = v2.z - v0.z;
-
- const float s1 = u1.x - u0.x;
- const float s2 = u2.x - u0.x;
- const float t1 = u1.y - u0.y;
- const float t2 = u2.y - u0.y;
-
- const float r = 1.0f / (s1 * t2 - s2 * t1);
- const XMVECTOR sdir = XMVectorSet((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
- (t2 * z1 - t1 * z2) * r, 0);
- const XMVECTOR tdir = XMVectorSet((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
- (s1 * z2 - s2 * z1) * r, 0);
-
- XMVECTOR tangent;
- tangent = XMVector3Normalize(sdir - facenormal * XMVector3Dot(facenormal, sdir));
- float sign = XMVectorGetX(XMVector3Dot(XMVector3Cross(tangent, facenormal), tdir)) < 0.0f ? -1.0f : 1.0f;
-
- XMFLOAT3 t;
- XMStoreFloat3(&t, tangent);
-
- vertex_tangents[i0].x += t.x;
- vertex_tangents[i0].y += t.y;
- vertex_tangents[i0].z += t.z;
- vertex_tangents[i0].w = sign;
-
- vertex_tangents[i1].x += t.x;
- vertex_tangents[i1].y += t.y;
- vertex_tangents[i1].z += t.z;
- vertex_tangents[i1].w = sign;
-
- vertex_tangents[i2].x += t.x;
- vertex_tangents[i2].y += t.y;
- vertex_tangents[i2].z += t.z;
- vertex_tangents[i2].w = sign;
- }
- }
- }
-
- {
- vertex_subsets.resize(vertex_positions.size());
- uint32_t first_subset = 0;
- uint32_t last_subset = 0;
- GetLODSubsetRange(0, first_subset, last_subset);
- for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
- {
- const MeshComponent::MeshSubset& subset = subsets[subsetIndex];
- for (uint32_t i = 0; i < subset.indexCount; ++i)
- {
- uint32_t index = indices[subset.indexOffset + i];
- vertex_subsets[index] = subsetIndex;
- }
- }
- }
-
- const size_t uv_count = std::max(vertex_uvset_0.size(), vertex_uvset_1.size());
-
- GPUBufferDesc bd;
- bd.usage = Usage::DEFAULT;
- bd.bind_flags = BindFlag::VERTEX_BUFFER | BindFlag::INDEX_BUFFER | BindFlag::SHADER_RESOURCE;
- bd.misc_flags = ResourceMiscFlag::BUFFER_RAW;
- if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
- {
- bd.misc_flags |= ResourceMiscFlag::RAY_TRACING;
- }
- const uint64_t alignment = device->GetMinOffsetAlignment(&bd);
- bd.size =
- AlignTo(indices.size() * GetIndexStride(), alignment) +
- AlignTo(vertex_positions.size() * sizeof(Vertex_POS), alignment) +
- AlignTo(vertex_tangents.size() * sizeof(Vertex_TAN), alignment) +
- AlignTo(uv_count * sizeof(Vertex_UVS), alignment) +
- AlignTo(vertex_atlas.size() * sizeof(Vertex_TEX), alignment) +
- AlignTo(vertex_colors.size() * sizeof(Vertex_COL), alignment) +
- AlignTo(vertex_boneindices.size() * sizeof(Vertex_BON), alignment)
- ;
-
- // single allocation storage for GPU buffer data:
- wi::vector buffer_data(bd.size);
- uint64_t buffer_offset = 0ull;
-
- // Create index buffer GPU data:
- if (GetIndexFormat() == IndexBufferFormat::UINT32)
- {
- ib.offset = buffer_offset;
- ib.size = indices.size() * sizeof(uint32_t);
- uint32_t* indexdata = (uint32_t*)(buffer_data.data() + buffer_offset);
- buffer_offset += AlignTo(ib.size, alignment);
- for (size_t i = 0; i < indices.size(); ++i)
- {
- indexdata[i] = indices[i];
- }
- }
- else
- {
- ib.offset = buffer_offset;
- ib.size = indices.size() * sizeof(uint16_t);
- uint16_t* indexdata = (uint16_t*)(buffer_data.data() + buffer_offset);
- buffer_offset += AlignTo(ib.size, alignment);
- for (size_t i = 0; i < indices.size(); ++i)
- {
- indexdata[i] = (uint16_t)indices[i];
- }
- }
-
- XMFLOAT3 _min = XMFLOAT3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max());
- XMFLOAT3 _max = XMFLOAT3(std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::lowest());
-
- // vertexBuffer - POSITION + NORMAL + WIND:
- {
- if (!morph_targets.empty())
- {
- vertex_positions_morphed.resize(vertex_positions.size());
- dirty_morph = true;
- }
-
- vb_pos_nor_wind.offset = buffer_offset;
- vb_pos_nor_wind.size = vertex_positions.size() * sizeof(Vertex_POS);
- Vertex_POS* vertices = (Vertex_POS*)(buffer_data.data() + buffer_offset);
- buffer_offset += AlignTo(vb_pos_nor_wind.size, alignment);
- for (size_t i = 0; i < vertex_positions.size(); ++i)
- {
- const XMFLOAT3& pos = vertex_positions[i];
- XMFLOAT3 nor = vertex_normals.empty() ? XMFLOAT3(1, 1, 1) : vertex_normals[i];
- XMStoreFloat3(&nor, XMVector3Normalize(XMLoadFloat3(&nor)));
- const uint8_t wind = vertex_windweights.empty() ? 0xFF : vertex_windweights[i];
- vertices[i].FromFULL(pos, nor, wind);
-
- _min = wi::math::Min(_min, pos);
- _max = wi::math::Max(_max, pos);
- }
- }
-
- aabb = AABB(_min, _max);
-
- // vertexBuffer - TANGENTS
- if (!vertex_tangents.empty())
- {
- vb_tan.offset = buffer_offset;
- vb_tan.size = vertex_tangents.size() * sizeof(Vertex_TAN);
- Vertex_TAN* vertices = (Vertex_TAN*)(buffer_data.data() + buffer_offset);
- buffer_offset += AlignTo(vb_tan.size, alignment);
- for (size_t i = 0; i < vertex_tangents.size(); ++i)
- {
- vertices[i].FromFULL(vertex_tangents[i]);
- }
- }
-
- // vertexBuffer - UV SETS
- if (!vertex_uvset_0.empty() || !vertex_uvset_1.empty())
- {
- const XMFLOAT2* uv0_stream = vertex_uvset_0.empty() ? vertex_uvset_1.data() : vertex_uvset_0.data();
- const XMFLOAT2* uv1_stream = vertex_uvset_1.empty() ? vertex_uvset_0.data() : vertex_uvset_1.data();
-
- vb_uvs.offset = buffer_offset;
- vb_uvs.size = uv_count * sizeof(Vertex_UVS);
- Vertex_UVS* vertices = (Vertex_UVS*)(buffer_data.data() + buffer_offset);
- buffer_offset += AlignTo(vb_uvs.size, alignment);
- for (size_t i = 0; i < uv_count; ++i)
- {
- vertices[i].uv0.FromFULL(uv0_stream[i]);
- vertices[i].uv1.FromFULL(uv1_stream[i]);
- }
- }
-
- // vertexBuffer - ATLAS
- if (!vertex_atlas.empty())
- {
- vb_atl.offset = buffer_offset;
- vb_atl.size = vertex_atlas.size() * sizeof(Vertex_TEX);
- Vertex_TEX* vertices = (Vertex_TEX*)(buffer_data.data() + buffer_offset);
- buffer_offset += AlignTo(vb_atl.size, alignment);
- for (size_t i = 0; i < vertex_atlas.size(); ++i)
- {
- vertices[i].FromFULL(vertex_atlas[i]);
- }
- }
-
- // vertexBuffer - COLORS
- if (!vertex_colors.empty())
- {
- vb_col.offset = buffer_offset;
- vb_col.size = vertex_colors.size() * sizeof(Vertex_COL);
- Vertex_COL* vertices = (Vertex_COL*)(buffer_data.data() + buffer_offset);
- buffer_offset += AlignTo(vb_col.size, alignment);
- for (size_t i = 0; i < vertex_colors.size(); ++i)
- {
- vertices[i].color = vertex_colors[i];
- }
- }
-
- // skinning buffers:
- if (!vertex_boneindices.empty())
- {
- vb_bon.offset = buffer_offset;
- vb_bon.size = vertex_boneindices.size() * sizeof(Vertex_BON);
- Vertex_BON* vertices = (Vertex_BON*)(buffer_data.data() + buffer_offset);
- buffer_offset += AlignTo(vb_bon.size, alignment);
- assert(vertex_boneindices.size() == vertex_boneweights.size());
- for (size_t i = 0; i < vertex_boneindices.size(); ++i)
- {
- XMFLOAT4& wei = vertex_boneweights[i];
- // normalize bone weights
- float len = wei.x + wei.y + wei.z + wei.w;
- if (len > 0)
- {
- wei.x /= len;
- wei.y /= len;
- wei.z /= len;
- wei.w /= len;
- }
- vertices[i].FromFULL(vertex_boneindices[i], wei);
- }
- CreateStreamoutRenderData();
- }
-
- bool success = device->CreateBuffer(&bd, buffer_data.data(), &generalBuffer);
- assert(success);
- device->SetName(&generalBuffer, "MeshComponent::generalBuffer");
-
- assert(ib.IsValid());
- const Format ib_format = GetIndexFormat() == IndexBufferFormat::UINT32 ? Format::R32_UINT : Format::R16_UINT;
- ib.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, ib.offset, ib.size, &ib_format);
- ib.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, ib.subresource_srv);
-
- assert(vb_pos_nor_wind.IsValid());
- vb_pos_nor_wind.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_pos_nor_wind.offset, vb_pos_nor_wind.size);
- vb_pos_nor_wind.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_pos_nor_wind.subresource_srv);
-
- if (vb_tan.IsValid())
- {
- vb_tan.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_tan.offset, vb_tan.size);
- vb_tan.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_tan.subresource_srv);
- }
- if (vb_uvs.IsValid())
- {
- vb_uvs.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_uvs.offset, vb_uvs.size);
- vb_uvs.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_uvs.subresource_srv);
- }
- if (vb_atl.IsValid())
- {
- vb_atl.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_atl.offset, vb_atl.size);
- vb_atl.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_atl.subresource_srv);
- }
- if (vb_col.IsValid())
- {
- vb_col.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_col.offset, vb_col.size);
- vb_col.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_col.subresource_srv);
- }
- if (vb_bon.IsValid())
- {
- vb_bon.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_bon.offset, vb_bon.size);
- vb_bon.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_bon.subresource_srv);
- }
-
- if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
- {
- BLAS_state = MeshComponent::BLAS_STATE_NEEDS_REBUILD;
-
- const uint32_t lod_count = GetLODCount();
- BLASes.resize(lod_count);
- for (uint32_t lod = 0; lod < lod_count; ++lod)
- {
- RaytracingAccelerationStructureDesc desc;
- desc.type = RaytracingAccelerationStructureDesc::Type::BOTTOMLEVEL;
-
- if (streamoutBuffer.IsValid())
- {
- desc.flags |= RaytracingAccelerationStructureDesc::FLAG_ALLOW_UPDATE;
- desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_BUILD;
- }
- else
- {
- desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_TRACE;
- }
-
- uint32_t first_subset = 0;
- uint32_t last_subset = 0;
- GetLODSubsetRange(lod, first_subset, last_subset);
- for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
- {
- const MeshComponent::MeshSubset& subset = subsets[subsetIndex];
- desc.bottom_level.geometries.emplace_back();
- auto& geometry = desc.bottom_level.geometries.back();
- geometry.type = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::Type::TRIANGLES;
- geometry.triangles.vertex_buffer = generalBuffer;
- geometry.triangles.vertex_byte_offset = vb_pos_nor_wind.offset;
- geometry.triangles.index_buffer = generalBuffer;
- geometry.triangles.index_format = GetIndexFormat();
- geometry.triangles.index_count = subset.indexCount;
- geometry.triangles.index_offset = ib.offset / GetIndexStride() + subset.indexOffset;
- geometry.triangles.vertex_count = (uint32_t)vertex_positions.size();
- geometry.triangles.vertex_format = Format::R32G32B32_FLOAT;
- geometry.triangles.vertex_stride = sizeof(MeshComponent::Vertex_POS);
- }
-
- bool success = device->CreateRaytracingAccelerationStructure(&desc, &BLASes[lod]);
- assert(success);
- device->SetName(&BLASes[lod], std::string("MeshComponent::BLAS[LOD" + std::to_string(lod) + "]").c_str());
- }
- }
- }
- void MeshComponent::CreateStreamoutRenderData()
- {
- GraphicsDevice* device = wi::graphics::GetDevice();
-
- GPUBufferDesc desc;
- desc.usage = Usage::DEFAULT;
- desc.bind_flags = BindFlag::VERTEX_BUFFER | BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
- desc.misc_flags = ResourceMiscFlag::BUFFER_RAW;
- if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
- {
- desc.misc_flags |= ResourceMiscFlag::RAY_TRACING;
- }
- const uint64_t alignment = device->GetMinOffsetAlignment(&desc);
- desc.size =
- AlignTo(vertex_positions.size() * sizeof(Vertex_POS) * 2, alignment) + // *2 because prevpos also goes into this!
- AlignTo(vertex_tangents.size() * sizeof(Vertex_TAN), alignment)
- ;
-
- bool success = device->CreateBuffer(&desc, nullptr, &streamoutBuffer);
- assert(success);
- device->SetName(&streamoutBuffer, "MeshComponent::streamoutBuffer");
-
- uint64_t buffer_offset = 0ull;
-
- so_pos_nor_wind.offset = buffer_offset;
- so_pos_nor_wind.size = vb_pos_nor_wind.size;
- buffer_offset += AlignTo(so_pos_nor_wind.size, alignment);
- so_pos_nor_wind.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_pos_nor_wind.offset, so_pos_nor_wind.size);
- so_pos_nor_wind.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_pos_nor_wind.offset, so_pos_nor_wind.size);
- so_pos_nor_wind.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_pos_nor_wind.subresource_srv);
- so_pos_nor_wind.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_pos_nor_wind.subresource_uav);
-
- if (vb_tan.IsValid())
- {
- so_tan.offset = buffer_offset;
- so_tan.size = vb_tan.size;
- buffer_offset += AlignTo(so_tan.size, alignment);
- so_tan.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_tan.offset, so_tan.size);
- so_tan.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_tan.offset, so_tan.size);
- so_tan.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_tan.subresource_srv);
- so_tan.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_tan.subresource_uav);
- }
-
- so_pre.offset = buffer_offset;
- so_pre.size = vb_pos_nor_wind.size;
- buffer_offset += AlignTo(so_pre.size, alignment);
- so_pre.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_pre.offset, so_pre.size);
- so_pre.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_pre.offset, so_pre.size);
- so_pre.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_pre.subresource_srv);
- so_pre.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_pre.subresource_uav);
- }
- void MeshComponent::ComputeNormals(COMPUTE_NORMALS compute)
- {
- // Start recalculating normals:
-
- if(compute != COMPUTE_NORMALS_SMOOTH_FAST)
- {
- // Compute hard surface normals:
-
- // Right now they are always computed even before smooth setting
-
- wi::vector newIndexBuffer;
- wi::vector newPositionsBuffer;
- wi::vector newNormalsBuffer;
- wi::vector newUV0Buffer;
- wi::vector newUV1Buffer;
- wi::vector newAtlasBuffer;
- wi::vector newBoneIndicesBuffer;
- wi::vector newBoneWeightsBuffer;
- wi::vector newColorsBuffer;
-
- for (size_t face = 0; face < indices.size() / 3; face++)
- {
- uint32_t i0 = indices[face * 3 + 0];
- uint32_t i1 = indices[face * 3 + 1];
- uint32_t i2 = indices[face * 3 + 2];
-
- XMFLOAT3& p0 = vertex_positions[i0];
- XMFLOAT3& p1 = vertex_positions[i1];
- XMFLOAT3& p2 = vertex_positions[i2];
-
- XMVECTOR U = XMLoadFloat3(&p2) - XMLoadFloat3(&p0);
- XMVECTOR V = XMLoadFloat3(&p1) - XMLoadFloat3(&p0);
-
- XMVECTOR N = XMVector3Cross(U, V);
- N = XMVector3Normalize(N);
-
- XMFLOAT3 normal;
- XMStoreFloat3(&normal, N);
-
- newPositionsBuffer.push_back(p0);
- newPositionsBuffer.push_back(p1);
- newPositionsBuffer.push_back(p2);
-
- newNormalsBuffer.push_back(normal);
- newNormalsBuffer.push_back(normal);
- newNormalsBuffer.push_back(normal);
-
- if (!vertex_uvset_0.empty())
- {
- newUV0Buffer.push_back(vertex_uvset_0[i0]);
- newUV0Buffer.push_back(vertex_uvset_0[i1]);
- newUV0Buffer.push_back(vertex_uvset_0[i2]);
- }
-
- if (!vertex_uvset_1.empty())
- {
- newUV1Buffer.push_back(vertex_uvset_1[i0]);
- newUV1Buffer.push_back(vertex_uvset_1[i1]);
- newUV1Buffer.push_back(vertex_uvset_1[i2]);
- }
-
- if (!vertex_atlas.empty())
- {
- newAtlasBuffer.push_back(vertex_atlas[i0]);
- newAtlasBuffer.push_back(vertex_atlas[i1]);
- newAtlasBuffer.push_back(vertex_atlas[i2]);
- }
-
- if (!vertex_boneindices.empty())
- {
- newBoneIndicesBuffer.push_back(vertex_boneindices[i0]);
- newBoneIndicesBuffer.push_back(vertex_boneindices[i1]);
- newBoneIndicesBuffer.push_back(vertex_boneindices[i2]);
- }
-
- if (!vertex_boneweights.empty())
- {
- newBoneWeightsBuffer.push_back(vertex_boneweights[i0]);
- newBoneWeightsBuffer.push_back(vertex_boneweights[i1]);
- newBoneWeightsBuffer.push_back(vertex_boneweights[i2]);
- }
-
- if (!vertex_colors.empty())
- {
- newColorsBuffer.push_back(vertex_colors[i0]);
- newColorsBuffer.push_back(vertex_colors[i1]);
- newColorsBuffer.push_back(vertex_colors[i2]);
- }
-
- newIndexBuffer.push_back(static_cast(newIndexBuffer.size()));
- newIndexBuffer.push_back(static_cast(newIndexBuffer.size()));
- newIndexBuffer.push_back(static_cast(newIndexBuffer.size()));
- }
-
- // For hard surface normals, we created a new mesh in the previous loop through faces, so swap data:
- vertex_positions = newPositionsBuffer;
- vertex_normals = newNormalsBuffer;
- vertex_uvset_0 = newUV0Buffer;
- vertex_uvset_1 = newUV1Buffer;
- vertex_atlas = newAtlasBuffer;
- vertex_colors = newColorsBuffer;
- if (!vertex_boneindices.empty())
- {
- vertex_boneindices = newBoneIndicesBuffer;
- }
- if (!vertex_boneweights.empty())
- {
- vertex_boneweights = newBoneWeightsBuffer;
- }
- indices = newIndexBuffer;
- }
-
- switch (compute)
- {
- case MeshComponent::COMPUTE_NORMALS_HARD:
- break;
-
- case MeshComponent::COMPUTE_NORMALS_SMOOTH:
- {
- // Compute smooth surface normals:
-
- // 1.) Zero normals, they will be averaged later
- for (size_t i = 0; i < vertex_normals.size(); i++)
- {
- vertex_normals[i] = XMFLOAT3(0, 0, 0);
- }
-
- // 2.) Find identical vertices by POSITION, accumulate face normals
- for (size_t i = 0; i < vertex_positions.size(); i++)
- {
- XMFLOAT3& v_search_pos = vertex_positions[i];
-
- for (size_t ind = 0; ind < indices.size() / 3; ++ind)
- {
- uint32_t i0 = indices[ind * 3 + 0];
- uint32_t i1 = indices[ind * 3 + 1];
- uint32_t i2 = indices[ind * 3 + 2];
-
- XMFLOAT3& v0 = vertex_positions[i0];
- XMFLOAT3& v1 = vertex_positions[i1];
- XMFLOAT3& v2 = vertex_positions[i2];
-
- bool match_pos0 =
- fabs(v_search_pos.x - v0.x) < FLT_EPSILON &&
- fabs(v_search_pos.y - v0.y) < FLT_EPSILON &&
- fabs(v_search_pos.z - v0.z) < FLT_EPSILON;
-
- bool match_pos1 =
- fabs(v_search_pos.x - v1.x) < FLT_EPSILON &&
- fabs(v_search_pos.y - v1.y) < FLT_EPSILON &&
- fabs(v_search_pos.z - v1.z) < FLT_EPSILON;
-
- bool match_pos2 =
- fabs(v_search_pos.x - v2.x) < FLT_EPSILON &&
- fabs(v_search_pos.y - v2.y) < FLT_EPSILON &&
- fabs(v_search_pos.z - v2.z) < FLT_EPSILON;
-
- if (match_pos0 || match_pos1 || match_pos2)
- {
- XMVECTOR U = XMLoadFloat3(&v2) - XMLoadFloat3(&v0);
- XMVECTOR V = XMLoadFloat3(&v1) - XMLoadFloat3(&v0);
-
- XMVECTOR N = XMVector3Cross(U, V);
- N = XMVector3Normalize(N);
-
- XMFLOAT3 normal;
- XMStoreFloat3(&normal, N);
-
- vertex_normals[i].x += normal.x;
- vertex_normals[i].y += normal.y;
- vertex_normals[i].z += normal.z;
- }
-
- }
- }
-
- // 3.) Find duplicated vertices by POSITION and UV0 and UV1 and ATLAS and SUBSET and remove them:
- for (auto& subset : subsets)
- {
- for (uint32_t i = 0; i < subset.indexCount - 1; i++)
- {
- uint32_t ind0 = indices[subset.indexOffset + (uint32_t)i];
- const XMFLOAT3& p0 = vertex_positions[ind0];
- const XMFLOAT2& u00 = vertex_uvset_0.empty() ? XMFLOAT2(0, 0) : vertex_uvset_0[ind0];
- const XMFLOAT2& u10 = vertex_uvset_1.empty() ? XMFLOAT2(0, 0) : vertex_uvset_1[ind0];
- const XMFLOAT2& at0 = vertex_atlas.empty() ? XMFLOAT2(0, 0) : vertex_atlas[ind0];
-
- for (uint32_t j = i + 1; j < subset.indexCount; j++)
- {
- uint32_t ind1 = indices[subset.indexOffset + (uint32_t)j];
-
- if (ind1 == ind0)
- {
- continue;
- }
-
- const XMFLOAT3& p1 = vertex_positions[ind1];
- const XMFLOAT2& u01 = vertex_uvset_0.empty() ? XMFLOAT2(0, 0) : vertex_uvset_0[ind1];
- const XMFLOAT2& u11 = vertex_uvset_1.empty() ? XMFLOAT2(0, 0) : vertex_uvset_1[ind1];
- const XMFLOAT2& at1 = vertex_atlas.empty() ? XMFLOAT2(0, 0) : vertex_atlas[ind1];
-
- const bool duplicated_pos =
- fabs(p0.x - p1.x) < FLT_EPSILON &&
- fabs(p0.y - p1.y) < FLT_EPSILON &&
- fabs(p0.z - p1.z) < FLT_EPSILON;
-
- const bool duplicated_uv0 =
- fabs(u00.x - u01.x) < FLT_EPSILON &&
- fabs(u00.y - u01.y) < FLT_EPSILON;
-
- const bool duplicated_uv1 =
- fabs(u10.x - u11.x) < FLT_EPSILON &&
- fabs(u10.y - u11.y) < FLT_EPSILON;
-
- const bool duplicated_atl =
- fabs(at0.x - at1.x) < FLT_EPSILON &&
- fabs(at0.y - at1.y) < FLT_EPSILON;
-
- if (duplicated_pos && duplicated_uv0 && duplicated_uv1 && duplicated_atl)
- {
- // Erase vertices[ind1] because it is a duplicate:
- if (ind1 < vertex_positions.size())
- {
- vertex_positions.erase(vertex_positions.begin() + ind1);
- }
- if (ind1 < vertex_normals.size())
- {
- vertex_normals.erase(vertex_normals.begin() + ind1);
- }
- if (ind1 < vertex_uvset_0.size())
- {
- vertex_uvset_0.erase(vertex_uvset_0.begin() + ind1);
- }
- if (ind1 < vertex_uvset_1.size())
- {
- vertex_uvset_1.erase(vertex_uvset_1.begin() + ind1);
- }
- if (ind1 < vertex_atlas.size())
- {
- vertex_atlas.erase(vertex_atlas.begin() + ind1);
- }
- if (ind1 < vertex_boneindices.size())
- {
- vertex_boneindices.erase(vertex_boneindices.begin() + ind1);
- }
- if (ind1 < vertex_boneweights.size())
- {
- vertex_boneweights.erase(vertex_boneweights.begin() + ind1);
- }
-
- // The vertices[ind1] was removed, so each index after that needs to be updated:
- for (auto& index : indices)
- {
- if (index > ind1 && index > 0)
- {
- index--;
- }
- else if (index == ind1)
- {
- index = ind0;
- }
- }
-
- }
-
- }
- }
-
- }
-
- }
- break;
-
- case MeshComponent::COMPUTE_NORMALS_SMOOTH_FAST:
- {
- for (size_t i = 0; i < vertex_normals.size(); i++)
- {
- vertex_normals[i] = XMFLOAT3(0, 0, 0);
- }
- for (size_t i = 0; i < indices.size() / 3; ++i)
- {
- uint32_t index1 = indices[i * 3];
- uint32_t index2 = indices[i * 3 + 1];
- uint32_t index3 = indices[i * 3 + 2];
-
- XMVECTOR side1 = XMLoadFloat3(&vertex_positions[index1]) - XMLoadFloat3(&vertex_positions[index3]);
- XMVECTOR side2 = XMLoadFloat3(&vertex_positions[index1]) - XMLoadFloat3(&vertex_positions[index2]);
- XMVECTOR N = XMVector3Normalize(XMVector3Cross(side1, side2));
- XMFLOAT3 normal;
- XMStoreFloat3(&normal, N);
-
- vertex_normals[index1].x += normal.x;
- vertex_normals[index1].y += normal.y;
- vertex_normals[index1].z += normal.z;
-
- vertex_normals[index2].x += normal.x;
- vertex_normals[index2].y += normal.y;
- vertex_normals[index2].z += normal.z;
-
- vertex_normals[index3].x += normal.x;
- vertex_normals[index3].y += normal.y;
- vertex_normals[index3].z += normal.z;
- }
- }
- break;
-
- }
-
- vertex_tangents.clear(); // <- will be recomputed
-
- CreateRenderData(); // <- normals will be normalized here!
- }
- void MeshComponent::FlipCulling()
- {
- for (size_t face = 0; face < indices.size() / 3; face++)
- {
- uint32_t i0 = indices[face * 3 + 0];
- uint32_t i1 = indices[face * 3 + 1];
- uint32_t i2 = indices[face * 3 + 2];
-
- indices[face * 3 + 0] = i0;
- indices[face * 3 + 1] = i2;
- indices[face * 3 + 2] = i1;
- }
-
- CreateRenderData();
- }
- void MeshComponent::FlipNormals()
- {
- for (auto& normal : vertex_normals)
- {
- normal.x *= -1;
- normal.y *= -1;
- normal.z *= -1;
- }
-
- CreateRenderData();
- }
- void MeshComponent::Recenter()
- {
- XMFLOAT3 center = aabb.getCenter();
-
- for (auto& pos : vertex_positions)
- {
- pos.x -= center.x;
- pos.y -= center.y;
- pos.z -= center.z;
- }
-
- CreateRenderData();
- }
- void MeshComponent::RecenterToBottom()
- {
- XMFLOAT3 center = aabb.getCenter();
- center.y -= aabb.getHalfWidth().y;
-
- for (auto& pos : vertex_positions)
- {
- pos.x -= center.x;
- pos.y -= center.y;
- pos.z -= center.z;
- }
-
- CreateRenderData();
- }
- Sphere MeshComponent::GetBoundingSphere() const
- {
- Sphere sphere;
- sphere.center = aabb.getCenter();
- sphere.radius = aabb.getRadius();
- return sphere;
- }
-
- void ObjectComponent::ClearLightmap()
- {
- lightmap = Texture();
- lightmapWidth = 0;
- lightmapHeight = 0;
- lightmapIterationCount = 0;
- lightmapTextureData.clear();
- SetLightmapRenderRequest(false);
- }
-
-#if __has_include("OpenImageDenoise/oidn.hpp")
-#define OPEN_IMAGE_DENOISE
-#include "OpenImageDenoise/oidn.hpp"
-#pragma comment(lib,"OpenImageDenoise.lib")
-#pragma comment(lib,"tbb.lib")
-// Also provide OpenImageDenoise.dll and tbb.dll near the exe!
-#endif
- void ObjectComponent::SaveLightmap()
- {
- if (lightmap.IsValid() && has_flag(lightmap.desc.bind_flags, BindFlag::RENDER_TARGET))
- {
- SetLightmapRenderRequest(false);
-
- bool success = wi::helper::saveTextureToMemory(lightmap, lightmapTextureData);
- assert(success);
-
-#ifdef OPEN_IMAGE_DENOISE
- if (success)
- {
- wi::vector texturedata_dst(lightmapTextureData.size());
-
- size_t width = (size_t)lightmapWidth;
- size_t height = (size_t)lightmapHeight;
- {
- // https://github.com/OpenImageDenoise/oidn#c11-api-example
-
- // Create an Intel Open Image Denoise device
- static oidn::DeviceRef device = oidn::newDevice();
- static bool init = false;
- if (!init)
- {
- device.commit();
- init = true;
- }
-
- // Create a denoising filter
- oidn::FilterRef filter = device.newFilter("RTLightmap");
- filter.setImage("color", lightmapTextureData.data(), oidn::Format::Float3, width, height, 0, sizeof(XMFLOAT4));
- filter.setImage("output", texturedata_dst.data(), oidn::Format::Float3, width, height, 0, sizeof(XMFLOAT4));
- filter.commit();
-
- // Filter the image
- filter.execute();
-
- // Check for errors
- const char* errorMessage;
- auto error = device.getError(errorMessage);
- if (error != oidn::Error::None && error != oidn::Error::Cancelled)
- {
- wi::backlog::post(std::string("[OpenImageDenoise error] ") + errorMessage);
- }
- }
-
- lightmapTextureData = std::move(texturedata_dst); // replace old (raw) data with denoised data
- }
-#endif // OPEN_IMAGE_DENOISE
-
- CompressLightmap();
-
- wi::texturehelper::CreateTexture(lightmap, lightmapTextureData.data(), lightmapWidth, lightmapHeight, lightmap.desc.format);
- wi::graphics::GetDevice()->SetName(&lightmap, "lightmap");
- }
- }
- void ObjectComponent::CompressLightmap()
- {
-
- // BC6 Block compression code that uses DirectXTex library, but it's not cross platform, so disabled:
-#if 0
- wi::Timer timer;
- wi::backlog::post("compressing lightmap...");
-
- lightmap.desc.Format = lightmap_block_format;
- lightmap.desc.BindFlags = BindFlag::SHADER_RESOURCE;
-
- static constexpr wi::graphics::FORMAT lightmap_block_format = wi::graphics::FORMAT_BC6H_UF16;
- static constexpr uint32_t lightmap_blocksize = wi::graphics::GetFormatBlockSize(lightmap_block_format);
- static_assert(lightmap_blocksize == 4u);
- const uint32_t bc6_width = lightmapWidth / lightmap_blocksize;
- const uint32_t bc6_height = lightmapHeight / lightmap_blocksize;
- wi::vector bc6_data;
- bc6_data.resize(sizeof(XMFLOAT4) * bc6_width * bc6_height);
- const XMFLOAT4* raw_data = (const XMFLOAT4*)lightmapTextureData.data();
-
- for (uint32_t x = 0; x < bc6_width; ++x)
- {
- for (uint32_t y = 0; y < bc6_height; ++y)
- {
- uint32_t bc6_idx = x + y * bc6_width;
- uint8_t* ptr = (uint8_t*)((XMFLOAT4*)bc6_data.data() + bc6_idx);
-
- XMVECTOR raw_vec[lightmap_blocksize * lightmap_blocksize];
- for (uint32_t i = 0; i < lightmap_blocksize; ++i)
- {
- for (uint32_t j = 0; j < lightmap_blocksize; ++j)
- {
- uint32_t raw_idx = (x * lightmap_blocksize + i) + (y * lightmap_blocksize + j) * lightmapWidth;
- uint32_t block_idx = i + j * lightmap_blocksize;
- raw_vec[block_idx] = XMLoadFloat4(raw_data + raw_idx);
- }
- }
- static_assert(arraysize(raw_vec) == 16); // it will work only for a certain block size!
- D3DXEncodeBC6HU(ptr, raw_vec, 0);
- }
- }
-
- lightmapTextureData = std::move(bc6_data); // replace old (raw) data with compressed data
-
- wi::backlog::post(
- "compressing lightmap [" +
- std::to_string(lightmapWidth) +
- "x" +
- std::to_string(lightmapHeight) +
- "] finished in " +
- std::to_string(timer.elapsed_seconds()) +
- " seconds"
- );
-#else
-
- // Simple compression to R11G11B10_FLOAT format:
- using namespace PackedVector;
- wi::vector packed_data;
- packed_data.resize(sizeof(XMFLOAT3PK) * lightmapWidth * lightmapHeight);
- XMFLOAT3PK* packed_ptr = (XMFLOAT3PK*)packed_data.data();
- XMFLOAT4* raw_ptr = (XMFLOAT4*)lightmapTextureData.data();
-
- uint32_t texelcount = lightmapWidth * lightmapHeight;
- for (uint32_t i = 0; i < texelcount; ++i)
- {
- XMStoreFloat3PK(packed_ptr + i, XMLoadFloat4(raw_ptr + i));
- }
-
- lightmapTextureData = std::move(packed_data);
- lightmap.desc.format = Format::R11G11B10_FLOAT;
- lightmap.desc.bind_flags = BindFlag::SHADER_RESOURCE;
-
-#endif
- }
-
- void ArmatureComponent::CreateRenderData()
- {
- GraphicsDevice* device = wi::graphics::GetDevice();
-
- GPUBufferDesc bd;
- bd.size = sizeof(ShaderTransform) * (uint32_t)boneCollection.size();
- bd.bind_flags = BindFlag::SHADER_RESOURCE;
- bd.misc_flags = ResourceMiscFlag::BUFFER_RAW;
- bd.stride = sizeof(ShaderTransform);
-
- bool success = device->CreateBuffer(&bd, nullptr, &boneBuffer);
- assert(success);
- device->SetName(&boneBuffer, "ArmatureComponent::boneBuffer");
- descriptor_srv = device->GetDescriptorIndex(&boneBuffer, SubresourceType::SRV);
- }
-
- AnimationComponent::AnimationChannel::PathDataType AnimationComponent::AnimationChannel::GetPathDataType() const
- {
- switch (path)
- {
- case wi::scene::AnimationComponent::AnimationChannel::Path::TRANSLATION:
- return PathDataType::Float3;
- case wi::scene::AnimationComponent::AnimationChannel::Path::ROTATION:
- return PathDataType::Float4;
- case wi::scene::AnimationComponent::AnimationChannel::Path::SCALE:
- return PathDataType::Float3;
- case wi::scene::AnimationComponent::AnimationChannel::Path::WEIGHTS:
- return PathDataType::Weights;
-
- case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_COLOR:
- return PathDataType::Float3;
- case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY:
- case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_RANGE:
- case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE:
- case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE:
- return PathDataType::Float;
-
- case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_PLAY:
- case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_STOP:
- return PathDataType::Event;
- case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_VOLUME:
- return PathDataType::Float;
-
- case wi::scene::AnimationComponent::AnimationChannel::Path::EMITTER_EMITCOUNT:
- return PathDataType::Float;
-
- case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_FOV:
- case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_FOCAL_LENGTH:
- case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SIZE:
- return PathDataType::Float;
- case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SHAPE:
- return PathDataType::Float2;
-
- case wi::scene::AnimationComponent::AnimationChannel::Path::SCRIPT_PLAY:
- case wi::scene::AnimationComponent::AnimationChannel::Path::SCRIPT_STOP:
- return PathDataType::Event;
-
- case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_COLOR:
- case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_EMISSIVE:
- case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_TEXMULADD:
- return PathDataType::Float4;
- case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_ROUGHNESS:
- case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_REFLECTANCE:
- case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_METALNESS:
- return PathDataType::Float;
-
- default:
- assert(0);
- break;
- }
- return PathDataType::Event;
- }
-
- void SoftBodyPhysicsComponent::CreateFromMesh(const MeshComponent& mesh)
- {
- vertex_positions_simulation.resize(mesh.vertex_positions.size());
- vertex_tangents_tmp.resize(mesh.vertex_tangents.size());
- vertex_tangents_simulation.resize(mesh.vertex_tangents.size());
-
- XMMATRIX W = XMLoadFloat4x4(&worldMatrix);
- XMFLOAT3 _min = XMFLOAT3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max());
- XMFLOAT3 _max = XMFLOAT3(std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::lowest());
- for (size_t i = 0; i < vertex_positions_simulation.size(); ++i)
- {
- XMFLOAT3 pos = mesh.vertex_positions[i];
- XMStoreFloat3(&pos, XMVector3Transform(XMLoadFloat3(&pos), W));
- XMFLOAT3 nor = mesh.vertex_normals.empty() ? XMFLOAT3(1, 1, 1) : mesh.vertex_normals[i];
- XMStoreFloat3(&nor, XMVector3Normalize(XMVector3TransformNormal(XMLoadFloat3(&nor), W)));
- const uint8_t wind = mesh.vertex_windweights.empty() ? 0xFF : mesh.vertex_windweights[i];
- vertex_positions_simulation[i].FromFULL(pos, nor, wind);
- _min = wi::math::Min(_min, pos);
- _max = wi::math::Max(_max, pos);
- }
- aabb = AABB(_min, _max);
-
- if(physicsToGraphicsVertexMapping.empty())
- {
- // Create a mapping that maps unique vertex positions to all vertex indices that share that. Unique vertex positions will make up the physics mesh:
- wi::unordered_map uniquePositions;
- graphicsToPhysicsVertexMapping.resize(mesh.vertex_positions.size());
- physicsToGraphicsVertexMapping.clear();
- weights.clear();
-
- for (size_t i = 0; i < mesh.vertex_positions.size(); ++i)
- {
- const XMFLOAT3& position = mesh.vertex_positions[i];
-
- size_t hashes[] = {
- std::hash{}(position.x),
- std::hash{}(position.y),
- std::hash{}(position.z),
- };
- size_t vertexHash = (((hashes[0] ^ (hashes[1] << 1) >> 1) ^ (hashes[2] << 1)) >> 1);
-
- if (uniquePositions.count(vertexHash) == 0)
- {
- uniquePositions[vertexHash] = (uint32_t)physicsToGraphicsVertexMapping.size();
- physicsToGraphicsVertexMapping.push_back((uint32_t)i);
- }
- graphicsToPhysicsVertexMapping[i] = uniquePositions[vertexHash];
- }
-
- weights.resize(physicsToGraphicsVertexMapping.size());
- std::fill(weights.begin(), weights.end(), 1.0f);
- }
- }
-
- void CameraComponent::CreatePerspective(float newWidth, float newHeight, float newNear, float newFar, float newFOV)
- {
- zNearP = newNear;
- zFarP = newFar;
- width = newWidth;
- height = newHeight;
- fov = newFOV;
-
- SetCustomProjectionEnabled(false);
-
- UpdateCamera();
- }
- void CameraComponent::UpdateCamera()
- {
- if (!IsCustomProjectionEnabled())
- {
- XMStoreFloat4x4(&Projection, XMMatrixPerspectiveFovLH(fov, width / height, zFarP, zNearP)); // reverse zbuffer!
- Projection.m[2][0] = jitter.x;
- Projection.m[2][1] = jitter.y;
- }
-
- XMVECTOR _Eye = XMLoadFloat3(&Eye);
- XMVECTOR _At = XMLoadFloat3(&At);
- XMVECTOR _Up = XMLoadFloat3(&Up);
-
- XMMATRIX _V = XMMatrixLookToLH(_Eye, _At, _Up);
- XMStoreFloat4x4(&View, _V);
-
- XMMATRIX _P = XMLoadFloat4x4(&Projection);
- XMMATRIX _InvP = XMMatrixInverse(nullptr, _P);
- XMStoreFloat4x4(&InvProjection, _InvP);
-
- XMMATRIX _VP = XMMatrixMultiply(_V, _P);
- XMStoreFloat4x4(&View, _V);
- XMStoreFloat4x4(&VP, _VP);
- XMStoreFloat4x4(&InvView, XMMatrixInverse(nullptr, _V));
- XMStoreFloat4x4(&InvVP, XMMatrixInverse(nullptr, _VP));
- XMStoreFloat4x4(&Projection, _P);
- XMStoreFloat4x4(&InvProjection, XMMatrixInverse(nullptr, _P));
-
- frustum.Create(_VP);
- }
- void CameraComponent::TransformCamera(const TransformComponent& transform)
- {
- XMMATRIX W = XMLoadFloat4x4(&transform.world);
-
- XMVECTOR _Eye = XMVector3Transform(XMVectorSet(0, 0, 0, 1), W);
- XMVECTOR _At = XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 0, 1, 0), W));
- XMVECTOR _Up = XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W));
-
- XMMATRIX _V = XMMatrixLookToLH(_Eye, _At, _Up);
- XMStoreFloat4x4(&View, _V);
-
- XMStoreFloat3x3(&rotationMatrix, XMMatrixInverse(nullptr, _V));
-
- XMStoreFloat3(&Eye, _Eye);
- XMStoreFloat3(&At, _At);
- XMStoreFloat3(&Up, _Up);
- }
- void CameraComponent::Reflect(const XMFLOAT4& plane)
- {
- XMVECTOR _Eye = XMLoadFloat3(&Eye);
- XMVECTOR _At = XMLoadFloat3(&At);
- XMVECTOR _Up = XMLoadFloat3(&Up);
- XMMATRIX _Ref = XMMatrixReflect(XMLoadFloat4(&plane));
-
- // reverse clipping if behind clip plane ("if underwater")
- clipPlane = plane;
- float d = XMVectorGetX(XMPlaneDotCoord(XMLoadFloat4(&clipPlane), _Eye));
- if (d < 0)
- {
- clipPlane.x *= -1;
- clipPlane.y *= -1;
- clipPlane.z *= -1;
- clipPlane.w *= -1;
- }
-
- _Eye = XMVector3Transform(_Eye, _Ref);
- _At = XMVector3TransformNormal(_At, _Ref);
- _Up = XMVector3TransformNormal(_Up, _Ref);
-
- XMStoreFloat3(&Eye, _Eye);
- XMStoreFloat3(&At, _At);
- XMStoreFloat3(&Up, _Up);
-
- UpdateCamera();
- }
- void CameraComponent::Lerp(const CameraComponent& a, const CameraComponent& b, float t)
- {
- SetDirty();
-
- width = wi::math::Lerp(a.width, b.width, t);
- height = wi::math::Lerp(a.height, b.height, t);
- zNearP = wi::math::Lerp(a.zNearP, b.zNearP, t);
- zFarP = wi::math::Lerp(a.zFarP, b.zFarP, t);
- fov = wi::math::Lerp(a.fov, b.fov, t);
- focal_length = wi::math::Lerp(a.focal_length, b.focal_length, t);
- aperture_size = wi::math::Lerp(a.aperture_size, b.aperture_size, t);
- aperture_shape = wi::math::Lerp(a.aperture_shape, b.aperture_shape, t);
- }
-
- void ScriptComponent::CreateFromFile(const std::string& filename)
- {
- this->filename = filename;
- resource = wi::resourcemanager::Load(filename, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA);
- script.clear(); // will be created on first Update()
- }
-
-
-
const uint32_t small_subtask_groupsize = 64u;
void Scene::Update(float dt)
@@ -1563,6 +34,19 @@ namespace wi::scene
// So GPU persistent resources need to be created accordingly for them too:
RunScriptUpdateSystem(ctx);
+ // Terrains updates kick off:
+ if (dt > 0)
+ {
+ // Because this also spawns render tasks, this must not be during dt == 0 (eg. background loading)
+ for (size_t i = 0; i < terrains.GetCount(); ++i)
+ {
+ wi::terrain::Terrain& terrain = terrains[i];
+ terrain.terrainEntity = terrains.GetEntity(i);
+ terrain.scene = this;
+ terrain.Generation_Update(camera);
+ }
+ }
+
GraphicsDevice* device = wi::graphics::GetDevice();
instanceArraySize = objects.GetCount() + hairs.GetCount() + emitters.GetCount();
@@ -1973,6 +457,14 @@ namespace wi::scene
}
std::swap(ddgi.color_texture[0], ddgi.color_texture[1]);
std::swap(ddgi.depth_texture[0], ddgi.depth_texture[1]);
+ ddgi.grid_min = bounds.getMin();
+ ddgi.grid_min.x -= 1;
+ ddgi.grid_min.y -= 1;
+ ddgi.grid_min.z -= 1;
+ ddgi.grid_max = bounds.getMax();
+ ddgi.grid_max.x += 1;
+ ddgi.grid_max.y += 1;
+ ddgi.grid_max.z += 1;
}
else if (ddgi.color_texture[1].IsValid()) // if just color_texture[0] is valid, it could be that ddgi was serialized, that's why we check color_texture[1] here
{
@@ -2095,19 +587,14 @@ namespace wi::scene
shaderscene.ddgi.color_texture = device->GetDescriptorIndex(&ddgi.color_texture[0], SubresourceType::SRV);
shaderscene.ddgi.depth_texture = device->GetDescriptorIndex(&ddgi.depth_texture[0], SubresourceType::SRV);
shaderscene.ddgi.offset_buffer = device->GetDescriptorIndex(&ddgi.offset_buffer, SubresourceType::SRV);
- shaderscene.ddgi.grid_min.x = shaderscene.aabb_min.x - 1;
- shaderscene.ddgi.grid_min.y = shaderscene.aabb_min.y - 1;
- shaderscene.ddgi.grid_min.z = shaderscene.aabb_min.z - 1;
- float3 grid_max = shaderscene.aabb_max;
- grid_max.x += 1;
- grid_max.y += 1;
- grid_max.z += 1;
- shaderscene.ddgi.grid_extents.x = abs(grid_max.x - shaderscene.ddgi.grid_min.x);
- shaderscene.ddgi.grid_extents.y = abs(grid_max.y - shaderscene.ddgi.grid_min.y);
- shaderscene.ddgi.grid_extents.z = abs(grid_max.z - shaderscene.ddgi.grid_min.z);
+ shaderscene.ddgi.grid_min = ddgi.grid_min;
+ shaderscene.ddgi.grid_extents.x = abs(ddgi.grid_max.x - ddgi.grid_min.x);
+ shaderscene.ddgi.grid_extents.y = abs(ddgi.grid_max.y - ddgi.grid_min.y);
+ shaderscene.ddgi.grid_extents.z = abs(ddgi.grid_max.z - ddgi.grid_min.z);
shaderscene.ddgi.grid_extents_rcp.x = 1.0f / shaderscene.ddgi.grid_extents.x;
shaderscene.ddgi.grid_extents_rcp.y = 1.0f / shaderscene.ddgi.grid_extents.y;
shaderscene.ddgi.grid_extents_rcp.z = 1.0f / shaderscene.ddgi.grid_extents.z;
+ shaderscene.ddgi.smooth_backface = ddgi.smooth_backface;
shaderscene.ddgi.cell_size.x = shaderscene.ddgi.grid_extents.x / (ddgi.grid_dimensions.x - 1);
shaderscene.ddgi.cell_size.y = shaderscene.ddgi.grid_extents.y / (ddgi.grid_dimensions.y - 1);
shaderscene.ddgi.cell_size.z = shaderscene.ddgi.grid_extents.z / (ddgi.grid_dimensions.z - 1);
diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h
index 475261444..9ea38c056 100644
--- a/WickedEngine/wiScene.h
+++ b/WickedEngine/wiScene.h
@@ -1,1542 +1,22 @@
#pragma once
#include "CommonInclude.h"
-#include "wiEnums.h"
-#include "wiPrimitive.h"
-#include "wiEmittedParticle.h"
-#include "wiHairParticle.h"
-#include "shaders/ShaderInterop_Renderer.h"
#include "wiJobSystem.h"
-#include "wiAudio.h"
-#include "wiResourceManager.h"
#include "wiSpinLock.h"
#include "wiGPUBVH.h"
-#include "wiOcean.h"
#include "wiSprite.h"
#include "wiMath.h"
#include "wiECS.h"
-#include "wiVector.h"
-#include "wiRectPacker.h"
-#include "wiUnorderedSet.h"
+#include "wiScene_Components.h"
+#include "wiEmittedParticle.h"
+#include "wiHairParticle.h"
+#include "wiTerrain.h"
#include
#include
#include
-namespace wi
-{
- class Archive;
-}
-
namespace wi::scene
{
- struct NameComponent
- {
- std::string name;
-
- inline void operator=(const std::string& str) { name = str; }
- inline void operator=(std::string&& str) { name = std::move(str); }
- inline bool operator==(const std::string& str) const { return name.compare(str) == 0; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct LayerComponent
- {
- uint32_t layerMask = ~0u;
-
- // Non-serialized attributes:
- uint32_t propagationMask = ~0u; // This shouldn't be modified by user usually
-
- inline uint32_t GetLayerMask() const { return layerMask & propagationMask; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct TransformComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- DIRTY = 1 << 0,
- };
- uint32_t _flags = DIRTY;
-
- XMFLOAT3 scale_local = XMFLOAT3(1, 1, 1);
- XMFLOAT4 rotation_local = XMFLOAT4(0, 0, 0, 1); // this is a quaternion
- XMFLOAT3 translation_local = XMFLOAT3(0, 0, 0);
-
- // Non-serialized attributes:
-
- // The world matrix can be computed from local scale, rotation, translation
- // - by calling UpdateTransform()
- // - or by calling SetDirty() and letting the TransformUpdateSystem handle the updating
- XMFLOAT4X4 world = wi::math::IDENTITY_MATRIX;
-
- inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
- inline bool IsDirty() const { return _flags & DIRTY; }
-
- XMFLOAT3 GetPosition() const;
- XMFLOAT4 GetRotation() const;
- XMFLOAT3 GetScale() const;
- XMVECTOR GetPositionV() const;
- XMVECTOR GetRotationV() const;
- XMVECTOR GetScaleV() const;
- // Computes the local space matrix from scale, rotation, translation and returns it
- XMMATRIX GetLocalMatrix() const;
- // Applies the local space to the world space matrix. This overwrites world matrix
- void UpdateTransform();
- // Apply a parent transform relative to the local space. This overwrites world matrix
- void UpdateTransform_Parented(const TransformComponent& parent);
- // Apply the world matrix to the local space. This overwrites scale, rotation, translation
- void ApplyTransform();
- // Clears the local space. This overwrites scale, rotation, translation
- void ClearTransform();
- void Translate(const XMFLOAT3& value);
- void Translate(const XMVECTOR& value);
- void RotateRollPitchYaw(const XMFLOAT3& value);
- void Rotate(const XMFLOAT4& quaternion);
- void Rotate(const XMVECTOR& quaternion);
- void Scale(const XMFLOAT3& value);
- void Scale(const XMVECTOR& value);
- void MatrixTransform(const XMFLOAT4X4& matrix);
- void MatrixTransform(const XMMATRIX& matrix);
- void Lerp(const TransformComponent& a, const TransformComponent& b, float t);
- void CatmullRom(const TransformComponent& a, const TransformComponent& b, const TransformComponent& c, const TransformComponent& d, float t);
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct HierarchyComponent
- {
- wi::ecs::Entity parentID = wi::ecs::INVALID_ENTITY;
- uint32_t layerMask_bind; // saved child layermask at the time of binding
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct MaterialComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- DIRTY = 1 << 0,
- CAST_SHADOW = 1 << 1,
- _DEPRECATED_PLANAR_REFLECTION = 1 << 2,
- _DEPRECATED_WATER = 1 << 3,
- _DEPRECATED_FLIP_NORMALMAP = 1 << 4,
- USE_VERTEXCOLORS = 1 << 5,
- SPECULAR_GLOSSINESS_WORKFLOW = 1 << 6,
- OCCLUSION_PRIMARY = 1 << 7,
- OCCLUSION_SECONDARY = 1 << 8,
- USE_WIND = 1 << 9,
- DISABLE_RECEIVE_SHADOW = 1 << 10,
- DOUBLE_SIDED = 1 << 11,
- OUTLINE = 1 << 12,
- };
- uint32_t _flags = CAST_SHADOW;
-
- enum SHADERTYPE
- {
- SHADERTYPE_PBR,
- SHADERTYPE_PBR_PLANARREFLECTION,
- SHADERTYPE_PBR_PARALLAXOCCLUSIONMAPPING,
- SHADERTYPE_PBR_ANISOTROPIC,
- SHADERTYPE_WATER,
- SHADERTYPE_CARTOON,
- SHADERTYPE_UNLIT,
- SHADERTYPE_PBR_CLOTH,
- SHADERTYPE_PBR_CLEARCOAT,
- SHADERTYPE_PBR_CLOTH_CLEARCOAT,
- SHADERTYPE_COUNT
- } shaderType = SHADERTYPE_PBR;
- static_assert(SHADERTYPE_COUNT == SHADERTYPE_BIN_COUNT, "These values must match!");
-
- inline static const wi::vector shaderTypeDefines[] = {
- {}, // SHADERTYPE_PBR,
- {"PLANARREFLECTION"}, // SHADERTYPE_PBR_PLANARREFLECTION,
- {"PARALLAXOCCLUSIONMAPPING"}, // SHADERTYPE_PBR_PARALLAXOCCLUSIONMAPPING,
- {"ANISOTROPIC"}, // SHADERTYPE_PBR_ANISOTROPIC,
- {"WATER"}, // SHADERTYPE_WATER,
- {"CARTOON"}, // SHADERTYPE_CARTOON,
- {"UNLIT"}, // SHADERTYPE_UNLIT,
- {"SHEEN"}, // SHADERTYPE_PBR_CLOTH,
- {"CLEARCOAT"}, // SHADERTYPE_PBR_CLEARCOAT,
- {"SHEEN", "CLEARCOAT"}, // SHADERTYPE_PBR_CLOTH_CLEARCOAT,
- };
- static_assert(SHADERTYPE_COUNT == arraysize(shaderTypeDefines), "These values must match!");
-
- wi::enums::STENCILREF engineStencilRef = wi::enums::STENCILREF_DEFAULT;
- uint8_t userStencilRef = 0;
- wi::enums::BLENDMODE userBlendMode = wi::enums::BLENDMODE_OPAQUE;
-
- XMFLOAT4 baseColor = XMFLOAT4(1, 1, 1, 1);
- XMFLOAT4 specularColor = XMFLOAT4(1, 1, 1, 1);
- XMFLOAT4 emissiveColor = XMFLOAT4(1, 1, 1, 0);
- XMFLOAT4 subsurfaceScattering = XMFLOAT4(1, 1, 1, 0);
- XMFLOAT4 texMulAdd = XMFLOAT4(1, 1, 0, 0);
- float roughness = 0.2f;
- float reflectance = 0.02f;
- float metalness = 0.0f;
- float normalMapStrength = 1.0f;
- float parallaxOcclusionMapping = 0.0f;
- float displacementMapping = 0.0f;
- float refraction = 0.0f;
- float transmission = 0.0f;
- float alphaRef = 1.0f;
-
- XMFLOAT4 sheenColor = XMFLOAT4(1, 1, 1, 1);
- float sheenRoughness = 0;
- float clearcoat = 0;
- float clearcoatRoughness = 0;
-
- wi::graphics::ShadingRate shadingRate = wi::graphics::ShadingRate::RATE_1X1;
-
- XMFLOAT2 texAnimDirection = XMFLOAT2(0, 0);
- float texAnimFrameRate = 0.0f;
- float texAnimElapsedTime = 0.0f;
-
- enum TEXTURESLOT
- {
- BASECOLORMAP,
- NORMALMAP,
- SURFACEMAP,
- EMISSIVEMAP,
- DISPLACEMENTMAP,
- OCCLUSIONMAP,
- TRANSMISSIONMAP,
- SHEENCOLORMAP,
- SHEENROUGHNESSMAP,
- CLEARCOATMAP,
- CLEARCOATROUGHNESSMAP,
- CLEARCOATNORMALMAP,
- SPECULARMAP,
-
- TEXTURESLOT_COUNT
- };
- struct TextureMap
- {
- std::string name;
- uint32_t uvset = 0;
- wi::Resource resource;
- const wi::graphics::GPUResource* GetGPUResource() const
- {
- if (!resource.IsValid() || !resource.GetTexture().IsValid())
- return nullptr;
- return &resource.GetTexture();
- }
- int GetUVSet() const
- {
- if (!resource.IsValid() || !resource.GetTexture().IsValid())
- return -1;
- return (int)uvset;
- }
- };
- TextureMap textures[TEXTURESLOT_COUNT];
-
- int customShaderID = -1;
- uint4 userdata = uint4(0, 0, 0, 0); // can be accessed by custom shader
-
- // Non-serialized attributes:
- uint32_t layerMask = ~0u;
-
- // User stencil value can be in range [0, 15]
- inline void SetUserStencilRef(uint8_t value)
- {
- assert(value < 16);
- userStencilRef = value & 0x0F;
- }
- uint32_t GetStencilRef() const;
-
- inline float GetOpacity() const { return baseColor.w; }
- inline float GetEmissiveStrength() const { return emissiveColor.w; }
- inline int GetCustomShaderID() const { return customShaderID; }
-
- inline bool HasPlanarReflection() const { return shaderType == SHADERTYPE_PBR_PLANARREFLECTION || shaderType == SHADERTYPE_WATER; }
-
- inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
- inline bool IsDirty() const { return _flags & DIRTY; }
-
- inline void SetCastShadow(bool value) { SetDirty(); if (value) { _flags |= CAST_SHADOW; } else { _flags &= ~CAST_SHADOW; } }
- inline void SetReceiveShadow(bool value) { SetDirty(); if (value) { _flags &= ~DISABLE_RECEIVE_SHADOW; } else { _flags |= DISABLE_RECEIVE_SHADOW; } }
- inline void SetOcclusionEnabled_Primary(bool value) { SetDirty(); if (value) { _flags |= OCCLUSION_PRIMARY; } else { _flags &= ~OCCLUSION_PRIMARY; } }
- inline void SetOcclusionEnabled_Secondary(bool value) { SetDirty(); if (value) { _flags |= OCCLUSION_SECONDARY; } else { _flags &= ~OCCLUSION_SECONDARY; } }
-
- inline wi::enums::BLENDMODE GetBlendMode() const { if (userBlendMode == wi::enums::BLENDMODE_OPAQUE && (GetRenderTypes() & wi::enums::RENDERTYPE_TRANSPARENT)) return wi::enums::BLENDMODE_ALPHA; else return userBlendMode; }
- inline bool IsCastingShadow() const { return _flags & CAST_SHADOW; }
- inline bool IsAlphaTestEnabled() const { return alphaRef <= 1.0f - 1.0f / 256.0f; }
- inline bool IsUsingVertexColors() const { return _flags & USE_VERTEXCOLORS; }
- inline bool IsUsingWind() const { return _flags & USE_WIND; }
- inline bool IsReceiveShadow() const { return (_flags & DISABLE_RECEIVE_SHADOW) == 0; }
- inline bool IsUsingSpecularGlossinessWorkflow() const { return _flags & SPECULAR_GLOSSINESS_WORKFLOW; }
- inline bool IsOcclusionEnabled_Primary() const { return _flags & OCCLUSION_PRIMARY; }
- inline bool IsOcclusionEnabled_Secondary() const { return _flags & OCCLUSION_SECONDARY; }
- inline bool IsCustomShader() const { return customShaderID >= 0; }
- inline bool IsDoubleSided() const { return _flags & DOUBLE_SIDED; }
- inline bool IsOutlineEnabled() const { return _flags & OUTLINE; }
-
- inline void SetBaseColor(const XMFLOAT4& value) { SetDirty(); baseColor = value; }
- inline void SetSpecularColor(const XMFLOAT4& value) { SetDirty(); specularColor = value; }
- inline void SetEmissiveColor(const XMFLOAT4& value) { SetDirty(); emissiveColor = value; }
- inline void SetRoughness(float value) { SetDirty(); roughness = value; }
- inline void SetReflectance(float value) { SetDirty(); reflectance = value; }
- inline void SetMetalness(float value) { SetDirty(); metalness = value; }
- inline void SetEmissiveStrength(float value) { SetDirty(); emissiveColor.w = value; }
- inline void SetTransmissionAmount(float value) { SetDirty(); transmission = value; }
- inline void SetRefractionAmount(float value) { SetDirty(); refraction = value; }
- inline void SetNormalMapStrength(float value) { SetDirty(); normalMapStrength = value; }
- inline void SetParallaxOcclusionMapping(float value) { SetDirty(); parallaxOcclusionMapping = value; }
- inline void SetDisplacementMapping(float value) { SetDirty(); displacementMapping = value; }
- inline void SetSubsurfaceScatteringColor(XMFLOAT3 value)
- {
- SetDirty();
- subsurfaceScattering.x = value.x;
- subsurfaceScattering.y = value.y;
- subsurfaceScattering.z = value.z;
- }
- inline void SetSubsurfaceScatteringAmount(float value) { SetDirty(); subsurfaceScattering.w = value; }
- inline void SetOpacity(float value) { SetDirty(); baseColor.w = value; }
- inline void SetAlphaRef(float value) { SetDirty(); alphaRef = value; }
- inline void SetUseVertexColors(bool value) { SetDirty(); if (value) { _flags |= USE_VERTEXCOLORS; } else { _flags &= ~USE_VERTEXCOLORS; } }
- inline void SetUseWind(bool value) { SetDirty(); if (value) { _flags |= USE_WIND; } else { _flags &= ~USE_WIND; } }
- inline void SetUseSpecularGlossinessWorkflow(bool value) { SetDirty(); if (value) { _flags |= SPECULAR_GLOSSINESS_WORKFLOW; } else { _flags &= ~SPECULAR_GLOSSINESS_WORKFLOW; } }
- inline void SetSheenColor(const XMFLOAT3& value)
- {
- sheenColor = XMFLOAT4(value.x, value.y, value.z, sheenColor.w);
- SetDirty();
- }
- inline void SetSheenRoughness(float value) { sheenRoughness = value; SetDirty(); }
- inline void SetClearcoatFactor(float value) { clearcoat = value; SetDirty(); }
- inline void SetClearcoatRoughness(float value) { clearcoatRoughness = value; SetDirty(); }
- inline void SetCustomShaderID(int id) { customShaderID = id; }
- inline void DisableCustomShader() { customShaderID = -1; }
- inline void SetDoubleSided(bool value = true) { if (value) { _flags |= DOUBLE_SIDED; } else { _flags &= ~DOUBLE_SIDED; } }
- inline void SetOutlineEnabled(bool value = true) { if (value) { _flags |= OUTLINE; } else { _flags &= ~OUTLINE; } }
-
- // The MaterialComponent will be written to ShaderMaterial (a struct that is optimized for GPU use)
- void WriteShaderMaterial(ShaderMaterial* dest) const;
-
- // Retrieve the array of textures from the material
- void WriteTextures(const wi::graphics::GPUResource** dest, int count) const;
-
- // Returns the bitwise OR of all the RENDERTYPE flags applicable to this material
- uint32_t GetRenderTypes() const;
-
- // Create constant buffer and texture resources for GPU
- void CreateRenderData();
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct MeshComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- RENDERABLE = 1 << 0,
- DOUBLE_SIDED = 1 << 1,
- DYNAMIC = 1 << 2,
- _DEPRECATED_TERRAIN = 1 << 3,
- _DEPRECATED_DIRTY_MORPH = 1 << 4,
- _DEPRECATED_DIRTY_BINDLESS = 1 << 5,
- TLAS_FORCE_DOUBLE_SIDED = 1 << 6,
- };
- uint32_t _flags = RENDERABLE;
-
- wi::vector vertex_positions;
- wi::vector vertex_normals;
- wi::vector vertex_tangents;
- wi::vector vertex_uvset_0;
- wi::vector vertex_uvset_1;
- wi::vector vertex_boneindices;
- wi::vector vertex_boneweights;
- wi::vector vertex_atlas;
- wi::vector vertex_colors;
- wi::vector vertex_windweights;
- wi::vector indices;
-
- struct MeshSubset
- {
- wi::ecs::Entity materialID = wi::ecs::INVALID_ENTITY;
- uint32_t indexOffset = 0;
- uint32_t indexCount = 0;
-
- // Non-serialized attributes:
- uint32_t materialIndex = 0;
- };
- wi::vector subsets;
-
- float tessellationFactor = 0.0f;
- wi::ecs::Entity armatureID = wi::ecs::INVALID_ENTITY;
-
- struct MorphTarget
- {
- wi::vector vertex_positions;
- wi::vector vertex_normals;
- wi::vector sparse_indices; // optional, these can be used to target vertices indirectly
- float weight = 0;
- };
- wi::vector morph_targets;
-
- uint32_t subsets_per_lod = 0; // this needs to be specified if there are multiple LOD levels
-
- // Non-serialized attributes:
- wi::primitive::AABB aabb;
- wi::graphics::GPUBuffer generalBuffer; // index buffer + all static vertex buffers
- wi::graphics::GPUBuffer streamoutBuffer; // all dynamic vertex buffers
- struct BufferView
- {
- uint64_t offset = ~0ull;
- uint64_t size = 0ull;
- int subresource_srv = -1;
- int descriptor_srv = -1;
- int subresource_uav = -1;
- int descriptor_uav = -1;
-
- constexpr bool IsValid() const
- {
- return offset != ~0ull;
- }
- };
- BufferView ib;
- BufferView vb_pos_nor_wind;
- BufferView vb_tan;
- BufferView vb_uvs;
- BufferView vb_atl;
- BufferView vb_col;
- BufferView vb_bon;
- BufferView so_pos_nor_wind;
- BufferView so_tan;
- BufferView so_pre;
- wi::vector vertex_subsets;
- uint32_t geometryOffset = 0;
- uint32_t meshletCount = 0;
-
- wi::vector BLASes; // one BLAS per LOD
- enum BLAS_STATE
- {
- BLAS_STATE_NEEDS_REBUILD,
- BLAS_STATE_NEEDS_REFIT,
- BLAS_STATE_COMPLETE,
- };
- mutable BLAS_STATE BLAS_state = BLAS_STATE_NEEDS_REBUILD;
-
- mutable bool dirty_morph = false;
-
- inline void SetRenderable(bool value) { if (value) { _flags |= RENDERABLE; } else { _flags &= ~RENDERABLE; } }
- inline void SetDoubleSided(bool value) { if (value) { _flags |= DOUBLE_SIDED; } else { _flags &= ~DOUBLE_SIDED; } }
- inline void SetDynamic(bool value) { if (value) { _flags |= DYNAMIC; } else { _flags &= ~DYNAMIC; } }
-
- inline bool IsRenderable() const { return _flags & RENDERABLE; }
- inline bool IsDoubleSided() const { return _flags & DOUBLE_SIDED; }
- inline bool IsDynamic() const { return _flags & DYNAMIC; }
-
- inline float GetTessellationFactor() const { return tessellationFactor; }
- inline wi::graphics::IndexBufferFormat GetIndexFormat() const { return vertex_positions.size() > 65536 ? wi::graphics::IndexBufferFormat::UINT32 : wi::graphics::IndexBufferFormat::UINT16; }
- inline size_t GetIndexStride() const { return GetIndexFormat() == wi::graphics::IndexBufferFormat::UINT32 ? sizeof(uint32_t) : sizeof(uint16_t); }
- inline bool IsSkinned() const { return armatureID != wi::ecs::INVALID_ENTITY; }
- inline uint32_t GetLODCount() const { return subsets_per_lod == 0 ? 1 : ((uint32_t)subsets.size() / subsets_per_lod); }
- inline void GetLODSubsetRange(uint32_t lod, uint32_t& first_subset, uint32_t& last_subset) const
- {
- first_subset = 0;
- last_subset = (uint32_t)subsets.size();
- if (subsets_per_lod > 0)
- {
- lod = std::min(lod, GetLODCount() - 1);
- first_subset = subsets_per_lod * lod;
- last_subset = first_subset + subsets_per_lod;
- }
- }
-
- // Recreates GPU resources for index/vertex buffers
- void CreateRenderData();
- void CreateStreamoutRenderData();
-
- enum COMPUTE_NORMALS
- {
- COMPUTE_NORMALS_HARD, // hard face normals, can result in additional vertices generated
- COMPUTE_NORMALS_SMOOTH, // smooth per vertex normals, this can remove/simplyfy geometry, but slow
- COMPUTE_NORMALS_SMOOTH_FAST // average normals, vertex count will be unchanged, fast
- };
- void ComputeNormals(COMPUTE_NORMALS compute);
- void FlipCulling();
- void FlipNormals();
- void Recenter();
- void RecenterToBottom();
- wi::primitive::Sphere GetBoundingSphere() const;
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
-
-
- struct Vertex_POS
- {
- XMFLOAT3 pos = XMFLOAT3(0.0f, 0.0f, 0.0f);
- uint32_t normal_wind = 0;
-
- void FromFULL(const XMFLOAT3& _pos, const XMFLOAT3& _nor, uint8_t wind)
- {
- pos.x = _pos.x;
- pos.y = _pos.y;
- pos.z = _pos.z;
- MakeFromParams(_nor, wind);
- }
- inline XMVECTOR LoadPOS() const
- {
- return XMLoadFloat3(&pos);
- }
- inline XMVECTOR LoadNOR() const
- {
- XMFLOAT3 N = GetNor_FULL();
- return XMLoadFloat3(&N);
- }
- inline void MakeFromParams(const XMFLOAT3& normal)
- {
- normal_wind = normal_wind & 0xFF000000; // reset only the normals
-
- normal_wind |= (uint32_t)((normal.x * 0.5f + 0.5f) * 255.0f) << 0;
- normal_wind |= (uint32_t)((normal.y * 0.5f + 0.5f) * 255.0f) << 8;
- normal_wind |= (uint32_t)((normal.z * 0.5f + 0.5f) * 255.0f) << 16;
- }
- inline void MakeFromParams(const XMFLOAT3& normal, uint8_t wind)
- {
- normal_wind = 0;
-
- normal_wind |= (uint32_t)((normal.x * 0.5f + 0.5f) * 255.0f) << 0;
- normal_wind |= (uint32_t)((normal.y * 0.5f + 0.5f) * 255.0f) << 8;
- normal_wind |= (uint32_t)((normal.z * 0.5f + 0.5f) * 255.0f) << 16;
- normal_wind |= (uint32_t)wind << 24;
- }
- inline XMFLOAT3 GetNor_FULL() const
- {
- XMFLOAT3 nor_FULL(0, 0, 0);
-
- nor_FULL.x = (float)((normal_wind >> 0) & 0x000000FF) / 255.0f * 2.0f - 1.0f;
- nor_FULL.y = (float)((normal_wind >> 8) & 0x000000FF) / 255.0f * 2.0f - 1.0f;
- nor_FULL.z = (float)((normal_wind >> 16) & 0x000000FF) / 255.0f * 2.0f - 1.0f;
-
- return nor_FULL;
- }
- inline uint8_t GetWind() const
- {
- return (normal_wind >> 24) & 0x000000FF;
- }
-
- static const wi::graphics::Format FORMAT = wi::graphics::Format::R32G32B32A32_FLOAT;
- };
- struct Vertex_TEX
- {
- XMHALF2 tex = XMHALF2(0.0f, 0.0f);
-
- void FromFULL(const XMFLOAT2& texcoords)
- {
- tex = XMHALF2(texcoords.x, texcoords.y);
- }
-
- static const wi::graphics::Format FORMAT = wi::graphics::Format::R16G16_FLOAT;
- };
- struct Vertex_UVS
- {
- Vertex_TEX uv0;
- Vertex_TEX uv1;
- };
- struct Vertex_BON
- {
- uint64_t ind = 0;
- uint64_t wei = 0;
-
- void FromFULL(const XMUINT4& boneIndices, const XMFLOAT4& boneWeights)
- {
- ind = 0;
- wei = 0;
-
- ind |= (uint64_t)boneIndices.x << 0;
- ind |= (uint64_t)boneIndices.y << 16;
- ind |= (uint64_t)boneIndices.z << 32;
- ind |= (uint64_t)boneIndices.w << 48;
-
- wei |= (uint64_t)(boneWeights.x * 65535.0f) << 0;
- wei |= (uint64_t)(boneWeights.y * 65535.0f) << 16;
- wei |= (uint64_t)(boneWeights.z * 65535.0f) << 32;
- wei |= (uint64_t)(boneWeights.w * 65535.0f) << 48;
- }
- inline XMUINT4 GetInd_FULL() const
- {
- XMUINT4 ind_FULL(0, 0, 0, 0);
-
- ind_FULL.x = ((ind >> 0) & 0x0000FFFF);
- ind_FULL.y = ((ind >> 16) & 0x0000FFFF);
- ind_FULL.z = ((ind >> 32) & 0x0000FFFF);
- ind_FULL.w = ((ind >> 48) & 0x0000FFFF);
-
- return ind_FULL;
- }
- inline XMFLOAT4 GetWei_FULL() const
- {
- XMFLOAT4 wei_FULL(0, 0, 0, 0);
-
- wei_FULL.x = (float)((wei >> 0) & 0x0000FFFF) / 65535.0f;
- wei_FULL.y = (float)((wei >> 16) & 0x0000FFFF) / 65535.0f;
- wei_FULL.z = (float)((wei >> 32) & 0x0000FFFF) / 65535.0f;
- wei_FULL.w = (float)((wei >> 48) & 0x0000FFFF) / 65535.0f;
-
- return wei_FULL;
- }
- };
- struct Vertex_COL
- {
- uint32_t color = 0;
- static const wi::graphics::Format FORMAT = wi::graphics::Format::R8G8B8A8_UNORM;
- };
- struct Vertex_TAN
- {
- uint32_t tangent = 0;
-
- void FromFULL(const XMFLOAT4& tan)
- {
- XMVECTOR T = XMLoadFloat4(&tan);
- T = XMVector3Normalize(T);
- XMFLOAT4 t;
- XMStoreFloat4(&t, T);
- t.w = tan.w;
- tangent = 0;
- tangent |= (uint)((t.x * 0.5f + 0.5f) * 255.0f) << 0;
- tangent |= (uint)((t.y * 0.5f + 0.5f) * 255.0f) << 8;
- tangent |= (uint)((t.z * 0.5f + 0.5f) * 255.0f) << 16;
- tangent |= (uint)((t.w * 0.5f + 0.5f) * 255.0f) << 24;
- }
-
- static const wi::graphics::Format FORMAT = wi::graphics::Format::R8G8B8A8_UNORM;
- };
-
- // Non serialized attributes:
- wi::vector vertex_positions_morphed;
- wi::vector morph_temp_pos;
- wi::vector morph_temp_nor;
-
- };
-
- struct ImpostorComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- DIRTY = 1 << 0,
- };
- uint32_t _flags = DIRTY;
-
- float swapInDistance = 100.0f;
-
- // Non-serialized attributes:
- mutable bool render_dirty = false;
- int textureIndex = -1;
-
- inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
- inline bool IsDirty() const { return _flags & DIRTY; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct ObjectComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- RENDERABLE = 1 << 0,
- CAST_SHADOW = 1 << 1,
- DYNAMIC = 1 << 2,
- _DEPRECATED_IMPOSTOR_PLACEMENT = 1 << 3,
- REQUEST_PLANAR_REFLECTION = 1 << 4,
- LIGHTMAP_RENDER_REQUEST = 1 << 5,
- };
- uint32_t _flags = RENDERABLE | CAST_SHADOW;
-
- wi::ecs::Entity meshID = wi::ecs::INVALID_ENTITY;
- uint32_t cascadeMask = 0; // which shadow cascades to skip from lowest detail to highest detail (0: skip none, 1: skip first, etc...)
- uint32_t rendertypeMask = 0;
- XMFLOAT4 color = XMFLOAT4(1, 1, 1, 1);
- XMFLOAT4 emissiveColor = XMFLOAT4(1, 1, 1, 1);
-
- uint32_t lightmapWidth = 0;
- uint32_t lightmapHeight = 0;
- wi::vector lightmapTextureData;
-
- uint8_t userStencilRef = 0;
- float lod_distance_multiplier = 1;
-
- float draw_distance = std::numeric_limits::max(); // object will begin to fade out at this distance to camera
-
- // Non-serialized attributes:
-
- wi::graphics::Texture lightmap;
- wi::graphics::RenderPass renderpass_lightmap_clear;
- wi::graphics::RenderPass renderpass_lightmap_accumulate;
- mutable uint32_t lightmapIterationCount = 0;
-
- XMFLOAT3 center = XMFLOAT3(0, 0, 0);
- float radius = 0;
- float fadeDistance = 0;
-
- uint32_t lod = 0;
-
- // these will only be valid for a single frame:
- uint32_t mesh_index = ~0u;
- XMFLOAT4X4 worldMatrix = wi::math::IDENTITY_MATRIX;
-
- // occlusion result history bitfield (32 bit->32 frame history)
- mutable uint32_t occlusionHistory = ~0u;
- mutable int occlusionQueries[wi::graphics::GraphicsDevice::GetBufferCount() + 1];
-
- inline bool IsOccluded() const
- {
- // Perform a conservative occlusion test:
- // If it is visible in any frames in the history, it is determined visible in this frame
- // But if all queries failed in the history, it is occluded.
- // If it pops up for a frame after occluded, it is visible again for some frames
- return occlusionHistory == 0;
- }
-
- inline void SetRenderable(bool value) { if (value) { _flags |= RENDERABLE; } else { _flags &= ~RENDERABLE; } }
- inline void SetCastShadow(bool value) { if (value) { _flags |= CAST_SHADOW; } else { _flags &= ~CAST_SHADOW; } }
- inline void SetDynamic(bool value) { if (value) { _flags |= DYNAMIC; } else { _flags &= ~DYNAMIC; } }
- inline void SetRequestPlanarReflection(bool value) { if (value) { _flags |= REQUEST_PLANAR_REFLECTION; } else { _flags &= ~REQUEST_PLANAR_REFLECTION; } }
- inline void SetLightmapRenderRequest(bool value) { if (value) { _flags |= LIGHTMAP_RENDER_REQUEST; } else { _flags &= ~LIGHTMAP_RENDER_REQUEST; } }
-
- inline bool IsRenderable() const { return _flags & RENDERABLE; }
- inline bool IsCastingShadow() const { return _flags & CAST_SHADOW; }
- inline bool IsDynamic() const { return _flags & DYNAMIC; }
- inline bool IsRequestPlanarReflection() const { return _flags & REQUEST_PLANAR_REFLECTION; }
- inline bool IsLightmapRenderRequested() const { return _flags & LIGHTMAP_RENDER_REQUEST; }
-
- inline float GetTransparency() const { return 1 - color.w; }
- inline uint32_t GetRenderTypes() const { return rendertypeMask; }
-
- // User stencil value can be in range [0, 15]
- // Values greater than 0 can be used to override userStencilRef of MaterialComponent
- inline void SetUserStencilRef(uint8_t value)
- {
- assert(value < 16);
- userStencilRef = value & 0x0F;
- }
-
- void ClearLightmap();
- void SaveLightmap();
- void CompressLightmap();
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct RigidBodyPhysicsComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- DISABLE_DEACTIVATION = 1 << 0,
- KINEMATIC = 1 << 1,
- };
- uint32_t _flags = EMPTY;
-
- enum CollisionShape
- {
- BOX,
- SPHERE,
- CAPSULE,
- CONVEX_HULL,
- TRIANGLE_MESH,
- ENUM_FORCE_UINT32 = 0xFFFFFFFF
- };
- CollisionShape shape;
- float mass = 1.0f;
- float friction = 0.5f;
- float restitution = 0.0f;
- float damping_linear = 0.0f;
- float damping_angular = 0.0f;
-
- struct BoxParams
- {
- XMFLOAT3 halfextents = XMFLOAT3(1, 1, 1);
- } box;
- struct SphereParams
- {
- float radius = 1;
- } sphere;
- struct CapsuleParams
- {
- float radius = 1;
- float height = 1;
- } capsule;
-
- // This will force LOD level for rigid body if it is a TRIANGLE_MESH shape:
- // The geometry for LOD level will be taken from MeshComponent.
- // The physics object will need to be recreated for it to take effect.
- uint32_t mesh_lod = 0;
-
- // Non-serialized attributes:
- 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; } }
-
- inline bool IsDisableDeactivation() const { return _flags & DISABLE_DEACTIVATION; }
- inline bool IsKinematic() const { return _flags & KINEMATIC; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct SoftBodyPhysicsComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- SAFE_TO_REGISTER = 1 << 0,
- DISABLE_DEACTIVATION = 1 << 1,
- FORCE_RESET = 1 << 2,
- };
- uint32_t _flags = DISABLE_DEACTIVATION;
-
- float mass = 1.0f;
- float friction = 0.5f;
- float restitution = 0.0f;
- wi::vector physicsToGraphicsVertexMapping; // maps graphics vertex index to physics vertex index of the same position
- wi::vector graphicsToPhysicsVertexMapping; // maps a physics vertex index to first graphics vertex index of the same position
- wi::vector weights; // weight per physics vertex controlling the mass. (0: disable weight (no physics, only animation), 1: default weight)
-
- // Non-serialized attributes:
- 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;
- wi::vector vertex_tangents_simulation;
- wi::primitive::AABB aabb;
-
- inline void SetDisableDeactivation(bool value) { if (value) { _flags |= DISABLE_DEACTIVATION; } else { _flags &= ~DISABLE_DEACTIVATION; } }
-
- inline bool IsDisableDeactivation() const { return _flags & DISABLE_DEACTIVATION; }
-
- // Create physics represenation of graphics mesh
- void CreateFromMesh(const MeshComponent& mesh);
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct ArmatureComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- };
- uint32_t _flags = EMPTY;
-
- wi::vector boneCollection;
- wi::vector inverseBindMatrices;
-
- // Non-serialized attributes:
- wi::primitive::AABB aabb;
-
- wi::vector boneData;
- wi::graphics::GPUBuffer boneBuffer;
- int descriptor_srv = -1;
-
- void CreateRenderData();
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct LightComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- CAST_SHADOW = 1 << 0,
- VOLUMETRICS = 1 << 1,
- VISUALIZER = 1 << 2,
- LIGHTMAPONLY_STATIC = 1 << 3,
- VOLUMETRICCLOUDS = 1 << 4,
- };
- uint32_t _flags = EMPTY;
-
- enum LightType
- {
- DIRECTIONAL = ENTITY_TYPE_DIRECTIONALLIGHT,
- POINT = ENTITY_TYPE_POINTLIGHT,
- SPOT = ENTITY_TYPE_SPOTLIGHT,
- //SPHERE = ENTITY_TYPE_SPHERELIGHT,
- //DISC = ENTITY_TYPE_DISCLIGHT,
- //RECTANGLE = ENTITY_TYPE_RECTANGLELIGHT,
- //TUBE = ENTITY_TYPE_TUBELIGHT,
- LIGHTTYPE_COUNT,
- ENUM_FORCE_UINT32 = 0xFFFFFFFF,
- };
- LightType type = POINT;
-
- XMFLOAT3 color = XMFLOAT3(1, 1, 1);
- float intensity = 1.0f; // Brightness of light in. The units that this is defined in depend on the type of light. Point and spot lights use luminous intensity in candela (lm/sr) while directional lights use illuminance in lux (lm/m2). https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_lights_punctual
- float range = 10.0f;
- float outerConeAngle = XM_PIDIV4;
- float innerConeAngle = 0; // default value is 0, means only outer cone angle is used
-
- wi::vector lensFlareNames;
-
- int forced_shadow_resolution = -1; // -1: disabled, greater: fixed shadow map resolution
-
- // Non-serialized attributes:
- XMFLOAT3 position;
- XMFLOAT3 direction;
- XMFLOAT4 rotation;
- XMFLOAT3 scale;
- XMFLOAT3 front;
- XMFLOAT3 right;
- mutable int occlusionquery = -1;
- wi::rectpacker::Rect shadow_rect = {};
-
- wi::vector lensFlareRimTextures;
-
- inline void SetCastShadow(bool value) { if (value) { _flags |= CAST_SHADOW; } else { _flags &= ~CAST_SHADOW; } }
- inline void SetVolumetricsEnabled(bool value) { if (value) { _flags |= VOLUMETRICS; } else { _flags &= ~VOLUMETRICS; } }
- inline void SetVisualizerEnabled(bool value) { if (value) { _flags |= VISUALIZER; } else { _flags &= ~VISUALIZER; } }
- inline void SetStatic(bool value) { if (value) { _flags |= LIGHTMAPONLY_STATIC; } else { _flags &= ~LIGHTMAPONLY_STATIC; } }
- inline void SetVolumetricCloudsEnabled(bool value) { if (value) { _flags |= VOLUMETRICCLOUDS; } else { _flags &= ~VOLUMETRICCLOUDS; } }
-
- inline bool IsCastingShadow() const { return _flags & CAST_SHADOW; }
- inline bool IsVolumetricsEnabled() const { return _flags & VOLUMETRICS; }
- inline bool IsVisualizerEnabled() const { return _flags & VISUALIZER; }
- inline bool IsStatic() const { return _flags & LIGHTMAPONLY_STATIC; }
- inline bool IsVolumetricCloudsEnabled() const { return _flags & VOLUMETRICCLOUDS; }
-
- inline float GetRange() const
- {
- float retval = range;
- retval = std::max(0.001f, retval);
- retval = std::min(retval, 65504.0f); // clamp to 16-bit float max value
- return retval;
- }
-
- inline void SetType(LightType val) { type = val; }
- inline LightType GetType() const { return type; }
-
- // Set energy amount with non physical light units (from before version 0.70.0):
- inline void BackCompatSetEnergy(float energy)
- {
- switch (type)
- {
- case wi::scene::LightComponent::POINT:
- intensity = energy * 20;
- break;
- case wi::scene::LightComponent::SPOT:
- intensity = energy * 200;
- break;
- default:
- break;
- }
- }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct CameraComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- DIRTY = 1 << 0,
- CUSTOM_PROJECTION = 1 << 1,
- };
- uint32_t _flags = EMPTY;
-
- float width = 0.0f;
- float height = 0.0f;
- float zNearP = 0.1f;
- float zFarP = 5000.0f;
- float fov = XM_PI / 3.0f;
- float focal_length = 1;
- float aperture_size = 0;
- XMFLOAT2 aperture_shape = XMFLOAT2(1, 1);
-
- // Non-serialized attributes:
- XMFLOAT3 Eye = XMFLOAT3(0, 0, 0);
- XMFLOAT3 At = XMFLOAT3(0, 0, 1);
- XMFLOAT3 Up = XMFLOAT3(0, 1, 0);
- XMFLOAT3X3 rotationMatrix;
- XMFLOAT4X4 View, Projection, VP;
- wi::primitive::Frustum frustum;
- XMFLOAT4X4 InvView, InvProjection, InvVP;
- XMFLOAT2 jitter;
- XMFLOAT4 clipPlane = XMFLOAT4(0, 0, 0, 0); // default: no clip plane
- wi::Canvas canvas;
- uint32_t sample_count = 1;
- int texture_primitiveID_index = -1;
- int texture_depth_index = -1;
- int texture_lineardepth_index = -1;
- int texture_velocity_index = -1;
- int texture_normal_index = -1;
- int texture_roughness_index = -1;
- int texture_reflection_index = -1;
- int texture_refraction_index = -1;
- int texture_waterriples_index = -1;
- int texture_ao_index = -1;
- int texture_ssr_index = -1;
- int texture_rtshadow_index = -1;
- int texture_surfelgi_index = -1;
- int buffer_entitytiles_opaque_index = -1;
- int buffer_entitytiles_transparent_index = -1;
-
- void CreatePerspective(float newWidth, float newHeight, float newNear, float newFar, float newFOV = XM_PI / 3.0f);
- void UpdateCamera();
- void TransformCamera(const TransformComponent& transform);
- void Reflect(const XMFLOAT4& plane = XMFLOAT4(0, 1, 0, 0));
-
- inline XMVECTOR GetEye() const { return XMLoadFloat3(&Eye); }
- inline XMVECTOR GetAt() const { return XMLoadFloat3(&At); }
- inline XMVECTOR GetUp() const { return XMLoadFloat3(&Up); }
- inline XMVECTOR GetRight() const { return XMVector3Cross(GetAt(), GetUp()); }
- inline XMMATRIX GetView() const { return XMLoadFloat4x4(&View); }
- inline XMMATRIX GetInvView() const { return XMLoadFloat4x4(&InvView); }
- inline XMMATRIX GetProjection() const { return XMLoadFloat4x4(&Projection); }
- inline XMMATRIX GetInvProjection() const { return XMLoadFloat4x4(&InvProjection); }
- inline XMMATRIX GetViewProjection() const { return XMLoadFloat4x4(&VP); }
- inline XMMATRIX GetInvViewProjection() const { return XMLoadFloat4x4(&InvVP); }
-
- inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
- inline void SetCustomProjectionEnabled(bool value = true) { if (value) { _flags |= CUSTOM_PROJECTION; } else { _flags &= ~CUSTOM_PROJECTION; } }
- inline bool IsDirty() const { return _flags & DIRTY; }
- inline bool IsCustomProjectionEnabled() const { return _flags & CUSTOM_PROJECTION; }
-
- void Lerp(const CameraComponent& a, const CameraComponent& b, float t);
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct EnvironmentProbeComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- DIRTY = 1 << 0,
- REALTIME = 1 << 1,
- MSAA = 1 << 2,
- };
- uint32_t _flags = DIRTY;
-
- // Non-serialized attributes:
- int textureIndex = -1;
- XMFLOAT3 position;
- float range;
- XMFLOAT4X4 inverseMatrix;
- mutable bool render_dirty = false;
-
- inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
- inline void SetRealTime(bool value) { if (value) { _flags |= REALTIME; } else { _flags &= ~REALTIME; } }
- inline void SetMSAA(bool value) { if (value) { _flags |= MSAA; } else { _flags &= ~MSAA; } }
-
- inline bool IsDirty() const { return _flags & DIRTY; }
- inline bool IsRealTime() const { return _flags & REALTIME; }
- inline bool IsMSAA() const { return _flags & MSAA; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct ForceFieldComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- };
- uint32_t _flags = EMPTY;
-
- enum class Type
- {
- Point,
- Plane,
- } type = Type::Point;
-
- float gravity = 0; // negative = deflector, positive = attractor
- float range = 0; // affection range
-
- // Non-serialized attributes:
- XMFLOAT3 position;
- XMFLOAT3 direction;
-
- inline float GetRange() const { return range; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct DecalComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- };
- uint32_t _flags = EMPTY;
-
- // Non-serialized attributes:
- XMFLOAT4 color;
- float emissive;
- XMFLOAT3 front;
- XMFLOAT3 position;
- float range;
- XMFLOAT4X4 world;
-
- wi::Resource texture;
- wi::Resource normal;
-
- inline float GetOpacity() const { return color.w; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct AnimationDataComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- };
- uint32_t _flags = EMPTY;
-
- wi::vector keyframe_times;
- wi::vector keyframe_data;
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct AnimationComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- PLAYING = 1 << 0,
- LOOPED = 1 << 1,
- };
- uint32_t _flags = LOOPED;
- float start = 0;
- float end = 0;
- float timer = 0;
- float amount = 1; // blend amount
- float speed = 1;
-
- struct AnimationChannel
- {
- enum FLAGS
- {
- EMPTY = 0,
- };
- uint32_t _flags = LOOPED;
-
- wi::ecs::Entity target = wi::ecs::INVALID_ENTITY;
- int samplerIndex = -1;
-
- enum class Path
- {
- TRANSLATION,
- ROTATION,
- SCALE,
- WEIGHTS,
-
- LIGHT_COLOR,
- LIGHT_INTENSITY,
- LIGHT_RANGE,
- LIGHT_INNERCONE,
- LIGHT_OUTERCONE,
- // additional light paths can go here...
- _LIGHT_RANGE_END = LIGHT_COLOR + 1000,
-
- SOUND_PLAY,
- SOUND_STOP,
- SOUND_VOLUME,
- // additional sound paths can go here...
- _SOUND_RANGE_END = SOUND_PLAY + 1000,
-
- EMITTER_EMITCOUNT,
- // additional emitter paths can go here...
- _EMITTER_RANGE_END = EMITTER_EMITCOUNT + 1000,
-
- CAMERA_FOV,
- CAMERA_FOCAL_LENGTH,
- CAMERA_APERTURE_SIZE,
- CAMERA_APERTURE_SHAPE,
- // additional camera paths can go here...
- _CAMERA_RANGE_END = CAMERA_FOV + 1000,
-
- SCRIPT_PLAY,
- SCRIPT_STOP,
- // additional script paths can go here...
- _SCRIPT_RANGE_END = SCRIPT_PLAY + 1000,
-
- MATERIAL_COLOR,
- MATERIAL_EMISSIVE,
- MATERIAL_ROUGHNESS,
- MATERIAL_METALNESS,
- MATERIAL_REFLECTANCE,
- MATERIAL_TEXMULADD,
- // additional material paths can go here...
- _MATERIAL_RANGE_END = MATERIAL_COLOR + 1000,
-
- UNKNOWN,
- } path = Path::UNKNOWN;
-
- enum class PathDataType
- {
- Event,
- Float,
- Float2,
- Float3,
- Float4,
- Weights,
-
- Count,
- };
- PathDataType GetPathDataType() const;
-
- // Non-serialized attributes:
- mutable int next_event = 0;
- };
- struct AnimationSampler
- {
- enum FLAGS
- {
- EMPTY = 0,
- };
- uint32_t _flags = LOOPED;
-
- wi::ecs::Entity data = wi::ecs::INVALID_ENTITY;
-
- enum Mode
- {
- LINEAR,
- STEP,
- CUBICSPLINE,
- MODE_FORCE_UINT32 = 0xFFFFFFFF
- } mode = LINEAR;
-
- // The data is now not part of the sampler, so it can be shared. This is kept only for backwards compatibility with previous versions.
- AnimationDataComponent backwards_compatibility_data;
- };
- wi::vector channels;
- wi::vector samplers;
-
- // Non-serialzied attributes:
- wi::vector morph_weights_temp;
- float last_update_time = 0;
-
- inline bool IsPlaying() const { return _flags & PLAYING; }
- inline bool IsLooped() const { return _flags & LOOPED; }
- inline float GetLength() const { return end - start; }
- inline bool IsEnded() const { return timer >= end; }
-
- inline void Play() { _flags |= PLAYING; }
- inline void Pause() { _flags &= ~PLAYING; }
- inline void Stop() { Pause(); timer = 0.0f; last_update_time = timer; }
- inline void SetLooped(bool value = true) { if (value) { _flags |= LOOPED; } else { _flags &= ~LOOPED; } }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct WeatherComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- OCEAN_ENABLED = 1 << 0,
- _DEPRECATED_SIMPLE_SKY = 1 << 1,
- REALISTIC_SKY = 1 << 2,
- VOLUMETRIC_CLOUDS = 1 << 3,
- HEIGHT_FOG = 1 << 4,
- VOLUMETRIC_CLOUDS_SHADOWS = 1 << 5,
- };
- uint32_t _flags = EMPTY;
-
- inline bool IsOceanEnabled() const { return _flags & OCEAN_ENABLED; }
- inline bool IsRealisticSky() const { return _flags & REALISTIC_SKY; }
- inline bool IsVolumetricClouds() const { return _flags & VOLUMETRIC_CLOUDS; }
- inline bool IsHeightFog() const { return _flags & HEIGHT_FOG; }
- inline bool IsVolumetricCloudsShadows() const { return _flags & VOLUMETRIC_CLOUDS_SHADOWS; }
-
- inline void SetOceanEnabled(bool value = true) { if (value) { _flags |= OCEAN_ENABLED; } else { _flags &= ~OCEAN_ENABLED; } }
- inline void SetRealisticSky(bool value = true) { if (value) { _flags |= REALISTIC_SKY; } else { _flags &= ~REALISTIC_SKY; } }
- inline void SetVolumetricClouds(bool value = true) { if (value) { _flags |= VOLUMETRIC_CLOUDS; } else { _flags &= ~VOLUMETRIC_CLOUDS; } }
- inline void SetHeightFog(bool value = true) { if (value) { _flags |= HEIGHT_FOG; } else { _flags &= ~HEIGHT_FOG; } }
- inline void SetVolumetricCloudsShadows(bool value = true) { if (value) { _flags |= VOLUMETRIC_CLOUDS_SHADOWS; } else { _flags &= ~VOLUMETRIC_CLOUDS_SHADOWS; } }
-
- XMFLOAT3 sunColor = XMFLOAT3(0, 0, 0);
- XMFLOAT3 sunDirection = XMFLOAT3(0, 1, 0);
- float skyExposure = 1;
- XMFLOAT3 horizon = XMFLOAT3(0.0f, 0.0f, 0.0f);
- XMFLOAT3 zenith = XMFLOAT3(0.0f, 0.0f, 0.0f);
- XMFLOAT3 ambient = XMFLOAT3(0.2f, 0.2f, 0.2f);
- float fogStart = 100;
- float fogEnd = 1000;
- float fogHeightStart = 1;
- float fogHeightEnd = 3;
- XMFLOAT3 windDirection = XMFLOAT3(0, 0, 0);
- float windRandomness = 5;
- float windWaveSize = 1;
- float windSpeed = 1;
- float stars = 0.5f;
-
- wi::Ocean::OceanParameters oceanParameters;
- AtmosphereParameters atmosphereParameters;
- VolumetricCloudParameters volumetricCloudParameters;
-
- std::string skyMapName;
- std::string colorGradingMapName;
- std::string volumetricCloudsWeatherMapName;
-
- // Non-serialized attributes:
- uint32_t most_important_light_index = ~0u;
- wi::Resource skyMap;
- wi::Resource colorGradingMap;
- wi::Resource volumetricCloudsWeatherMap;
- XMFLOAT4 stars_rotation_quaternion = XMFLOAT4(0, 0, 0, 1);
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct SoundComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- PLAYING = 1 << 0,
- LOOPED = 1 << 1,
- DISABLE_3D = 1 << 2,
- };
- uint32_t _flags = LOOPED;
-
- std::string filename;
- wi::Resource soundResource;
- wi::audio::SoundInstance soundinstance;
- float volume = 1;
-
- inline bool IsPlaying() const { return _flags & PLAYING; }
- inline bool IsLooped() const { return _flags & LOOPED; }
- inline bool IsDisable3D() const { return _flags & DISABLE_3D; }
-
- inline void Play() { _flags |= PLAYING; }
- inline void Stop() { _flags &= ~PLAYING; }
- inline void SetLooped(bool value = true) { if (value) { _flags |= LOOPED; } else { _flags &= ~LOOPED; } }
- inline void SetDisable3D(bool value = true) { if (value) { _flags |= DISABLE_3D; } else { _flags &= ~DISABLE_3D; } }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct InverseKinematicsComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- DISABLED = 1 << 0,
- };
- uint32_t _flags = EMPTY;
-
- wi::ecs::Entity target = wi::ecs::INVALID_ENTITY; // which entity to follow (must have a transform component)
- uint32_t chain_length = 0; // recursive depth
- uint32_t iteration_count = 1; // computation step count. Increase this too for greater chain length
-
- inline void SetDisabled(bool value = true) { if (value) { _flags |= DISABLED; } else { _flags &= ~DISABLED; } }
- inline bool IsDisabled() const { return _flags & DISABLED; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct SpringComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- RESET = 1 << 0,
- DISABLED = 1 << 1,
- STRETCH_ENABLED = 1 << 2,
- GRAVITY_ENABLED = 1 << 3,
- };
- uint32_t _flags = RESET | GRAVITY_ENABLED;
-
- float stiffnessForce = 0.5f;
- float dragForce = 0.5f;
- float windForce = 0.5f;
- float hitRadius = 0;
- float gravityPower = 0.5f;
- XMFLOAT3 gravityDir = {};
-
- // Non-serialized attributes:
- XMFLOAT3 prevTail = {};
- XMFLOAT3 currentTail = {};
- XMFLOAT3 boneAxis = {};
- float boneLength = 0;
-
- inline void Reset(bool value = true) { if (value) { _flags |= RESET; } else { _flags &= ~RESET; } }
- inline void SetDisabled(bool value = true) { if (value) { _flags |= DISABLED; } else { _flags &= ~DISABLED; } }
- inline void SetStretchEnabled(bool value) { if (value) { _flags |= STRETCH_ENABLED; } else { _flags &= ~STRETCH_ENABLED; } }
- inline void SetGravityEnabled(bool value) { if (value) { _flags |= GRAVITY_ENABLED; } else { _flags &= ~GRAVITY_ENABLED; } }
-
- inline bool IsResetting() const { return _flags & RESET; }
- inline bool IsDisabled() const { return _flags & DISABLED; }
- inline bool IsStretchEnabled() const { return _flags & STRETCH_ENABLED; }
- inline bool IsGravityEnabled() const { return _flags & GRAVITY_ENABLED; }
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct ColliderComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- };
- uint32_t _flags = EMPTY;
-
- enum class Shape
- {
- Sphere,
- Capsule,
- Plane,
- };
- Shape shape;
-
- float radius = 0;
- XMFLOAT3 offset = {};
- XMFLOAT3 tail = {};
-
- // Non-serialized attributes:
- wi::primitive::Sphere sphere;
- wi::primitive::Capsule capsule;
- XMFLOAT3 planeOrigin = {};
- XMFLOAT3 planeNormal = {};
- XMFLOAT4X4 planeProjection = wi::math::IDENTITY_MATRIX;
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct ScriptComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- PLAYING = 1 << 0,
- PLAY_ONCE = 1 << 1,
- };
- uint32_t _flags = EMPTY;
-
- std::string filename;
-
- // Non-serialized attributes:
- std::string script;
- wi::Resource resource;
-
- inline void Play() { _flags |= PLAYING; }
- inline void SetPlayOnce(bool once = true) { if (once) { _flags |= PLAY_ONCE; } else { _flags &= ~PLAY_ONCE; } }
- inline void Stop() { _flags &= ~PLAYING; }
-
- inline bool IsPlaying() const { return _flags & PLAYING; }
- inline bool IsPlayingOnlyOnce() const { return _flags & PLAY_ONCE; }
-
- void CreateFromFile(const std::string& filename);
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
- struct ExpressionComponent
- {
- enum FLAGS
- {
- EMPTY = 0,
- };
- uint32_t _flags = EMPTY;
-
- // Preset expressions can have common behaviours assigned:
- // https://github.com/vrm-c/vrm-specification/blob/bd205a6c3839993f2729e4e7c3a74af89877cfce/specification/VRMC_vrm-1.0-beta/expressions.md#preset-expressions
- enum class Preset
- {
- // Emotions:
- Happy, // Changed from joy
- Angry, // anger
- Sad, // Changed from sorrow
- Relaxed, // Comfortable. Changed from fun
- Surprised, // surprised. Added new in VRM 1.0
-
- // Lip sync procedural:
- // Procedural: A value that can be automatically generated by the system.
- // - Analyze microphone input, generate from text, etc.
- Aa, // aa
- Ih, // i
- Ou, // u
- Ee, // eh
- Oh, // oh
-
- // Blink procedural:
- // Procedural: A value that can be automatically generated by the system.
- // - Randomly blink, etc.
- Blink, // close both eyelids
- BlinkLeft, // Close the left eyelid
- BlinkRight, // Close right eyelid
-
- // Gaze procedural:
- // Procedural: A value that can be automatically generated by the system.
- // - The VRM LookAt will generate a value for the gaze point from time to time (see LookAt Expression Type).
- LookUp, // For models where the line of sight moves with Expression instead of bone. See eye control.
- LookDown, // For models where the line of sight moves with Expression instead of bone. See eye control.
- LookLeft, // For models whose line of sight moves with Expression instead of bone. See eye control.
- LookRight, // For models where the line of sight moves with Expression instead of bone. See eye control.
-
- // Other:
- Neutral, // left for backwards compatibility.
-
- Count,
- };
- int presets[size_t(Preset::Count)] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
-
- enum class Override
- {
- None,
- Block,
- Blend,
- };
-
- float blink_frequency = 0.3f; // number of blinks per second
- float blink_length = 0.1f; // blink's completion time in seconds
- int blink_count = 2;
- float look_frequency = 0; // number of lookAt changes per second
- float look_length = 0.6f; // lookAt's completion time in seconds
-
- struct Expression
- {
- enum FLAGS
- {
- EMPTY = 0,
- DIRTY = 1 << 0,
- BINARY = 1 << 1,
- };
- uint32_t _flags = EMPTY;
-
- std::string name;
- float weight = 0;
-
- Preset preset = Preset::Count;
- Override override_mouth = Override::None;
- Override override_blink = Override::None;
- Override override_look = Override::None;
-
- struct MorphTargetBinding
- {
- wi::ecs::Entity meshID = wi::ecs::INVALID_ENTITY;
- int index = 0;
- float weight = 0;
- };
- wi::vector morph_target_bindings;
-
- constexpr bool IsDirty() const { return _flags & DIRTY; }
- constexpr bool IsBinary() const { return _flags & BINARY; }
-
- constexpr void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
- constexpr void SetBinary(bool value = true) { if (value) { _flags |= BINARY; } else { _flags &= ~BINARY; } }
- };
- wi::vector expressions;
-
- // Non-serialized attributes:
- float blink_timer = 0;
- float look_timer = 0;
- float look_weights[4] = {};
-
- void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
- };
-
struct Scene
{
wi::ecs::ComponentLibrary componentLibrary;
@@ -1572,6 +52,7 @@ namespace wi::scene
wi::ecs::ComponentManager& colliders = componentLibrary.Register("wi::scene::Scene::colliders", 1); // version = 1
wi::ecs::ComponentManager& scripts = componentLibrary.Register("wi::scene::Scene::scripts");
wi::ecs::ComponentManager& expressions = componentLibrary.Register("wi::scene::Scene::expressions");
+ wi::ecs::ComponentManager& terrains = componentLibrary.Register("wi::scene::Scene::terrains");
// Non-serialized attributes:
float dt = 0;
@@ -1655,6 +136,9 @@ namespace wi::scene
{
uint frame_index = 0;
uint3 grid_dimensions = uint3(32, 8, 32); // The scene extents will be subdivided into a grid of this resolution, each grid cell will have one probe
+ float3 grid_min = float3(-1, -1, -1);
+ float3 grid_max = float3(1, 1, 1);
+ float smooth_backface = 0; // smoothness of backface test
wi::graphics::GPUBuffer ray_buffer;
wi::graphics::GPUBuffer offset_buffer;
wi::graphics::Texture color_texture[2];
diff --git a/WickedEngine/wiScene_Components.cpp b/WickedEngine/wiScene_Components.cpp
new file mode 100644
index 000000000..56fa84e22
--- /dev/null
+++ b/WickedEngine/wiScene_Components.cpp
@@ -0,0 +1,1549 @@
+#include "wiScene_Components.h"
+#include "wiTextureHelper.h"
+#include "wiResourceManager.h"
+#include "wiPhysics.h"
+#include "wiRenderer.h"
+#include "wiJobSystem.h"
+#include "wiSpinLock.h"
+#include "wiHelper.h"
+#include "wiRenderer.h"
+#include "wiBacklog.h"
+#include "wiTimer.h"
+#include "wiUnorderedMap.h"
+#include "wiLua.h"
+
+using namespace wi::ecs;
+using namespace wi::enums;
+using namespace wi::graphics;
+using namespace wi::primitive;
+
+namespace wi::scene
+{
+
+
+ XMFLOAT3 TransformComponent::GetPosition() const
+ {
+ return *((XMFLOAT3*)&world._41);
+ }
+ XMFLOAT4 TransformComponent::GetRotation() const
+ {
+ XMFLOAT4 rotation;
+ XMStoreFloat4(&rotation, GetRotationV());
+ return rotation;
+ }
+ XMFLOAT3 TransformComponent::GetScale() const
+ {
+ XMFLOAT3 scale;
+ XMStoreFloat3(&scale, GetScaleV());
+ return scale;
+ }
+ XMVECTOR TransformComponent::GetPositionV() const
+ {
+ return XMLoadFloat3((XMFLOAT3*)&world._41);
+ }
+ XMVECTOR TransformComponent::GetRotationV() const
+ {
+ XMVECTOR S, R, T;
+ XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
+ return R;
+ }
+ XMVECTOR TransformComponent::GetScaleV() const
+ {
+ XMVECTOR S, R, T;
+ XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
+ return S;
+ }
+ XMMATRIX TransformComponent::GetLocalMatrix() const
+ {
+ XMVECTOR S_local = XMLoadFloat3(&scale_local);
+ XMVECTOR R_local = XMLoadFloat4(&rotation_local);
+ XMVECTOR T_local = XMLoadFloat3(&translation_local);
+ return
+ XMMatrixScalingFromVector(S_local) *
+ XMMatrixRotationQuaternion(R_local) *
+ XMMatrixTranslationFromVector(T_local);
+ }
+ void TransformComponent::UpdateTransform()
+ {
+ if (IsDirty())
+ {
+ SetDirty(false);
+
+ XMStoreFloat4x4(&world, GetLocalMatrix());
+ }
+ }
+ void TransformComponent::UpdateTransform_Parented(const TransformComponent& parent)
+ {
+ XMMATRIX W = GetLocalMatrix();
+ XMMATRIX W_parent = XMLoadFloat4x4(&parent.world);
+ W = W * W_parent;
+
+ XMStoreFloat4x4(&world, W);
+ }
+ void TransformComponent::ApplyTransform()
+ {
+ SetDirty();
+
+ XMVECTOR S, R, T;
+ XMMatrixDecompose(&S, &R, &T, XMLoadFloat4x4(&world));
+ XMStoreFloat3(&scale_local, S);
+ XMStoreFloat4(&rotation_local, R);
+ XMStoreFloat3(&translation_local, T);
+ }
+ void TransformComponent::ClearTransform()
+ {
+ SetDirty();
+ scale_local = XMFLOAT3(1, 1, 1);
+ rotation_local = XMFLOAT4(0, 0, 0, 1);
+ translation_local = XMFLOAT3(0, 0, 0);
+ }
+ void TransformComponent::Translate(const XMFLOAT3& value)
+ {
+ SetDirty();
+ translation_local.x += value.x;
+ translation_local.y += value.y;
+ translation_local.z += value.z;
+ }
+ void TransformComponent::Translate(const XMVECTOR& value)
+ {
+ XMFLOAT3 translation;
+ XMStoreFloat3(&translation, value);
+ Translate(translation);
+ }
+ void TransformComponent::RotateRollPitchYaw(const XMFLOAT3& value)
+ {
+ SetDirty();
+
+ // This needs to be handled a bit differently
+ XMVECTOR quat = XMLoadFloat4(&rotation_local);
+ XMVECTOR x = XMQuaternionRotationRollPitchYaw(value.x, 0, 0);
+ XMVECTOR y = XMQuaternionRotationRollPitchYaw(0, value.y, 0);
+ XMVECTOR z = XMQuaternionRotationRollPitchYaw(0, 0, value.z);
+
+ quat = XMQuaternionMultiply(x, quat);
+ quat = XMQuaternionMultiply(quat, y);
+ quat = XMQuaternionMultiply(z, quat);
+ quat = XMQuaternionNormalize(quat);
+
+ XMStoreFloat4(&rotation_local, quat);
+ }
+ void TransformComponent::Rotate(const XMFLOAT4& quaternion)
+ {
+ SetDirty();
+
+ XMVECTOR result = XMQuaternionMultiply(XMLoadFloat4(&rotation_local), XMLoadFloat4(&quaternion));
+ result = XMQuaternionNormalize(result);
+ XMStoreFloat4(&rotation_local, result);
+ }
+ void TransformComponent::Rotate(const XMVECTOR& quaternion)
+ {
+ XMFLOAT4 rotation;
+ XMStoreFloat4(&rotation, quaternion);
+ Rotate(rotation);
+ }
+ void TransformComponent::Scale(const XMFLOAT3& value)
+ {
+ SetDirty();
+ scale_local.x *= value.x;
+ scale_local.y *= value.y;
+ scale_local.z *= value.z;
+ }
+ void TransformComponent::Scale(const XMVECTOR& value)
+ {
+ XMFLOAT3 scale;
+ XMStoreFloat3(&scale, value);
+ Scale(scale);
+ }
+ void TransformComponent::MatrixTransform(const XMFLOAT4X4& matrix)
+ {
+ MatrixTransform(XMLoadFloat4x4(&matrix));
+ }
+ void TransformComponent::MatrixTransform(const XMMATRIX& matrix)
+ {
+ SetDirty();
+
+ XMVECTOR S;
+ XMVECTOR R;
+ XMVECTOR T;
+ XMMatrixDecompose(&S, &R, &T, GetLocalMatrix() * matrix);
+
+ XMStoreFloat3(&scale_local, S);
+ XMStoreFloat4(&rotation_local, R);
+ XMStoreFloat3(&translation_local, T);
+ }
+ void TransformComponent::Lerp(const TransformComponent& a, const TransformComponent& b, float t)
+ {
+ SetDirty();
+
+ XMVECTOR aS, aR, aT;
+ XMMatrixDecompose(&aS, &aR, &aT, XMLoadFloat4x4(&a.world));
+
+ XMVECTOR bS, bR, bT;
+ XMMatrixDecompose(&bS, &bR, &bT, XMLoadFloat4x4(&b.world));
+
+ XMVECTOR S = XMVectorLerp(aS, bS, t);
+ XMVECTOR R = XMQuaternionSlerp(aR, bR, t);
+ XMVECTOR T = XMVectorLerp(aT, bT, t);
+
+ XMStoreFloat3(&scale_local, S);
+ XMStoreFloat4(&rotation_local, R);
+ XMStoreFloat3(&translation_local, T);
+ }
+ void TransformComponent::CatmullRom(const TransformComponent& a, const TransformComponent& b, const TransformComponent& c, const TransformComponent& d, float t)
+ {
+ SetDirty();
+
+ XMVECTOR aS, aR, aT;
+ XMMatrixDecompose(&aS, &aR, &aT, XMLoadFloat4x4(&a.world));
+
+ XMVECTOR bS, bR, bT;
+ XMMatrixDecompose(&bS, &bR, &bT, XMLoadFloat4x4(&b.world));
+
+ XMVECTOR cS, cR, cT;
+ XMMatrixDecompose(&cS, &cR, &cT, XMLoadFloat4x4(&c.world));
+
+ XMVECTOR dS, dR, dT;
+ XMMatrixDecompose(&dS, &dR, &dT, XMLoadFloat4x4(&d.world));
+
+ XMVECTOR T = XMVectorCatmullRom(aT, bT, cT, dT, t);
+
+ XMVECTOR setupA;
+ XMVECTOR setupB;
+ XMVECTOR setupC;
+
+ aR = XMQuaternionNormalize(aR);
+ bR = XMQuaternionNormalize(bR);
+ cR = XMQuaternionNormalize(cR);
+ dR = XMQuaternionNormalize(dR);
+
+ XMQuaternionSquadSetup(&setupA, &setupB, &setupC, aR, bR, cR, dR);
+ XMVECTOR R = XMQuaternionSquad(bR, setupA, setupB, setupC, t);
+
+ XMVECTOR S = XMVectorCatmullRom(aS, bS, cS, dS, t);
+
+ XMStoreFloat3(&translation_local, T);
+ XMStoreFloat4(&rotation_local, R);
+ XMStoreFloat3(&scale_local, S);
+ }
+
+ void MaterialComponent::WriteShaderMaterial(ShaderMaterial* dest) const
+ {
+ ShaderMaterial material;
+ material.baseColor = baseColor;
+ material.emissive_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(emissiveColor.x * emissiveColor.w, emissiveColor.y * emissiveColor.w, emissiveColor.z * emissiveColor.w));
+ material.specular_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(specularColor.x * specularColor.w, specularColor.y * specularColor.w, specularColor.z * specularColor.w));
+ material.texMulAdd = texMulAdd;
+ material.roughness = roughness;
+ material.reflectance = reflectance;
+ material.metalness = metalness;
+ material.refraction = refraction;
+ material.normalMapStrength = (textures[NORMALMAP].resource.IsValid() ? normalMapStrength : 0);
+ material.parallaxOcclusionMapping = parallaxOcclusionMapping;
+ material.displacementMapping = displacementMapping;
+ XMFLOAT4 sss = subsurfaceScattering;
+ sss.x *= sss.w;
+ sss.y *= sss.w;
+ sss.z *= sss.w;
+ XMFLOAT4 sss_inv = XMFLOAT4(
+ sss_inv.x = 1.0f / ((1 + sss.x) * (1 + sss.x)),
+ sss_inv.y = 1.0f / ((1 + sss.y) * (1 + sss.y)),
+ sss_inv.z = 1.0f / ((1 + sss.z) * (1 + sss.z)),
+ sss_inv.w = 1.0f / ((1 + sss.w) * (1 + sss.w))
+ );
+ material.subsurfaceScattering = sss;
+ material.subsurfaceScattering_inv = sss_inv;
+ material.uvset_baseColorMap = textures[BASECOLORMAP].GetUVSet();
+ material.uvset_surfaceMap = textures[SURFACEMAP].GetUVSet();
+ material.uvset_normalMap = textures[NORMALMAP].GetUVSet();
+ material.uvset_displacementMap = textures[DISPLACEMENTMAP].GetUVSet();
+ material.uvset_emissiveMap = textures[EMISSIVEMAP].GetUVSet();
+ material.uvset_occlusionMap = textures[OCCLUSIONMAP].GetUVSet();
+ material.uvset_transmissionMap = textures[TRANSMISSIONMAP].GetUVSet();
+ material.uvset_sheenColorMap = textures[SHEENCOLORMAP].GetUVSet();
+ material.uvset_sheenRoughnessMap = textures[SHEENROUGHNESSMAP].GetUVSet();
+ material.uvset_clearcoatMap = textures[CLEARCOATMAP].GetUVSet();
+ material.uvset_clearcoatRoughnessMap = textures[CLEARCOATROUGHNESSMAP].GetUVSet();
+ material.uvset_clearcoatNormalMap = textures[CLEARCOATNORMALMAP].GetUVSet();
+ material.uvset_specularMap = textures[SPECULARMAP].GetUVSet();
+ material.sheenColor_r11g11b10 = wi::math::Pack_R11G11B10_FLOAT(XMFLOAT3(sheenColor.x, sheenColor.y, sheenColor.z));
+ material.sheenRoughness = sheenRoughness;
+ material.clearcoat = clearcoat;
+ material.clearcoatRoughness = clearcoatRoughness;
+ material.alphaTest = 1 - alphaRef;
+ material.layerMask = layerMask;
+ material.transmission = transmission;
+ material.shaderType = (uint)shaderType;
+ material.userdata = userdata;
+
+ material.options = 0;
+ if (IsUsingVertexColors())
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_USE_VERTEXCOLORS;
+ }
+ if (IsUsingSpecularGlossinessWorkflow())
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_SPECULARGLOSSINESS_WORKFLOW;
+ }
+ if (IsOcclusionEnabled_Primary())
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_OCCLUSION_PRIMARY;
+ }
+ if (IsOcclusionEnabled_Secondary())
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_OCCLUSION_SECONDARY;
+ }
+ if (IsUsingWind())
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_USE_WIND;
+ }
+ if (IsReceiveShadow())
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_RECEIVE_SHADOW;
+ }
+ if (IsCastingShadow())
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_CAST_SHADOW;
+ }
+ if (IsDoubleSided())
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_DOUBLE_SIDED;
+ }
+ if (GetRenderTypes() & RENDERTYPE_TRANSPARENT)
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_TRANSPARENT;
+ }
+ if (userBlendMode == BLENDMODE_ADDITIVE)
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_ADDITIVE;
+ }
+ if (shaderType == SHADERTYPE_UNLIT)
+ {
+ material.options |= SHADERMATERIAL_OPTION_BIT_UNLIT;
+ }
+
+ GraphicsDevice* device = wi::graphics::GetDevice();
+ material.texture_basecolormap_index = device->GetDescriptorIndex(textures[BASECOLORMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_surfacemap_index = device->GetDescriptorIndex(textures[SURFACEMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_emissivemap_index = device->GetDescriptorIndex(textures[EMISSIVEMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_normalmap_index = device->GetDescriptorIndex(textures[NORMALMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_displacementmap_index = device->GetDescriptorIndex(textures[DISPLACEMENTMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_occlusionmap_index = device->GetDescriptorIndex(textures[OCCLUSIONMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_transmissionmap_index = device->GetDescriptorIndex(textures[TRANSMISSIONMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_sheencolormap_index = device->GetDescriptorIndex(textures[SHEENCOLORMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_sheenroughnessmap_index = device->GetDescriptorIndex(textures[SHEENROUGHNESSMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_clearcoatmap_index = device->GetDescriptorIndex(textures[CLEARCOATMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_clearcoatroughnessmap_index = device->GetDescriptorIndex(textures[CLEARCOATROUGHNESSMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_clearcoatnormalmap_index = device->GetDescriptorIndex(textures[CLEARCOATNORMALMAP].GetGPUResource(), SubresourceType::SRV);
+ material.texture_specularmap_index = device->GetDescriptorIndex(textures[SPECULARMAP].GetGPUResource(), SubresourceType::SRV);
+
+ std::memcpy(dest, &material, sizeof(ShaderMaterial)); // memcpy whole structure into mapped pointer to avoid read from uncached memory
+ }
+ void MaterialComponent::WriteTextures(const wi::graphics::GPUResource** dest, int count) const
+ {
+ count = std::min(count, (int)TEXTURESLOT_COUNT);
+ for (int i = 0; i < count; ++i)
+ {
+ dest[i] = textures[i].GetGPUResource();
+ }
+ }
+ uint32_t MaterialComponent::GetRenderTypes() const
+ {
+ if (IsCustomShader() && customShaderID < (int)wi::renderer::GetCustomShaders().size())
+ {
+ auto& customShader = wi::renderer::GetCustomShaders()[customShaderID];
+ return customShader.renderTypeFlags;
+ }
+ if (shaderType == SHADERTYPE_WATER)
+ {
+ return RENDERTYPE_TRANSPARENT | RENDERTYPE_WATER;
+ }
+ if (transmission > 0)
+ {
+ return RENDERTYPE_TRANSPARENT;
+ }
+ if (userBlendMode == BLENDMODE_OPAQUE)
+ {
+ return RENDERTYPE_OPAQUE;
+ }
+ return RENDERTYPE_TRANSPARENT;
+ }
+ void MaterialComponent::CreateRenderData()
+ {
+ for (auto& x : textures)
+ {
+ if (!x.name.empty())
+ {
+ x.resource = wi::resourcemanager::Load(x.name, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA);
+ }
+ }
+ }
+ uint32_t MaterialComponent::GetStencilRef() const
+ {
+ return wi::renderer::CombineStencilrefs(engineStencilRef, userStencilRef);
+ }
+
+ void MeshComponent::CreateRenderData()
+ {
+ GraphicsDevice* device = wi::graphics::GetDevice();
+
+ generalBuffer = {};
+ streamoutBuffer = {};
+ ib = {};
+ vb_pos_nor_wind = {};
+ vb_tan = {};
+ vb_uvs = {};
+ vb_atl = {};
+ vb_col = {};
+ vb_bon = {};
+ so_pos_nor_wind = {};
+ so_tan = {};
+ so_pre = {};
+
+ if (vertex_tangents.empty() && !vertex_uvset_0.empty() && !vertex_normals.empty())
+ {
+ // Generate tangents if not found:
+ vertex_tangents.resize(vertex_positions.size());
+
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
+ {
+ const MeshComponent::MeshSubset& subset = subsets[subsetIndex];
+ for (size_t i = 0; i < subset.indexCount; i += 3)
+ {
+ const uint32_t i0 = indices[subset.indexOffset + i + 0];
+ const uint32_t i1 = indices[subset.indexOffset + i + 1];
+ const uint32_t i2 = indices[subset.indexOffset + i + 2];
+
+ const XMFLOAT3 v0 = vertex_positions[i0];
+ const XMFLOAT3 v1 = vertex_positions[i1];
+ const XMFLOAT3 v2 = vertex_positions[i2];
+
+ const XMFLOAT2 u0 = vertex_uvset_0[i0];
+ const XMFLOAT2 u1 = vertex_uvset_0[i1];
+ const XMFLOAT2 u2 = vertex_uvset_0[i2];
+
+ const XMFLOAT3 n0 = vertex_normals[i0];
+ const XMFLOAT3 n1 = vertex_normals[i1];
+ const XMFLOAT3 n2 = vertex_normals[i2];
+
+ const XMVECTOR nor0 = XMLoadFloat3(&n0);
+ const XMVECTOR nor1 = XMLoadFloat3(&n1);
+ const XMVECTOR nor2 = XMLoadFloat3(&n2);
+
+ const XMVECTOR facenormal = XMVector3Normalize(nor0 + nor1 + nor2);
+
+ const float x1 = v1.x - v0.x;
+ const float x2 = v2.x - v0.x;
+ const float y1 = v1.y - v0.y;
+ const float y2 = v2.y - v0.y;
+ const float z1 = v1.z - v0.z;
+ const float z2 = v2.z - v0.z;
+
+ const float s1 = u1.x - u0.x;
+ const float s2 = u2.x - u0.x;
+ const float t1 = u1.y - u0.y;
+ const float t2 = u2.y - u0.y;
+
+ const float r = 1.0f / (s1 * t2 - s2 * t1);
+ const XMVECTOR sdir = XMVectorSet((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
+ (t2 * z1 - t1 * z2) * r, 0);
+ const XMVECTOR tdir = XMVectorSet((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
+ (s1 * z2 - s2 * z1) * r, 0);
+
+ XMVECTOR tangent;
+ tangent = XMVector3Normalize(sdir - facenormal * XMVector3Dot(facenormal, sdir));
+ float sign = XMVectorGetX(XMVector3Dot(XMVector3Cross(tangent, facenormal), tdir)) < 0.0f ? -1.0f : 1.0f;
+
+ XMFLOAT3 t;
+ XMStoreFloat3(&t, tangent);
+
+ vertex_tangents[i0].x += t.x;
+ vertex_tangents[i0].y += t.y;
+ vertex_tangents[i0].z += t.z;
+ vertex_tangents[i0].w = sign;
+
+ vertex_tangents[i1].x += t.x;
+ vertex_tangents[i1].y += t.y;
+ vertex_tangents[i1].z += t.z;
+ vertex_tangents[i1].w = sign;
+
+ vertex_tangents[i2].x += t.x;
+ vertex_tangents[i2].y += t.y;
+ vertex_tangents[i2].z += t.z;
+ vertex_tangents[i2].w = sign;
+ }
+ }
+ }
+
+ {
+ vertex_subsets.resize(vertex_positions.size());
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
+ {
+ const MeshComponent::MeshSubset& subset = subsets[subsetIndex];
+ for (uint32_t i = 0; i < subset.indexCount; ++i)
+ {
+ uint32_t index = indices[subset.indexOffset + i];
+ vertex_subsets[index] = subsetIndex;
+ }
+ }
+ }
+
+ const size_t uv_count = std::max(vertex_uvset_0.size(), vertex_uvset_1.size());
+
+ GPUBufferDesc bd;
+ bd.usage = Usage::DEFAULT;
+ bd.bind_flags = BindFlag::VERTEX_BUFFER | BindFlag::INDEX_BUFFER | BindFlag::SHADER_RESOURCE;
+ bd.misc_flags = ResourceMiscFlag::BUFFER_RAW;
+ if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
+ {
+ bd.misc_flags |= ResourceMiscFlag::RAY_TRACING;
+ }
+ const uint64_t alignment = device->GetMinOffsetAlignment(&bd);
+ bd.size =
+ AlignTo(indices.size() * GetIndexStride(), alignment) +
+ AlignTo(vertex_positions.size() * sizeof(Vertex_POS), alignment) +
+ AlignTo(vertex_tangents.size() * sizeof(Vertex_TAN), alignment) +
+ AlignTo(uv_count * sizeof(Vertex_UVS), alignment) +
+ AlignTo(vertex_atlas.size() * sizeof(Vertex_TEX), alignment) +
+ AlignTo(vertex_colors.size() * sizeof(Vertex_COL), alignment) +
+ AlignTo(vertex_boneindices.size() * sizeof(Vertex_BON), alignment)
+ ;
+
+ // single allocation storage for GPU buffer data:
+ wi::vector buffer_data(bd.size);
+ uint64_t buffer_offset = 0ull;
+
+ // Create index buffer GPU data:
+ if (GetIndexFormat() == IndexBufferFormat::UINT32)
+ {
+ ib.offset = buffer_offset;
+ ib.size = indices.size() * sizeof(uint32_t);
+ uint32_t* indexdata = (uint32_t*)(buffer_data.data() + buffer_offset);
+ buffer_offset += AlignTo(ib.size, alignment);
+ for (size_t i = 0; i < indices.size(); ++i)
+ {
+ indexdata[i] = indices[i];
+ }
+ }
+ else
+ {
+ ib.offset = buffer_offset;
+ ib.size = indices.size() * sizeof(uint16_t);
+ uint16_t* indexdata = (uint16_t*)(buffer_data.data() + buffer_offset);
+ buffer_offset += AlignTo(ib.size, alignment);
+ for (size_t i = 0; i < indices.size(); ++i)
+ {
+ indexdata[i] = (uint16_t)indices[i];
+ }
+ }
+
+ XMFLOAT3 _min = XMFLOAT3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max());
+ XMFLOAT3 _max = XMFLOAT3(std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::lowest());
+
+ // vertexBuffer - POSITION + NORMAL + WIND:
+ {
+ if (!morph_targets.empty())
+ {
+ vertex_positions_morphed.resize(vertex_positions.size());
+ dirty_morph = true;
+ }
+
+ vb_pos_nor_wind.offset = buffer_offset;
+ vb_pos_nor_wind.size = vertex_positions.size() * sizeof(Vertex_POS);
+ Vertex_POS* vertices = (Vertex_POS*)(buffer_data.data() + buffer_offset);
+ buffer_offset += AlignTo(vb_pos_nor_wind.size, alignment);
+ for (size_t i = 0; i < vertex_positions.size(); ++i)
+ {
+ const XMFLOAT3& pos = vertex_positions[i];
+ XMFLOAT3 nor = vertex_normals.empty() ? XMFLOAT3(1, 1, 1) : vertex_normals[i];
+ XMStoreFloat3(&nor, XMVector3Normalize(XMLoadFloat3(&nor)));
+ const uint8_t wind = vertex_windweights.empty() ? 0xFF : vertex_windweights[i];
+ vertices[i].FromFULL(pos, nor, wind);
+
+ _min = wi::math::Min(_min, pos);
+ _max = wi::math::Max(_max, pos);
+ }
+ }
+
+ aabb = AABB(_min, _max);
+
+ // vertexBuffer - TANGENTS
+ if (!vertex_tangents.empty())
+ {
+ vb_tan.offset = buffer_offset;
+ vb_tan.size = vertex_tangents.size() * sizeof(Vertex_TAN);
+ Vertex_TAN* vertices = (Vertex_TAN*)(buffer_data.data() + buffer_offset);
+ buffer_offset += AlignTo(vb_tan.size, alignment);
+ for (size_t i = 0; i < vertex_tangents.size(); ++i)
+ {
+ vertices[i].FromFULL(vertex_tangents[i]);
+ }
+ }
+
+ // vertexBuffer - UV SETS
+ if (!vertex_uvset_0.empty() || !vertex_uvset_1.empty())
+ {
+ const XMFLOAT2* uv0_stream = vertex_uvset_0.empty() ? vertex_uvset_1.data() : vertex_uvset_0.data();
+ const XMFLOAT2* uv1_stream = vertex_uvset_1.empty() ? vertex_uvset_0.data() : vertex_uvset_1.data();
+
+ vb_uvs.offset = buffer_offset;
+ vb_uvs.size = uv_count * sizeof(Vertex_UVS);
+ Vertex_UVS* vertices = (Vertex_UVS*)(buffer_data.data() + buffer_offset);
+ buffer_offset += AlignTo(vb_uvs.size, alignment);
+ for (size_t i = 0; i < uv_count; ++i)
+ {
+ vertices[i].uv0.FromFULL(uv0_stream[i]);
+ vertices[i].uv1.FromFULL(uv1_stream[i]);
+ }
+ }
+
+ // vertexBuffer - ATLAS
+ if (!vertex_atlas.empty())
+ {
+ vb_atl.offset = buffer_offset;
+ vb_atl.size = vertex_atlas.size() * sizeof(Vertex_TEX);
+ Vertex_TEX* vertices = (Vertex_TEX*)(buffer_data.data() + buffer_offset);
+ buffer_offset += AlignTo(vb_atl.size, alignment);
+ for (size_t i = 0; i < vertex_atlas.size(); ++i)
+ {
+ vertices[i].FromFULL(vertex_atlas[i]);
+ }
+ }
+
+ // vertexBuffer - COLORS
+ if (!vertex_colors.empty())
+ {
+ vb_col.offset = buffer_offset;
+ vb_col.size = vertex_colors.size() * sizeof(Vertex_COL);
+ Vertex_COL* vertices = (Vertex_COL*)(buffer_data.data() + buffer_offset);
+ buffer_offset += AlignTo(vb_col.size, alignment);
+ for (size_t i = 0; i < vertex_colors.size(); ++i)
+ {
+ vertices[i].color = vertex_colors[i];
+ }
+ }
+
+ // skinning buffers:
+ if (!vertex_boneindices.empty())
+ {
+ vb_bon.offset = buffer_offset;
+ vb_bon.size = vertex_boneindices.size() * sizeof(Vertex_BON);
+ Vertex_BON* vertices = (Vertex_BON*)(buffer_data.data() + buffer_offset);
+ buffer_offset += AlignTo(vb_bon.size, alignment);
+ assert(vertex_boneindices.size() == vertex_boneweights.size());
+ for (size_t i = 0; i < vertex_boneindices.size(); ++i)
+ {
+ XMFLOAT4& wei = vertex_boneweights[i];
+ // normalize bone weights
+ float len = wei.x + wei.y + wei.z + wei.w;
+ if (len > 0)
+ {
+ wei.x /= len;
+ wei.y /= len;
+ wei.z /= len;
+ wei.w /= len;
+ }
+ vertices[i].FromFULL(vertex_boneindices[i], wei);
+ }
+ CreateStreamoutRenderData();
+ }
+
+ bool success = device->CreateBuffer(&bd, buffer_data.data(), &generalBuffer);
+ assert(success);
+ device->SetName(&generalBuffer, "MeshComponent::generalBuffer");
+
+ assert(ib.IsValid());
+ const Format ib_format = GetIndexFormat() == IndexBufferFormat::UINT32 ? Format::R32_UINT : Format::R16_UINT;
+ ib.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, ib.offset, ib.size, &ib_format);
+ ib.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, ib.subresource_srv);
+
+ assert(vb_pos_nor_wind.IsValid());
+ vb_pos_nor_wind.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_pos_nor_wind.offset, vb_pos_nor_wind.size);
+ vb_pos_nor_wind.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_pos_nor_wind.subresource_srv);
+
+ if (vb_tan.IsValid())
+ {
+ vb_tan.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_tan.offset, vb_tan.size);
+ vb_tan.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_tan.subresource_srv);
+ }
+ if (vb_uvs.IsValid())
+ {
+ vb_uvs.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_uvs.offset, vb_uvs.size);
+ vb_uvs.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_uvs.subresource_srv);
+ }
+ if (vb_atl.IsValid())
+ {
+ vb_atl.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_atl.offset, vb_atl.size);
+ vb_atl.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_atl.subresource_srv);
+ }
+ if (vb_col.IsValid())
+ {
+ vb_col.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_col.offset, vb_col.size);
+ vb_col.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_col.subresource_srv);
+ }
+ if (vb_bon.IsValid())
+ {
+ vb_bon.subresource_srv = device->CreateSubresource(&generalBuffer, SubresourceType::SRV, vb_bon.offset, vb_bon.size);
+ vb_bon.descriptor_srv = device->GetDescriptorIndex(&generalBuffer, SubresourceType::SRV, vb_bon.subresource_srv);
+ }
+
+ if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
+ {
+ BLAS_state = MeshComponent::BLAS_STATE_NEEDS_REBUILD;
+
+ const uint32_t lod_count = GetLODCount();
+ BLASes.resize(lod_count);
+ for (uint32_t lod = 0; lod < lod_count; ++lod)
+ {
+ RaytracingAccelerationStructureDesc desc;
+ desc.type = RaytracingAccelerationStructureDesc::Type::BOTTOMLEVEL;
+
+ if (streamoutBuffer.IsValid())
+ {
+ desc.flags |= RaytracingAccelerationStructureDesc::FLAG_ALLOW_UPDATE;
+ desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_BUILD;
+ }
+ else
+ {
+ desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_TRACE;
+ }
+
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ GetLODSubsetRange(lod, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
+ {
+ const MeshComponent::MeshSubset& subset = subsets[subsetIndex];
+ desc.bottom_level.geometries.emplace_back();
+ auto& geometry = desc.bottom_level.geometries.back();
+ geometry.type = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::Type::TRIANGLES;
+ geometry.triangles.vertex_buffer = generalBuffer;
+ geometry.triangles.vertex_byte_offset = vb_pos_nor_wind.offset;
+ geometry.triangles.index_buffer = generalBuffer;
+ geometry.triangles.index_format = GetIndexFormat();
+ geometry.triangles.index_count = subset.indexCount;
+ geometry.triangles.index_offset = ib.offset / GetIndexStride() + subset.indexOffset;
+ geometry.triangles.vertex_count = (uint32_t)vertex_positions.size();
+ geometry.triangles.vertex_format = Format::R32G32B32_FLOAT;
+ geometry.triangles.vertex_stride = sizeof(MeshComponent::Vertex_POS);
+ }
+
+ bool success = device->CreateRaytracingAccelerationStructure(&desc, &BLASes[lod]);
+ assert(success);
+ device->SetName(&BLASes[lod], std::string("MeshComponent::BLAS[LOD" + std::to_string(lod) + "]").c_str());
+ }
+ }
+ }
+ void MeshComponent::CreateStreamoutRenderData()
+ {
+ GraphicsDevice* device = wi::graphics::GetDevice();
+
+ GPUBufferDesc desc;
+ desc.usage = Usage::DEFAULT;
+ desc.bind_flags = BindFlag::VERTEX_BUFFER | BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
+ desc.misc_flags = ResourceMiscFlag::BUFFER_RAW;
+ if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
+ {
+ desc.misc_flags |= ResourceMiscFlag::RAY_TRACING;
+ }
+ const uint64_t alignment = device->GetMinOffsetAlignment(&desc);
+ desc.size =
+ AlignTo(vertex_positions.size() * sizeof(Vertex_POS) * 2, alignment) + // *2 because prevpos also goes into this!
+ AlignTo(vertex_tangents.size() * sizeof(Vertex_TAN), alignment)
+ ;
+
+ bool success = device->CreateBuffer(&desc, nullptr, &streamoutBuffer);
+ assert(success);
+ device->SetName(&streamoutBuffer, "MeshComponent::streamoutBuffer");
+
+ uint64_t buffer_offset = 0ull;
+
+ so_pos_nor_wind.offset = buffer_offset;
+ so_pos_nor_wind.size = vb_pos_nor_wind.size;
+ buffer_offset += AlignTo(so_pos_nor_wind.size, alignment);
+ so_pos_nor_wind.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_pos_nor_wind.offset, so_pos_nor_wind.size);
+ so_pos_nor_wind.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_pos_nor_wind.offset, so_pos_nor_wind.size);
+ so_pos_nor_wind.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_pos_nor_wind.subresource_srv);
+ so_pos_nor_wind.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_pos_nor_wind.subresource_uav);
+
+ if (vb_tan.IsValid())
+ {
+ so_tan.offset = buffer_offset;
+ so_tan.size = vb_tan.size;
+ buffer_offset += AlignTo(so_tan.size, alignment);
+ so_tan.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_tan.offset, so_tan.size);
+ so_tan.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_tan.offset, so_tan.size);
+ so_tan.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_tan.subresource_srv);
+ so_tan.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_tan.subresource_uav);
+ }
+
+ so_pre.offset = buffer_offset;
+ so_pre.size = vb_pos_nor_wind.size;
+ buffer_offset += AlignTo(so_pre.size, alignment);
+ so_pre.subresource_srv = device->CreateSubresource(&streamoutBuffer, SubresourceType::SRV, so_pre.offset, so_pre.size);
+ so_pre.subresource_uav = device->CreateSubresource(&streamoutBuffer, SubresourceType::UAV, so_pre.offset, so_pre.size);
+ so_pre.descriptor_srv = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::SRV, so_pre.subresource_srv);
+ so_pre.descriptor_uav = device->GetDescriptorIndex(&streamoutBuffer, SubresourceType::UAV, so_pre.subresource_uav);
+ }
+ void MeshComponent::ComputeNormals(COMPUTE_NORMALS compute)
+ {
+ // Start recalculating normals:
+
+ if (compute != COMPUTE_NORMALS_SMOOTH_FAST)
+ {
+ // Compute hard surface normals:
+
+ // Right now they are always computed even before smooth setting
+
+ wi::vector newIndexBuffer;
+ wi::vector newPositionsBuffer;
+ wi::vector newNormalsBuffer;
+ wi::vector newUV0Buffer;
+ wi::vector newUV1Buffer;
+ wi::vector newAtlasBuffer;
+ wi::vector newBoneIndicesBuffer;
+ wi::vector newBoneWeightsBuffer;
+ wi::vector newColorsBuffer;
+
+ for (size_t face = 0; face < indices.size() / 3; face++)
+ {
+ uint32_t i0 = indices[face * 3 + 0];
+ uint32_t i1 = indices[face * 3 + 1];
+ uint32_t i2 = indices[face * 3 + 2];
+
+ XMFLOAT3& p0 = vertex_positions[i0];
+ XMFLOAT3& p1 = vertex_positions[i1];
+ XMFLOAT3& p2 = vertex_positions[i2];
+
+ XMVECTOR U = XMLoadFloat3(&p2) - XMLoadFloat3(&p0);
+ XMVECTOR V = XMLoadFloat3(&p1) - XMLoadFloat3(&p0);
+
+ XMVECTOR N = XMVector3Cross(U, V);
+ N = XMVector3Normalize(N);
+
+ XMFLOAT3 normal;
+ XMStoreFloat3(&normal, N);
+
+ newPositionsBuffer.push_back(p0);
+ newPositionsBuffer.push_back(p1);
+ newPositionsBuffer.push_back(p2);
+
+ newNormalsBuffer.push_back(normal);
+ newNormalsBuffer.push_back(normal);
+ newNormalsBuffer.push_back(normal);
+
+ if (!vertex_uvset_0.empty())
+ {
+ newUV0Buffer.push_back(vertex_uvset_0[i0]);
+ newUV0Buffer.push_back(vertex_uvset_0[i1]);
+ newUV0Buffer.push_back(vertex_uvset_0[i2]);
+ }
+
+ if (!vertex_uvset_1.empty())
+ {
+ newUV1Buffer.push_back(vertex_uvset_1[i0]);
+ newUV1Buffer.push_back(vertex_uvset_1[i1]);
+ newUV1Buffer.push_back(vertex_uvset_1[i2]);
+ }
+
+ if (!vertex_atlas.empty())
+ {
+ newAtlasBuffer.push_back(vertex_atlas[i0]);
+ newAtlasBuffer.push_back(vertex_atlas[i1]);
+ newAtlasBuffer.push_back(vertex_atlas[i2]);
+ }
+
+ if (!vertex_boneindices.empty())
+ {
+ newBoneIndicesBuffer.push_back(vertex_boneindices[i0]);
+ newBoneIndicesBuffer.push_back(vertex_boneindices[i1]);
+ newBoneIndicesBuffer.push_back(vertex_boneindices[i2]);
+ }
+
+ if (!vertex_boneweights.empty())
+ {
+ newBoneWeightsBuffer.push_back(vertex_boneweights[i0]);
+ newBoneWeightsBuffer.push_back(vertex_boneweights[i1]);
+ newBoneWeightsBuffer.push_back(vertex_boneweights[i2]);
+ }
+
+ if (!vertex_colors.empty())
+ {
+ newColorsBuffer.push_back(vertex_colors[i0]);
+ newColorsBuffer.push_back(vertex_colors[i1]);
+ newColorsBuffer.push_back(vertex_colors[i2]);
+ }
+
+ newIndexBuffer.push_back(static_cast(newIndexBuffer.size()));
+ newIndexBuffer.push_back(static_cast(newIndexBuffer.size()));
+ newIndexBuffer.push_back(static_cast(newIndexBuffer.size()));
+ }
+
+ // For hard surface normals, we created a new mesh in the previous loop through faces, so swap data:
+ vertex_positions = newPositionsBuffer;
+ vertex_normals = newNormalsBuffer;
+ vertex_uvset_0 = newUV0Buffer;
+ vertex_uvset_1 = newUV1Buffer;
+ vertex_atlas = newAtlasBuffer;
+ vertex_colors = newColorsBuffer;
+ if (!vertex_boneindices.empty())
+ {
+ vertex_boneindices = newBoneIndicesBuffer;
+ }
+ if (!vertex_boneweights.empty())
+ {
+ vertex_boneweights = newBoneWeightsBuffer;
+ }
+ indices = newIndexBuffer;
+ }
+
+ switch (compute)
+ {
+ case MeshComponent::COMPUTE_NORMALS_HARD:
+ break;
+
+ case MeshComponent::COMPUTE_NORMALS_SMOOTH:
+ {
+ // Compute smooth surface normals:
+
+ // 1.) Zero normals, they will be averaged later
+ for (size_t i = 0; i < vertex_normals.size(); i++)
+ {
+ vertex_normals[i] = XMFLOAT3(0, 0, 0);
+ }
+
+ // 2.) Find identical vertices by POSITION, accumulate face normals
+ for (size_t i = 0; i < vertex_positions.size(); i++)
+ {
+ XMFLOAT3& v_search_pos = vertex_positions[i];
+
+ for (size_t ind = 0; ind < indices.size() / 3; ++ind)
+ {
+ uint32_t i0 = indices[ind * 3 + 0];
+ uint32_t i1 = indices[ind * 3 + 1];
+ uint32_t i2 = indices[ind * 3 + 2];
+
+ XMFLOAT3& v0 = vertex_positions[i0];
+ XMFLOAT3& v1 = vertex_positions[i1];
+ XMFLOAT3& v2 = vertex_positions[i2];
+
+ bool match_pos0 =
+ fabs(v_search_pos.x - v0.x) < FLT_EPSILON &&
+ fabs(v_search_pos.y - v0.y) < FLT_EPSILON &&
+ fabs(v_search_pos.z - v0.z) < FLT_EPSILON;
+
+ bool match_pos1 =
+ fabs(v_search_pos.x - v1.x) < FLT_EPSILON &&
+ fabs(v_search_pos.y - v1.y) < FLT_EPSILON &&
+ fabs(v_search_pos.z - v1.z) < FLT_EPSILON;
+
+ bool match_pos2 =
+ fabs(v_search_pos.x - v2.x) < FLT_EPSILON &&
+ fabs(v_search_pos.y - v2.y) < FLT_EPSILON &&
+ fabs(v_search_pos.z - v2.z) < FLT_EPSILON;
+
+ if (match_pos0 || match_pos1 || match_pos2)
+ {
+ XMVECTOR U = XMLoadFloat3(&v2) - XMLoadFloat3(&v0);
+ XMVECTOR V = XMLoadFloat3(&v1) - XMLoadFloat3(&v0);
+
+ XMVECTOR N = XMVector3Cross(U, V);
+ N = XMVector3Normalize(N);
+
+ XMFLOAT3 normal;
+ XMStoreFloat3(&normal, N);
+
+ vertex_normals[i].x += normal.x;
+ vertex_normals[i].y += normal.y;
+ vertex_normals[i].z += normal.z;
+ }
+
+ }
+ }
+
+ // 3.) Find duplicated vertices by POSITION and UV0 and UV1 and ATLAS and SUBSET and remove them:
+ for (auto& subset : subsets)
+ {
+ for (uint32_t i = 0; i < subset.indexCount - 1; i++)
+ {
+ uint32_t ind0 = indices[subset.indexOffset + (uint32_t)i];
+ const XMFLOAT3& p0 = vertex_positions[ind0];
+ const XMFLOAT2& u00 = vertex_uvset_0.empty() ? XMFLOAT2(0, 0) : vertex_uvset_0[ind0];
+ const XMFLOAT2& u10 = vertex_uvset_1.empty() ? XMFLOAT2(0, 0) : vertex_uvset_1[ind0];
+ const XMFLOAT2& at0 = vertex_atlas.empty() ? XMFLOAT2(0, 0) : vertex_atlas[ind0];
+
+ for (uint32_t j = i + 1; j < subset.indexCount; j++)
+ {
+ uint32_t ind1 = indices[subset.indexOffset + (uint32_t)j];
+
+ if (ind1 == ind0)
+ {
+ continue;
+ }
+
+ const XMFLOAT3& p1 = vertex_positions[ind1];
+ const XMFLOAT2& u01 = vertex_uvset_0.empty() ? XMFLOAT2(0, 0) : vertex_uvset_0[ind1];
+ const XMFLOAT2& u11 = vertex_uvset_1.empty() ? XMFLOAT2(0, 0) : vertex_uvset_1[ind1];
+ const XMFLOAT2& at1 = vertex_atlas.empty() ? XMFLOAT2(0, 0) : vertex_atlas[ind1];
+
+ const bool duplicated_pos =
+ fabs(p0.x - p1.x) < FLT_EPSILON &&
+ fabs(p0.y - p1.y) < FLT_EPSILON &&
+ fabs(p0.z - p1.z) < FLT_EPSILON;
+
+ const bool duplicated_uv0 =
+ fabs(u00.x - u01.x) < FLT_EPSILON &&
+ fabs(u00.y - u01.y) < FLT_EPSILON;
+
+ const bool duplicated_uv1 =
+ fabs(u10.x - u11.x) < FLT_EPSILON &&
+ fabs(u10.y - u11.y) < FLT_EPSILON;
+
+ const bool duplicated_atl =
+ fabs(at0.x - at1.x) < FLT_EPSILON &&
+ fabs(at0.y - at1.y) < FLT_EPSILON;
+
+ if (duplicated_pos && duplicated_uv0 && duplicated_uv1 && duplicated_atl)
+ {
+ // Erase vertices[ind1] because it is a duplicate:
+ if (ind1 < vertex_positions.size())
+ {
+ vertex_positions.erase(vertex_positions.begin() + ind1);
+ }
+ if (ind1 < vertex_normals.size())
+ {
+ vertex_normals.erase(vertex_normals.begin() + ind1);
+ }
+ if (ind1 < vertex_uvset_0.size())
+ {
+ vertex_uvset_0.erase(vertex_uvset_0.begin() + ind1);
+ }
+ if (ind1 < vertex_uvset_1.size())
+ {
+ vertex_uvset_1.erase(vertex_uvset_1.begin() + ind1);
+ }
+ if (ind1 < vertex_atlas.size())
+ {
+ vertex_atlas.erase(vertex_atlas.begin() + ind1);
+ }
+ if (ind1 < vertex_boneindices.size())
+ {
+ vertex_boneindices.erase(vertex_boneindices.begin() + ind1);
+ }
+ if (ind1 < vertex_boneweights.size())
+ {
+ vertex_boneweights.erase(vertex_boneweights.begin() + ind1);
+ }
+
+ // The vertices[ind1] was removed, so each index after that needs to be updated:
+ for (auto& index : indices)
+ {
+ if (index > ind1 && index > 0)
+ {
+ index--;
+ }
+ else if (index == ind1)
+ {
+ index = ind0;
+ }
+ }
+
+ }
+
+ }
+ }
+
+ }
+
+ }
+ break;
+
+ case MeshComponent::COMPUTE_NORMALS_SMOOTH_FAST:
+ {
+ for (size_t i = 0; i < vertex_normals.size(); i++)
+ {
+ vertex_normals[i] = XMFLOAT3(0, 0, 0);
+ }
+ for (size_t i = 0; i < indices.size() / 3; ++i)
+ {
+ uint32_t index1 = indices[i * 3];
+ uint32_t index2 = indices[i * 3 + 1];
+ uint32_t index3 = indices[i * 3 + 2];
+
+ XMVECTOR side1 = XMLoadFloat3(&vertex_positions[index1]) - XMLoadFloat3(&vertex_positions[index3]);
+ XMVECTOR side2 = XMLoadFloat3(&vertex_positions[index1]) - XMLoadFloat3(&vertex_positions[index2]);
+ XMVECTOR N = XMVector3Normalize(XMVector3Cross(side1, side2));
+ XMFLOAT3 normal;
+ XMStoreFloat3(&normal, N);
+
+ vertex_normals[index1].x += normal.x;
+ vertex_normals[index1].y += normal.y;
+ vertex_normals[index1].z += normal.z;
+
+ vertex_normals[index2].x += normal.x;
+ vertex_normals[index2].y += normal.y;
+ vertex_normals[index2].z += normal.z;
+
+ vertex_normals[index3].x += normal.x;
+ vertex_normals[index3].y += normal.y;
+ vertex_normals[index3].z += normal.z;
+ }
+ }
+ break;
+
+ }
+
+ vertex_tangents.clear(); // <- will be recomputed
+
+ CreateRenderData(); // <- normals will be normalized here!
+ }
+ void MeshComponent::FlipCulling()
+ {
+ for (size_t face = 0; face < indices.size() / 3; face++)
+ {
+ uint32_t i0 = indices[face * 3 + 0];
+ uint32_t i1 = indices[face * 3 + 1];
+ uint32_t i2 = indices[face * 3 + 2];
+
+ indices[face * 3 + 0] = i0;
+ indices[face * 3 + 1] = i2;
+ indices[face * 3 + 2] = i1;
+ }
+
+ CreateRenderData();
+ }
+ void MeshComponent::FlipNormals()
+ {
+ for (auto& normal : vertex_normals)
+ {
+ normal.x *= -1;
+ normal.y *= -1;
+ normal.z *= -1;
+ }
+
+ CreateRenderData();
+ }
+ void MeshComponent::Recenter()
+ {
+ XMFLOAT3 center = aabb.getCenter();
+
+ for (auto& pos : vertex_positions)
+ {
+ pos.x -= center.x;
+ pos.y -= center.y;
+ pos.z -= center.z;
+ }
+
+ CreateRenderData();
+ }
+ void MeshComponent::RecenterToBottom()
+ {
+ XMFLOAT3 center = aabb.getCenter();
+ center.y -= aabb.getHalfWidth().y;
+
+ for (auto& pos : vertex_positions)
+ {
+ pos.x -= center.x;
+ pos.y -= center.y;
+ pos.z -= center.z;
+ }
+
+ CreateRenderData();
+ }
+ Sphere MeshComponent::GetBoundingSphere() const
+ {
+ Sphere sphere;
+ sphere.center = aabb.getCenter();
+ sphere.radius = aabb.getRadius();
+ return sphere;
+ }
+
+ void ObjectComponent::ClearLightmap()
+ {
+ lightmap = Texture();
+ lightmapWidth = 0;
+ lightmapHeight = 0;
+ lightmapIterationCount = 0;
+ lightmapTextureData.clear();
+ SetLightmapRenderRequest(false);
+ }
+
+#if __has_include("OpenImageDenoise/oidn.hpp")
+#define OPEN_IMAGE_DENOISE
+#include "OpenImageDenoise/oidn.hpp"
+#pragma comment(lib,"OpenImageDenoise.lib")
+#pragma comment(lib,"tbb.lib")
+ // Also provide OpenImageDenoise.dll and tbb.dll near the exe!
+#endif
+ void ObjectComponent::SaveLightmap()
+ {
+ if (lightmap.IsValid() && has_flag(lightmap.desc.bind_flags, BindFlag::RENDER_TARGET))
+ {
+ SetLightmapRenderRequest(false);
+
+ bool success = wi::helper::saveTextureToMemory(lightmap, lightmapTextureData);
+ assert(success);
+
+#ifdef OPEN_IMAGE_DENOISE
+ if (success)
+ {
+ wi::vector texturedata_dst(lightmapTextureData.size());
+
+ size_t width = (size_t)lightmapWidth;
+ size_t height = (size_t)lightmapHeight;
+ {
+ // https://github.com/OpenImageDenoise/oidn#c11-api-example
+
+ // Create an Intel Open Image Denoise device
+ static oidn::DeviceRef device = oidn::newDevice();
+ static bool init = false;
+ if (!init)
+ {
+ device.commit();
+ init = true;
+ }
+
+ // Create a denoising filter
+ oidn::FilterRef filter = device.newFilter("RTLightmap");
+ filter.setImage("color", lightmapTextureData.data(), oidn::Format::Float3, width, height, 0, sizeof(XMFLOAT4));
+ filter.setImage("output", texturedata_dst.data(), oidn::Format::Float3, width, height, 0, sizeof(XMFLOAT4));
+ filter.commit();
+
+ // Filter the image
+ filter.execute();
+
+ // Check for errors
+ const char* errorMessage;
+ auto error = device.getError(errorMessage);
+ if (error != oidn::Error::None && error != oidn::Error::Cancelled)
+ {
+ wi::backlog::post(std::string("[OpenImageDenoise error] ") + errorMessage);
+ }
+ }
+
+ lightmapTextureData = std::move(texturedata_dst); // replace old (raw) data with denoised data
+ }
+#endif // OPEN_IMAGE_DENOISE
+
+ CompressLightmap();
+
+ wi::texturehelper::CreateTexture(lightmap, lightmapTextureData.data(), lightmapWidth, lightmapHeight, lightmap.desc.format);
+ wi::graphics::GetDevice()->SetName(&lightmap, "lightmap");
+ }
+ }
+ void ObjectComponent::CompressLightmap()
+ {
+
+ // BC6 Block compression code that uses DirectXTex library, but it's not cross platform, so disabled:
+#if 0
+ wi::Timer timer;
+ wi::backlog::post("compressing lightmap...");
+
+ lightmap.desc.Format = lightmap_block_format;
+ lightmap.desc.BindFlags = BindFlag::SHADER_RESOURCE;
+
+ static constexpr wi::graphics::FORMAT lightmap_block_format = wi::graphics::FORMAT_BC6H_UF16;
+ static constexpr uint32_t lightmap_blocksize = wi::graphics::GetFormatBlockSize(lightmap_block_format);
+ static_assert(lightmap_blocksize == 4u);
+ const uint32_t bc6_width = lightmapWidth / lightmap_blocksize;
+ const uint32_t bc6_height = lightmapHeight / lightmap_blocksize;
+ wi::vector bc6_data;
+ bc6_data.resize(sizeof(XMFLOAT4) * bc6_width * bc6_height);
+ const XMFLOAT4* raw_data = (const XMFLOAT4*)lightmapTextureData.data();
+
+ for (uint32_t x = 0; x < bc6_width; ++x)
+ {
+ for (uint32_t y = 0; y < bc6_height; ++y)
+ {
+ uint32_t bc6_idx = x + y * bc6_width;
+ uint8_t* ptr = (uint8_t*)((XMFLOAT4*)bc6_data.data() + bc6_idx);
+
+ XMVECTOR raw_vec[lightmap_blocksize * lightmap_blocksize];
+ for (uint32_t i = 0; i < lightmap_blocksize; ++i)
+ {
+ for (uint32_t j = 0; j < lightmap_blocksize; ++j)
+ {
+ uint32_t raw_idx = (x * lightmap_blocksize + i) + (y * lightmap_blocksize + j) * lightmapWidth;
+ uint32_t block_idx = i + j * lightmap_blocksize;
+ raw_vec[block_idx] = XMLoadFloat4(raw_data + raw_idx);
+ }
+ }
+ static_assert(arraysize(raw_vec) == 16); // it will work only for a certain block size!
+ D3DXEncodeBC6HU(ptr, raw_vec, 0);
+ }
+ }
+
+ lightmapTextureData = std::move(bc6_data); // replace old (raw) data with compressed data
+
+ wi::backlog::post(
+ "compressing lightmap [" +
+ std::to_string(lightmapWidth) +
+ "x" +
+ std::to_string(lightmapHeight) +
+ "] finished in " +
+ std::to_string(timer.elapsed_seconds()) +
+ " seconds"
+ );
+#else
+
+ // Simple compression to R11G11B10_FLOAT format:
+ using namespace PackedVector;
+ wi::vector packed_data;
+ packed_data.resize(sizeof(XMFLOAT3PK) * lightmapWidth * lightmapHeight);
+ XMFLOAT3PK* packed_ptr = (XMFLOAT3PK*)packed_data.data();
+ XMFLOAT4* raw_ptr = (XMFLOAT4*)lightmapTextureData.data();
+
+ uint32_t texelcount = lightmapWidth * lightmapHeight;
+ for (uint32_t i = 0; i < texelcount; ++i)
+ {
+ XMStoreFloat3PK(packed_ptr + i, XMLoadFloat4(raw_ptr + i));
+ }
+
+ lightmapTextureData = std::move(packed_data);
+ lightmap.desc.format = Format::R11G11B10_FLOAT;
+ lightmap.desc.bind_flags = BindFlag::SHADER_RESOURCE;
+
+#endif
+ }
+
+ void ArmatureComponent::CreateRenderData()
+ {
+ GraphicsDevice* device = wi::graphics::GetDevice();
+
+ GPUBufferDesc bd;
+ bd.size = sizeof(ShaderTransform) * (uint32_t)boneCollection.size();
+ bd.bind_flags = BindFlag::SHADER_RESOURCE;
+ bd.misc_flags = ResourceMiscFlag::BUFFER_RAW;
+ bd.stride = sizeof(ShaderTransform);
+
+ bool success = device->CreateBuffer(&bd, nullptr, &boneBuffer);
+ assert(success);
+ device->SetName(&boneBuffer, "ArmatureComponent::boneBuffer");
+ descriptor_srv = device->GetDescriptorIndex(&boneBuffer, SubresourceType::SRV);
+ }
+
+ AnimationComponent::AnimationChannel::PathDataType AnimationComponent::AnimationChannel::GetPathDataType() const
+ {
+ switch (path)
+ {
+ case wi::scene::AnimationComponent::AnimationChannel::Path::TRANSLATION:
+ return PathDataType::Float3;
+ case wi::scene::AnimationComponent::AnimationChannel::Path::ROTATION:
+ return PathDataType::Float4;
+ case wi::scene::AnimationComponent::AnimationChannel::Path::SCALE:
+ return PathDataType::Float3;
+ case wi::scene::AnimationComponent::AnimationChannel::Path::WEIGHTS:
+ return PathDataType::Weights;
+
+ case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_COLOR:
+ return PathDataType::Float3;
+ case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_RANGE:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE:
+ return PathDataType::Float;
+
+ case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_PLAY:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_STOP:
+ return PathDataType::Event;
+ case wi::scene::AnimationComponent::AnimationChannel::Path::SOUND_VOLUME:
+ return PathDataType::Float;
+
+ case wi::scene::AnimationComponent::AnimationChannel::Path::EMITTER_EMITCOUNT:
+ return PathDataType::Float;
+
+ case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_FOV:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_FOCAL_LENGTH:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SIZE:
+ return PathDataType::Float;
+ case wi::scene::AnimationComponent::AnimationChannel::Path::CAMERA_APERTURE_SHAPE:
+ return PathDataType::Float2;
+
+ case wi::scene::AnimationComponent::AnimationChannel::Path::SCRIPT_PLAY:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::SCRIPT_STOP:
+ return PathDataType::Event;
+
+ case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_COLOR:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_EMISSIVE:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_TEXMULADD:
+ return PathDataType::Float4;
+ case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_ROUGHNESS:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_REFLECTANCE:
+ case wi::scene::AnimationComponent::AnimationChannel::Path::MATERIAL_METALNESS:
+ return PathDataType::Float;
+
+ default:
+ assert(0);
+ break;
+ }
+ return PathDataType::Event;
+ }
+
+ void SoftBodyPhysicsComponent::CreateFromMesh(const MeshComponent& mesh)
+ {
+ vertex_positions_simulation.resize(mesh.vertex_positions.size());
+ vertex_tangents_tmp.resize(mesh.vertex_tangents.size());
+ vertex_tangents_simulation.resize(mesh.vertex_tangents.size());
+
+ XMMATRIX W = XMLoadFloat4x4(&worldMatrix);
+ XMFLOAT3 _min = XMFLOAT3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max());
+ XMFLOAT3 _max = XMFLOAT3(std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::lowest());
+ for (size_t i = 0; i < vertex_positions_simulation.size(); ++i)
+ {
+ XMFLOAT3 pos = mesh.vertex_positions[i];
+ XMStoreFloat3(&pos, XMVector3Transform(XMLoadFloat3(&pos), W));
+ XMFLOAT3 nor = mesh.vertex_normals.empty() ? XMFLOAT3(1, 1, 1) : mesh.vertex_normals[i];
+ XMStoreFloat3(&nor, XMVector3Normalize(XMVector3TransformNormal(XMLoadFloat3(&nor), W)));
+ const uint8_t wind = mesh.vertex_windweights.empty() ? 0xFF : mesh.vertex_windweights[i];
+ vertex_positions_simulation[i].FromFULL(pos, nor, wind);
+ _min = wi::math::Min(_min, pos);
+ _max = wi::math::Max(_max, pos);
+ }
+ aabb = AABB(_min, _max);
+
+ if (physicsToGraphicsVertexMapping.empty())
+ {
+ // Create a mapping that maps unique vertex positions to all vertex indices that share that. Unique vertex positions will make up the physics mesh:
+ wi::unordered_map uniquePositions;
+ graphicsToPhysicsVertexMapping.resize(mesh.vertex_positions.size());
+ physicsToGraphicsVertexMapping.clear();
+ weights.clear();
+
+ for (size_t i = 0; i < mesh.vertex_positions.size(); ++i)
+ {
+ const XMFLOAT3& position = mesh.vertex_positions[i];
+
+ size_t hashes[] = {
+ std::hash{}(position.x),
+ std::hash{}(position.y),
+ std::hash{}(position.z),
+ };
+ size_t vertexHash = (((hashes[0] ^ (hashes[1] << 1) >> 1) ^ (hashes[2] << 1)) >> 1);
+
+ if (uniquePositions.count(vertexHash) == 0)
+ {
+ uniquePositions[vertexHash] = (uint32_t)physicsToGraphicsVertexMapping.size();
+ physicsToGraphicsVertexMapping.push_back((uint32_t)i);
+ }
+ graphicsToPhysicsVertexMapping[i] = uniquePositions[vertexHash];
+ }
+
+ weights.resize(physicsToGraphicsVertexMapping.size());
+ std::fill(weights.begin(), weights.end(), 1.0f);
+ }
+ }
+
+ void CameraComponent::CreatePerspective(float newWidth, float newHeight, float newNear, float newFar, float newFOV)
+ {
+ zNearP = newNear;
+ zFarP = newFar;
+ width = newWidth;
+ height = newHeight;
+ fov = newFOV;
+
+ SetCustomProjectionEnabled(false);
+
+ UpdateCamera();
+ }
+ void CameraComponent::UpdateCamera()
+ {
+ if (!IsCustomProjectionEnabled())
+ {
+ XMStoreFloat4x4(&Projection, XMMatrixPerspectiveFovLH(fov, width / height, zFarP, zNearP)); // reverse zbuffer!
+ Projection.m[2][0] = jitter.x;
+ Projection.m[2][1] = jitter.y;
+ }
+
+ XMVECTOR _Eye = XMLoadFloat3(&Eye);
+ XMVECTOR _At = XMLoadFloat3(&At);
+ XMVECTOR _Up = XMLoadFloat3(&Up);
+
+ XMMATRIX _V = XMMatrixLookToLH(_Eye, _At, _Up);
+ XMStoreFloat4x4(&View, _V);
+
+ XMMATRIX _P = XMLoadFloat4x4(&Projection);
+ XMMATRIX _InvP = XMMatrixInverse(nullptr, _P);
+ XMStoreFloat4x4(&InvProjection, _InvP);
+
+ XMMATRIX _VP = XMMatrixMultiply(_V, _P);
+ XMStoreFloat4x4(&View, _V);
+ XMStoreFloat4x4(&VP, _VP);
+ XMStoreFloat4x4(&InvView, XMMatrixInverse(nullptr, _V));
+ XMStoreFloat4x4(&InvVP, XMMatrixInverse(nullptr, _VP));
+ XMStoreFloat4x4(&Projection, _P);
+ XMStoreFloat4x4(&InvProjection, XMMatrixInverse(nullptr, _P));
+
+ frustum.Create(_VP);
+ }
+ void CameraComponent::TransformCamera(const TransformComponent& transform)
+ {
+ XMMATRIX W = XMLoadFloat4x4(&transform.world);
+
+ XMVECTOR _Eye = XMVector3Transform(XMVectorSet(0, 0, 0, 1), W);
+ XMVECTOR _At = XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 0, 1, 0), W));
+ XMVECTOR _Up = XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W));
+
+ XMMATRIX _V = XMMatrixLookToLH(_Eye, _At, _Up);
+ XMStoreFloat4x4(&View, _V);
+
+ XMStoreFloat3x3(&rotationMatrix, XMMatrixInverse(nullptr, _V));
+
+ XMStoreFloat3(&Eye, _Eye);
+ XMStoreFloat3(&At, _At);
+ XMStoreFloat3(&Up, _Up);
+ }
+ void CameraComponent::Reflect(const XMFLOAT4& plane)
+ {
+ XMVECTOR _Eye = XMLoadFloat3(&Eye);
+ XMVECTOR _At = XMLoadFloat3(&At);
+ XMVECTOR _Up = XMLoadFloat3(&Up);
+ XMMATRIX _Ref = XMMatrixReflect(XMLoadFloat4(&plane));
+
+ // reverse clipping if behind clip plane ("if underwater")
+ clipPlane = plane;
+ float d = XMVectorGetX(XMPlaneDotCoord(XMLoadFloat4(&clipPlane), _Eye));
+ if (d < 0)
+ {
+ clipPlane.x *= -1;
+ clipPlane.y *= -1;
+ clipPlane.z *= -1;
+ clipPlane.w *= -1;
+ }
+
+ _Eye = XMVector3Transform(_Eye, _Ref);
+ _At = XMVector3TransformNormal(_At, _Ref);
+ _Up = XMVector3TransformNormal(_Up, _Ref);
+
+ XMStoreFloat3(&Eye, _Eye);
+ XMStoreFloat3(&At, _At);
+ XMStoreFloat3(&Up, _Up);
+
+ UpdateCamera();
+ }
+ void CameraComponent::Lerp(const CameraComponent& a, const CameraComponent& b, float t)
+ {
+ SetDirty();
+
+ width = wi::math::Lerp(a.width, b.width, t);
+ height = wi::math::Lerp(a.height, b.height, t);
+ zNearP = wi::math::Lerp(a.zNearP, b.zNearP, t);
+ zFarP = wi::math::Lerp(a.zFarP, b.zFarP, t);
+ fov = wi::math::Lerp(a.fov, b.fov, t);
+ focal_length = wi::math::Lerp(a.focal_length, b.focal_length, t);
+ aperture_size = wi::math::Lerp(a.aperture_size, b.aperture_size, t);
+ aperture_shape = wi::math::Lerp(a.aperture_shape, b.aperture_shape, t);
+ }
+
+ void ScriptComponent::CreateFromFile(const std::string& filename)
+ {
+ this->filename = filename;
+ resource = wi::resourcemanager::Load(filename, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA);
+ script.clear(); // will be created on first Update()
+ }
+
+}
diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h
new file mode 100644
index 000000000..b3e76958d
--- /dev/null
+++ b/WickedEngine/wiScene_Components.h
@@ -0,0 +1,1526 @@
+#pragma once
+#include "CommonInclude.h"
+#include "wiArchive.h"
+#include "wiCanvas.h"
+#include "wiAudio.h"
+#include "wiEnums.h"
+#include "wiOcean.h"
+#include "wiPrimitive.h"
+#include "shaders/ShaderInterop_Renderer.h"
+#include "wiResourceManager.h"
+#include "wiVector.h"
+#include "wiArchive.h"
+#include "wiRectPacker.h"
+#include "wiUnorderedSet.h"
+
+namespace wi::scene
+{
+
+ struct NameComponent
+ {
+ std::string name;
+
+ inline void operator=(const std::string& str) { name = str; }
+ inline void operator=(std::string&& str) { name = std::move(str); }
+ inline bool operator==(const std::string& str) const { return name.compare(str) == 0; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct LayerComponent
+ {
+ uint32_t layerMask = ~0u;
+
+ // Non-serialized attributes:
+ uint32_t propagationMask = ~0u; // This shouldn't be modified by user usually
+
+ inline uint32_t GetLayerMask() const { return layerMask & propagationMask; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct TransformComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DIRTY = 1 << 0,
+ };
+ uint32_t _flags = DIRTY;
+
+ XMFLOAT3 scale_local = XMFLOAT3(1, 1, 1);
+ XMFLOAT4 rotation_local = XMFLOAT4(0, 0, 0, 1); // this is a quaternion
+ XMFLOAT3 translation_local = XMFLOAT3(0, 0, 0);
+
+ // Non-serialized attributes:
+
+ // The world matrix can be computed from local scale, rotation, translation
+ // - by calling UpdateTransform()
+ // - or by calling SetDirty() and letting the TransformUpdateSystem handle the updating
+ XMFLOAT4X4 world = wi::math::IDENTITY_MATRIX;
+
+ inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
+ inline bool IsDirty() const { return _flags & DIRTY; }
+
+ XMFLOAT3 GetPosition() const;
+ XMFLOAT4 GetRotation() const;
+ XMFLOAT3 GetScale() const;
+ XMVECTOR GetPositionV() const;
+ XMVECTOR GetRotationV() const;
+ XMVECTOR GetScaleV() const;
+ // Computes the local space matrix from scale, rotation, translation and returns it
+ XMMATRIX GetLocalMatrix() const;
+ // Applies the local space to the world space matrix. This overwrites world matrix
+ void UpdateTransform();
+ // Apply a parent transform relative to the local space. This overwrites world matrix
+ void UpdateTransform_Parented(const TransformComponent& parent);
+ // Apply the world matrix to the local space. This overwrites scale, rotation, translation
+ void ApplyTransform();
+ // Clears the local space. This overwrites scale, rotation, translation
+ void ClearTransform();
+ void Translate(const XMFLOAT3& value);
+ void Translate(const XMVECTOR& value);
+ void RotateRollPitchYaw(const XMFLOAT3& value);
+ void Rotate(const XMFLOAT4& quaternion);
+ void Rotate(const XMVECTOR& quaternion);
+ void Scale(const XMFLOAT3& value);
+ void Scale(const XMVECTOR& value);
+ void MatrixTransform(const XMFLOAT4X4& matrix);
+ void MatrixTransform(const XMMATRIX& matrix);
+ void Lerp(const TransformComponent& a, const TransformComponent& b, float t);
+ void CatmullRom(const TransformComponent& a, const TransformComponent& b, const TransformComponent& c, const TransformComponent& d, float t);
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct HierarchyComponent
+ {
+ wi::ecs::Entity parentID = wi::ecs::INVALID_ENTITY;
+ uint32_t layerMask_bind; // saved child layermask at the time of binding
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct MaterialComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DIRTY = 1 << 0,
+ CAST_SHADOW = 1 << 1,
+ _DEPRECATED_PLANAR_REFLECTION = 1 << 2,
+ _DEPRECATED_WATER = 1 << 3,
+ _DEPRECATED_FLIP_NORMALMAP = 1 << 4,
+ USE_VERTEXCOLORS = 1 << 5,
+ SPECULAR_GLOSSINESS_WORKFLOW = 1 << 6,
+ OCCLUSION_PRIMARY = 1 << 7,
+ OCCLUSION_SECONDARY = 1 << 8,
+ USE_WIND = 1 << 9,
+ DISABLE_RECEIVE_SHADOW = 1 << 10,
+ DOUBLE_SIDED = 1 << 11,
+ OUTLINE = 1 << 12,
+ };
+ uint32_t _flags = CAST_SHADOW;
+
+ enum SHADERTYPE
+ {
+ SHADERTYPE_PBR,
+ SHADERTYPE_PBR_PLANARREFLECTION,
+ SHADERTYPE_PBR_PARALLAXOCCLUSIONMAPPING,
+ SHADERTYPE_PBR_ANISOTROPIC,
+ SHADERTYPE_WATER,
+ SHADERTYPE_CARTOON,
+ SHADERTYPE_UNLIT,
+ SHADERTYPE_PBR_CLOTH,
+ SHADERTYPE_PBR_CLEARCOAT,
+ SHADERTYPE_PBR_CLOTH_CLEARCOAT,
+ SHADERTYPE_COUNT
+ } shaderType = SHADERTYPE_PBR;
+ static_assert(SHADERTYPE_COUNT == SHADERTYPE_BIN_COUNT, "These values must match!");
+
+ inline static const wi::vector shaderTypeDefines[] = {
+ {}, // SHADERTYPE_PBR,
+ {"PLANARREFLECTION"}, // SHADERTYPE_PBR_PLANARREFLECTION,
+ {"PARALLAXOCCLUSIONMAPPING"}, // SHADERTYPE_PBR_PARALLAXOCCLUSIONMAPPING,
+ {"ANISOTROPIC"}, // SHADERTYPE_PBR_ANISOTROPIC,
+ {"WATER"}, // SHADERTYPE_WATER,
+ {"CARTOON"}, // SHADERTYPE_CARTOON,
+ {"UNLIT"}, // SHADERTYPE_UNLIT,
+ {"SHEEN"}, // SHADERTYPE_PBR_CLOTH,
+ {"CLEARCOAT"}, // SHADERTYPE_PBR_CLEARCOAT,
+ {"SHEEN", "CLEARCOAT"}, // SHADERTYPE_PBR_CLOTH_CLEARCOAT,
+ };
+ static_assert(SHADERTYPE_COUNT == arraysize(shaderTypeDefines), "These values must match!");
+
+ wi::enums::STENCILREF engineStencilRef = wi::enums::STENCILREF_DEFAULT;
+ uint8_t userStencilRef = 0;
+ wi::enums::BLENDMODE userBlendMode = wi::enums::BLENDMODE_OPAQUE;
+
+ XMFLOAT4 baseColor = XMFLOAT4(1, 1, 1, 1);
+ XMFLOAT4 specularColor = XMFLOAT4(1, 1, 1, 1);
+ XMFLOAT4 emissiveColor = XMFLOAT4(1, 1, 1, 0);
+ XMFLOAT4 subsurfaceScattering = XMFLOAT4(1, 1, 1, 0);
+ XMFLOAT4 texMulAdd = XMFLOAT4(1, 1, 0, 0);
+ float roughness = 0.2f;
+ float reflectance = 0.02f;
+ float metalness = 0.0f;
+ float normalMapStrength = 1.0f;
+ float parallaxOcclusionMapping = 0.0f;
+ float displacementMapping = 0.0f;
+ float refraction = 0.0f;
+ float transmission = 0.0f;
+ float alphaRef = 1.0f;
+
+ XMFLOAT4 sheenColor = XMFLOAT4(1, 1, 1, 1);
+ float sheenRoughness = 0;
+ float clearcoat = 0;
+ float clearcoatRoughness = 0;
+
+ wi::graphics::ShadingRate shadingRate = wi::graphics::ShadingRate::RATE_1X1;
+
+ XMFLOAT2 texAnimDirection = XMFLOAT2(0, 0);
+ float texAnimFrameRate = 0.0f;
+ float texAnimElapsedTime = 0.0f;
+
+ enum TEXTURESLOT
+ {
+ BASECOLORMAP,
+ NORMALMAP,
+ SURFACEMAP,
+ EMISSIVEMAP,
+ DISPLACEMENTMAP,
+ OCCLUSIONMAP,
+ TRANSMISSIONMAP,
+ SHEENCOLORMAP,
+ SHEENROUGHNESSMAP,
+ CLEARCOATMAP,
+ CLEARCOATROUGHNESSMAP,
+ CLEARCOATNORMALMAP,
+ SPECULARMAP,
+
+ TEXTURESLOT_COUNT
+ };
+ struct TextureMap
+ {
+ std::string name;
+ uint32_t uvset = 0;
+ wi::Resource resource;
+ const wi::graphics::GPUResource* GetGPUResource() const
+ {
+ if (!resource.IsValid() || !resource.GetTexture().IsValid())
+ return nullptr;
+ return &resource.GetTexture();
+ }
+ int GetUVSet() const
+ {
+ if (!resource.IsValid() || !resource.GetTexture().IsValid())
+ return -1;
+ return (int)uvset;
+ }
+ };
+ TextureMap textures[TEXTURESLOT_COUNT];
+
+ int customShaderID = -1;
+ uint4 userdata = uint4(0, 0, 0, 0); // can be accessed by custom shader
+
+ // Non-serialized attributes:
+ uint32_t layerMask = ~0u;
+
+ // User stencil value can be in range [0, 15]
+ inline void SetUserStencilRef(uint8_t value)
+ {
+ assert(value < 16);
+ userStencilRef = value & 0x0F;
+ }
+ uint32_t GetStencilRef() const;
+
+ inline float GetOpacity() const { return baseColor.w; }
+ inline float GetEmissiveStrength() const { return emissiveColor.w; }
+ inline int GetCustomShaderID() const { return customShaderID; }
+
+ inline bool HasPlanarReflection() const { return shaderType == SHADERTYPE_PBR_PLANARREFLECTION || shaderType == SHADERTYPE_WATER; }
+
+ inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
+ inline bool IsDirty() const { return _flags & DIRTY; }
+
+ inline void SetCastShadow(bool value) { SetDirty(); if (value) { _flags |= CAST_SHADOW; } else { _flags &= ~CAST_SHADOW; } }
+ inline void SetReceiveShadow(bool value) { SetDirty(); if (value) { _flags &= ~DISABLE_RECEIVE_SHADOW; } else { _flags |= DISABLE_RECEIVE_SHADOW; } }
+ inline void SetOcclusionEnabled_Primary(bool value) { SetDirty(); if (value) { _flags |= OCCLUSION_PRIMARY; } else { _flags &= ~OCCLUSION_PRIMARY; } }
+ inline void SetOcclusionEnabled_Secondary(bool value) { SetDirty(); if (value) { _flags |= OCCLUSION_SECONDARY; } else { _flags &= ~OCCLUSION_SECONDARY; } }
+
+ inline wi::enums::BLENDMODE GetBlendMode() const { if (userBlendMode == wi::enums::BLENDMODE_OPAQUE && (GetRenderTypes() & wi::enums::RENDERTYPE_TRANSPARENT)) return wi::enums::BLENDMODE_ALPHA; else return userBlendMode; }
+ inline bool IsCastingShadow() const { return _flags & CAST_SHADOW; }
+ inline bool IsAlphaTestEnabled() const { return alphaRef <= 1.0f - 1.0f / 256.0f; }
+ inline bool IsUsingVertexColors() const { return _flags & USE_VERTEXCOLORS; }
+ inline bool IsUsingWind() const { return _flags & USE_WIND; }
+ inline bool IsReceiveShadow() const { return (_flags & DISABLE_RECEIVE_SHADOW) == 0; }
+ inline bool IsUsingSpecularGlossinessWorkflow() const { return _flags & SPECULAR_GLOSSINESS_WORKFLOW; }
+ inline bool IsOcclusionEnabled_Primary() const { return _flags & OCCLUSION_PRIMARY; }
+ inline bool IsOcclusionEnabled_Secondary() const { return _flags & OCCLUSION_SECONDARY; }
+ inline bool IsCustomShader() const { return customShaderID >= 0; }
+ inline bool IsDoubleSided() const { return _flags & DOUBLE_SIDED; }
+ inline bool IsOutlineEnabled() const { return _flags & OUTLINE; }
+
+ inline void SetBaseColor(const XMFLOAT4& value) { SetDirty(); baseColor = value; }
+ inline void SetSpecularColor(const XMFLOAT4& value) { SetDirty(); specularColor = value; }
+ inline void SetEmissiveColor(const XMFLOAT4& value) { SetDirty(); emissiveColor = value; }
+ inline void SetRoughness(float value) { SetDirty(); roughness = value; }
+ inline void SetReflectance(float value) { SetDirty(); reflectance = value; }
+ inline void SetMetalness(float value) { SetDirty(); metalness = value; }
+ inline void SetEmissiveStrength(float value) { SetDirty(); emissiveColor.w = value; }
+ inline void SetTransmissionAmount(float value) { SetDirty(); transmission = value; }
+ inline void SetRefractionAmount(float value) { SetDirty(); refraction = value; }
+ inline void SetNormalMapStrength(float value) { SetDirty(); normalMapStrength = value; }
+ inline void SetParallaxOcclusionMapping(float value) { SetDirty(); parallaxOcclusionMapping = value; }
+ inline void SetDisplacementMapping(float value) { SetDirty(); displacementMapping = value; }
+ inline void SetSubsurfaceScatteringColor(XMFLOAT3 value)
+ {
+ SetDirty();
+ subsurfaceScattering.x = value.x;
+ subsurfaceScattering.y = value.y;
+ subsurfaceScattering.z = value.z;
+ }
+ inline void SetSubsurfaceScatteringAmount(float value) { SetDirty(); subsurfaceScattering.w = value; }
+ inline void SetOpacity(float value) { SetDirty(); baseColor.w = value; }
+ inline void SetAlphaRef(float value) { SetDirty(); alphaRef = value; }
+ inline void SetUseVertexColors(bool value) { SetDirty(); if (value) { _flags |= USE_VERTEXCOLORS; } else { _flags &= ~USE_VERTEXCOLORS; } }
+ inline void SetUseWind(bool value) { SetDirty(); if (value) { _flags |= USE_WIND; } else { _flags &= ~USE_WIND; } }
+ inline void SetUseSpecularGlossinessWorkflow(bool value) { SetDirty(); if (value) { _flags |= SPECULAR_GLOSSINESS_WORKFLOW; } else { _flags &= ~SPECULAR_GLOSSINESS_WORKFLOW; } }
+ inline void SetSheenColor(const XMFLOAT3& value)
+ {
+ sheenColor = XMFLOAT4(value.x, value.y, value.z, sheenColor.w);
+ SetDirty();
+ }
+ inline void SetSheenRoughness(float value) { sheenRoughness = value; SetDirty(); }
+ inline void SetClearcoatFactor(float value) { clearcoat = value; SetDirty(); }
+ inline void SetClearcoatRoughness(float value) { clearcoatRoughness = value; SetDirty(); }
+ inline void SetCustomShaderID(int id) { customShaderID = id; }
+ inline void DisableCustomShader() { customShaderID = -1; }
+ inline void SetDoubleSided(bool value = true) { if (value) { _flags |= DOUBLE_SIDED; } else { _flags &= ~DOUBLE_SIDED; } }
+ inline void SetOutlineEnabled(bool value = true) { if (value) { _flags |= OUTLINE; } else { _flags &= ~OUTLINE; } }
+
+ // The MaterialComponent will be written to ShaderMaterial (a struct that is optimized for GPU use)
+ void WriteShaderMaterial(ShaderMaterial* dest) const;
+
+ // Retrieve the array of textures from the material
+ void WriteTextures(const wi::graphics::GPUResource** dest, int count) const;
+
+ // Returns the bitwise OR of all the RENDERTYPE flags applicable to this material
+ uint32_t GetRenderTypes() const;
+
+ // Create constant buffer and texture resources for GPU
+ void CreateRenderData();
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct MeshComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ RENDERABLE = 1 << 0,
+ DOUBLE_SIDED = 1 << 1,
+ DYNAMIC = 1 << 2,
+ _DEPRECATED_TERRAIN = 1 << 3,
+ _DEPRECATED_DIRTY_MORPH = 1 << 4,
+ _DEPRECATED_DIRTY_BINDLESS = 1 << 5,
+ TLAS_FORCE_DOUBLE_SIDED = 1 << 6,
+ };
+ uint32_t _flags = RENDERABLE;
+
+ wi::vector vertex_positions;
+ wi::vector vertex_normals;
+ wi::vector vertex_tangents;
+ wi::vector vertex_uvset_0;
+ wi::vector vertex_uvset_1;
+ wi::vector vertex_boneindices;
+ wi::vector vertex_boneweights;
+ wi::vector vertex_atlas;
+ wi::vector vertex_colors;
+ wi::vector vertex_windweights;
+ wi::vector indices;
+
+ struct MeshSubset
+ {
+ wi::ecs::Entity materialID = wi::ecs::INVALID_ENTITY;
+ uint32_t indexOffset = 0;
+ uint32_t indexCount = 0;
+
+ // Non-serialized attributes:
+ uint32_t materialIndex = 0;
+ };
+ wi::vector subsets;
+
+ float tessellationFactor = 0.0f;
+ wi::ecs::Entity armatureID = wi::ecs::INVALID_ENTITY;
+
+ struct MorphTarget
+ {
+ wi::vector vertex_positions;
+ wi::vector vertex_normals;
+ wi::vector sparse_indices; // optional, these can be used to target vertices indirectly
+ float weight = 0;
+ };
+ wi::vector morph_targets;
+
+ uint32_t subsets_per_lod = 0; // this needs to be specified if there are multiple LOD levels
+
+ // Non-serialized attributes:
+ wi::primitive::AABB aabb;
+ wi::graphics::GPUBuffer generalBuffer; // index buffer + all static vertex buffers
+ wi::graphics::GPUBuffer streamoutBuffer; // all dynamic vertex buffers
+ struct BufferView
+ {
+ uint64_t offset = ~0ull;
+ uint64_t size = 0ull;
+ int subresource_srv = -1;
+ int descriptor_srv = -1;
+ int subresource_uav = -1;
+ int descriptor_uav = -1;
+
+ constexpr bool IsValid() const
+ {
+ return offset != ~0ull;
+ }
+ };
+ BufferView ib;
+ BufferView vb_pos_nor_wind;
+ BufferView vb_tan;
+ BufferView vb_uvs;
+ BufferView vb_atl;
+ BufferView vb_col;
+ BufferView vb_bon;
+ BufferView so_pos_nor_wind;
+ BufferView so_tan;
+ BufferView so_pre;
+ wi::vector vertex_subsets;
+ uint32_t geometryOffset = 0;
+ uint32_t meshletCount = 0;
+
+ wi::vector BLASes; // one BLAS per LOD
+ enum BLAS_STATE
+ {
+ BLAS_STATE_NEEDS_REBUILD,
+ BLAS_STATE_NEEDS_REFIT,
+ BLAS_STATE_COMPLETE,
+ };
+ mutable BLAS_STATE BLAS_state = BLAS_STATE_NEEDS_REBUILD;
+
+ mutable bool dirty_morph = false;
+
+ inline void SetRenderable(bool value) { if (value) { _flags |= RENDERABLE; } else { _flags &= ~RENDERABLE; } }
+ inline void SetDoubleSided(bool value) { if (value) { _flags |= DOUBLE_SIDED; } else { _flags &= ~DOUBLE_SIDED; } }
+ inline void SetDynamic(bool value) { if (value) { _flags |= DYNAMIC; } else { _flags &= ~DYNAMIC; } }
+
+ inline bool IsRenderable() const { return _flags & RENDERABLE; }
+ inline bool IsDoubleSided() const { return _flags & DOUBLE_SIDED; }
+ inline bool IsDynamic() const { return _flags & DYNAMIC; }
+
+ inline float GetTessellationFactor() const { return tessellationFactor; }
+ inline wi::graphics::IndexBufferFormat GetIndexFormat() const { return vertex_positions.size() > 65536 ? wi::graphics::IndexBufferFormat::UINT32 : wi::graphics::IndexBufferFormat::UINT16; }
+ inline size_t GetIndexStride() const { return GetIndexFormat() == wi::graphics::IndexBufferFormat::UINT32 ? sizeof(uint32_t) : sizeof(uint16_t); }
+ inline bool IsSkinned() const { return armatureID != wi::ecs::INVALID_ENTITY; }
+ inline uint32_t GetLODCount() const { return subsets_per_lod == 0 ? 1 : ((uint32_t)subsets.size() / subsets_per_lod); }
+ inline void GetLODSubsetRange(uint32_t lod, uint32_t& first_subset, uint32_t& last_subset) const
+ {
+ first_subset = 0;
+ last_subset = (uint32_t)subsets.size();
+ if (subsets_per_lod > 0)
+ {
+ lod = std::min(lod, GetLODCount() - 1);
+ first_subset = subsets_per_lod * lod;
+ last_subset = first_subset + subsets_per_lod;
+ }
+ }
+
+ // Recreates GPU resources for index/vertex buffers
+ void CreateRenderData();
+ void CreateStreamoutRenderData();
+
+ enum COMPUTE_NORMALS
+ {
+ COMPUTE_NORMALS_HARD, // hard face normals, can result in additional vertices generated
+ COMPUTE_NORMALS_SMOOTH, // smooth per vertex normals, this can remove/simplyfy geometry, but slow
+ COMPUTE_NORMALS_SMOOTH_FAST // average normals, vertex count will be unchanged, fast
+ };
+ void ComputeNormals(COMPUTE_NORMALS compute);
+ void FlipCulling();
+ void FlipNormals();
+ void Recenter();
+ void RecenterToBottom();
+ wi::primitive::Sphere GetBoundingSphere() const;
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+
+
+ struct Vertex_POS
+ {
+ XMFLOAT3 pos = XMFLOAT3(0.0f, 0.0f, 0.0f);
+ uint32_t normal_wind = 0;
+
+ void FromFULL(const XMFLOAT3& _pos, const XMFLOAT3& _nor, uint8_t wind)
+ {
+ pos.x = _pos.x;
+ pos.y = _pos.y;
+ pos.z = _pos.z;
+ MakeFromParams(_nor, wind);
+ }
+ inline XMVECTOR LoadPOS() const
+ {
+ return XMLoadFloat3(&pos);
+ }
+ inline XMVECTOR LoadNOR() const
+ {
+ XMFLOAT3 N = GetNor_FULL();
+ return XMLoadFloat3(&N);
+ }
+ inline void MakeFromParams(const XMFLOAT3& normal)
+ {
+ normal_wind = normal_wind & 0xFF000000; // reset only the normals
+
+ normal_wind |= (uint32_t)((normal.x * 0.5f + 0.5f) * 255.0f) << 0;
+ normal_wind |= (uint32_t)((normal.y * 0.5f + 0.5f) * 255.0f) << 8;
+ normal_wind |= (uint32_t)((normal.z * 0.5f + 0.5f) * 255.0f) << 16;
+ }
+ inline void MakeFromParams(const XMFLOAT3& normal, uint8_t wind)
+ {
+ normal_wind = 0;
+
+ normal_wind |= (uint32_t)((normal.x * 0.5f + 0.5f) * 255.0f) << 0;
+ normal_wind |= (uint32_t)((normal.y * 0.5f + 0.5f) * 255.0f) << 8;
+ normal_wind |= (uint32_t)((normal.z * 0.5f + 0.5f) * 255.0f) << 16;
+ normal_wind |= (uint32_t)wind << 24;
+ }
+ inline XMFLOAT3 GetNor_FULL() const
+ {
+ XMFLOAT3 nor_FULL(0, 0, 0);
+
+ nor_FULL.x = (float)((normal_wind >> 0) & 0x000000FF) / 255.0f * 2.0f - 1.0f;
+ nor_FULL.y = (float)((normal_wind >> 8) & 0x000000FF) / 255.0f * 2.0f - 1.0f;
+ nor_FULL.z = (float)((normal_wind >> 16) & 0x000000FF) / 255.0f * 2.0f - 1.0f;
+
+ return nor_FULL;
+ }
+ inline uint8_t GetWind() const
+ {
+ return (normal_wind >> 24) & 0x000000FF;
+ }
+
+ static const wi::graphics::Format FORMAT = wi::graphics::Format::R32G32B32A32_FLOAT;
+ };
+ struct Vertex_TEX
+ {
+ XMHALF2 tex = XMHALF2(0.0f, 0.0f);
+
+ void FromFULL(const XMFLOAT2& texcoords)
+ {
+ tex = XMHALF2(texcoords.x, texcoords.y);
+ }
+
+ static const wi::graphics::Format FORMAT = wi::graphics::Format::R16G16_FLOAT;
+ };
+ struct Vertex_UVS
+ {
+ Vertex_TEX uv0;
+ Vertex_TEX uv1;
+ };
+ struct Vertex_BON
+ {
+ uint64_t ind = 0;
+ uint64_t wei = 0;
+
+ void FromFULL(const XMUINT4& boneIndices, const XMFLOAT4& boneWeights)
+ {
+ ind = 0;
+ wei = 0;
+
+ ind |= (uint64_t)boneIndices.x << 0;
+ ind |= (uint64_t)boneIndices.y << 16;
+ ind |= (uint64_t)boneIndices.z << 32;
+ ind |= (uint64_t)boneIndices.w << 48;
+
+ wei |= (uint64_t)(boneWeights.x * 65535.0f) << 0;
+ wei |= (uint64_t)(boneWeights.y * 65535.0f) << 16;
+ wei |= (uint64_t)(boneWeights.z * 65535.0f) << 32;
+ wei |= (uint64_t)(boneWeights.w * 65535.0f) << 48;
+ }
+ inline XMUINT4 GetInd_FULL() const
+ {
+ XMUINT4 ind_FULL(0, 0, 0, 0);
+
+ ind_FULL.x = ((ind >> 0) & 0x0000FFFF);
+ ind_FULL.y = ((ind >> 16) & 0x0000FFFF);
+ ind_FULL.z = ((ind >> 32) & 0x0000FFFF);
+ ind_FULL.w = ((ind >> 48) & 0x0000FFFF);
+
+ return ind_FULL;
+ }
+ inline XMFLOAT4 GetWei_FULL() const
+ {
+ XMFLOAT4 wei_FULL(0, 0, 0, 0);
+
+ wei_FULL.x = (float)((wei >> 0) & 0x0000FFFF) / 65535.0f;
+ wei_FULL.y = (float)((wei >> 16) & 0x0000FFFF) / 65535.0f;
+ wei_FULL.z = (float)((wei >> 32) & 0x0000FFFF) / 65535.0f;
+ wei_FULL.w = (float)((wei >> 48) & 0x0000FFFF) / 65535.0f;
+
+ return wei_FULL;
+ }
+ };
+ struct Vertex_COL
+ {
+ uint32_t color = 0;
+ static const wi::graphics::Format FORMAT = wi::graphics::Format::R8G8B8A8_UNORM;
+ };
+ struct Vertex_TAN
+ {
+ uint32_t tangent = 0;
+
+ void FromFULL(const XMFLOAT4& tan)
+ {
+ XMVECTOR T = XMLoadFloat4(&tan);
+ T = XMVector3Normalize(T);
+ XMFLOAT4 t;
+ XMStoreFloat4(&t, T);
+ t.w = tan.w;
+ tangent = 0;
+ tangent |= (uint)((t.x * 0.5f + 0.5f) * 255.0f) << 0;
+ tangent |= (uint)((t.y * 0.5f + 0.5f) * 255.0f) << 8;
+ tangent |= (uint)((t.z * 0.5f + 0.5f) * 255.0f) << 16;
+ tangent |= (uint)((t.w * 0.5f + 0.5f) * 255.0f) << 24;
+ }
+
+ static const wi::graphics::Format FORMAT = wi::graphics::Format::R8G8B8A8_UNORM;
+ };
+
+ // Non serialized attributes:
+ wi::vector vertex_positions_morphed;
+ wi::vector morph_temp_pos;
+ wi::vector morph_temp_nor;
+
+ };
+
+ struct ImpostorComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DIRTY = 1 << 0,
+ };
+ uint32_t _flags = DIRTY;
+
+ float swapInDistance = 100.0f;
+
+ // Non-serialized attributes:
+ mutable bool render_dirty = false;
+ int textureIndex = -1;
+
+ inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
+ inline bool IsDirty() const { return _flags & DIRTY; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct ObjectComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ RENDERABLE = 1 << 0,
+ CAST_SHADOW = 1 << 1,
+ DYNAMIC = 1 << 2,
+ _DEPRECATED_IMPOSTOR_PLACEMENT = 1 << 3,
+ REQUEST_PLANAR_REFLECTION = 1 << 4,
+ LIGHTMAP_RENDER_REQUEST = 1 << 5,
+ };
+ uint32_t _flags = RENDERABLE | CAST_SHADOW;
+
+ wi::ecs::Entity meshID = wi::ecs::INVALID_ENTITY;
+ uint32_t cascadeMask = 0; // which shadow cascades to skip from lowest detail to highest detail (0: skip none, 1: skip first, etc...)
+ uint32_t rendertypeMask = 0;
+ XMFLOAT4 color = XMFLOAT4(1, 1, 1, 1);
+ XMFLOAT4 emissiveColor = XMFLOAT4(1, 1, 1, 1);
+
+ uint32_t lightmapWidth = 0;
+ uint32_t lightmapHeight = 0;
+ wi::vector lightmapTextureData;
+
+ uint8_t userStencilRef = 0;
+ float lod_distance_multiplier = 1;
+
+ float draw_distance = std::numeric_limits::max(); // object will begin to fade out at this distance to camera
+
+ // Non-serialized attributes:
+
+ wi::graphics::Texture lightmap;
+ wi::graphics::RenderPass renderpass_lightmap_clear;
+ wi::graphics::RenderPass renderpass_lightmap_accumulate;
+ mutable uint32_t lightmapIterationCount = 0;
+
+ XMFLOAT3 center = XMFLOAT3(0, 0, 0);
+ float radius = 0;
+ float fadeDistance = 0;
+
+ uint32_t lod = 0;
+
+ // these will only be valid for a single frame:
+ uint32_t mesh_index = ~0u;
+ XMFLOAT4X4 worldMatrix = wi::math::IDENTITY_MATRIX;
+
+ // occlusion result history bitfield (32 bit->32 frame history)
+ mutable uint32_t occlusionHistory = ~0u;
+ mutable int occlusionQueries[wi::graphics::GraphicsDevice::GetBufferCount() + 1];
+
+ inline bool IsOccluded() const
+ {
+ // Perform a conservative occlusion test:
+ // If it is visible in any frames in the history, it is determined visible in this frame
+ // But if all queries failed in the history, it is occluded.
+ // If it pops up for a frame after occluded, it is visible again for some frames
+ return occlusionHistory == 0;
+ }
+
+ inline void SetRenderable(bool value) { if (value) { _flags |= RENDERABLE; } else { _flags &= ~RENDERABLE; } }
+ inline void SetCastShadow(bool value) { if (value) { _flags |= CAST_SHADOW; } else { _flags &= ~CAST_SHADOW; } }
+ inline void SetDynamic(bool value) { if (value) { _flags |= DYNAMIC; } else { _flags &= ~DYNAMIC; } }
+ inline void SetRequestPlanarReflection(bool value) { if (value) { _flags |= REQUEST_PLANAR_REFLECTION; } else { _flags &= ~REQUEST_PLANAR_REFLECTION; } }
+ inline void SetLightmapRenderRequest(bool value) { if (value) { _flags |= LIGHTMAP_RENDER_REQUEST; } else { _flags &= ~LIGHTMAP_RENDER_REQUEST; } }
+
+ inline bool IsRenderable() const { return _flags & RENDERABLE; }
+ inline bool IsCastingShadow() const { return _flags & CAST_SHADOW; }
+ inline bool IsDynamic() const { return _flags & DYNAMIC; }
+ inline bool IsRequestPlanarReflection() const { return _flags & REQUEST_PLANAR_REFLECTION; }
+ inline bool IsLightmapRenderRequested() const { return _flags & LIGHTMAP_RENDER_REQUEST; }
+
+ inline float GetTransparency() const { return 1 - color.w; }
+ inline uint32_t GetRenderTypes() const { return rendertypeMask; }
+
+ // User stencil value can be in range [0, 15]
+ // Values greater than 0 can be used to override userStencilRef of MaterialComponent
+ inline void SetUserStencilRef(uint8_t value)
+ {
+ assert(value < 16);
+ userStencilRef = value & 0x0F;
+ }
+
+ void ClearLightmap();
+ void SaveLightmap();
+ void CompressLightmap();
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct RigidBodyPhysicsComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DISABLE_DEACTIVATION = 1 << 0,
+ KINEMATIC = 1 << 1,
+ };
+ uint32_t _flags = EMPTY;
+
+ enum CollisionShape
+ {
+ BOX,
+ SPHERE,
+ CAPSULE,
+ CONVEX_HULL,
+ TRIANGLE_MESH,
+ ENUM_FORCE_UINT32 = 0xFFFFFFFF
+ };
+ CollisionShape shape;
+ float mass = 1.0f;
+ float friction = 0.5f;
+ float restitution = 0.0f;
+ float damping_linear = 0.0f;
+ float damping_angular = 0.0f;
+
+ struct BoxParams
+ {
+ XMFLOAT3 halfextents = XMFLOAT3(1, 1, 1);
+ } box;
+ struct SphereParams
+ {
+ float radius = 1;
+ } sphere;
+ struct CapsuleParams
+ {
+ float radius = 1;
+ float height = 1;
+ } capsule;
+
+ // This will force LOD level for rigid body if it is a TRIANGLE_MESH shape:
+ // The geometry for LOD level will be taken from MeshComponent.
+ // The physics object will need to be recreated for it to take effect.
+ uint32_t mesh_lod = 0;
+
+ // Non-serialized attributes:
+ 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; } }
+
+ inline bool IsDisableDeactivation() const { return _flags & DISABLE_DEACTIVATION; }
+ inline bool IsKinematic() const { return _flags & KINEMATIC; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct SoftBodyPhysicsComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ SAFE_TO_REGISTER = 1 << 0,
+ DISABLE_DEACTIVATION = 1 << 1,
+ FORCE_RESET = 1 << 2,
+ };
+ uint32_t _flags = DISABLE_DEACTIVATION;
+
+ float mass = 1.0f;
+ float friction = 0.5f;
+ float restitution = 0.0f;
+ wi::vector physicsToGraphicsVertexMapping; // maps graphics vertex index to physics vertex index of the same position
+ wi::vector graphicsToPhysicsVertexMapping; // maps a physics vertex index to first graphics vertex index of the same position
+ wi::vector weights; // weight per physics vertex controlling the mass. (0: disable weight (no physics, only animation), 1: default weight)
+
+ // Non-serialized attributes:
+ 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;
+ wi::vector vertex_tangents_simulation;
+ wi::primitive::AABB aabb;
+
+ inline void SetDisableDeactivation(bool value) { if (value) { _flags |= DISABLE_DEACTIVATION; } else { _flags &= ~DISABLE_DEACTIVATION; } }
+
+ inline bool IsDisableDeactivation() const { return _flags & DISABLE_DEACTIVATION; }
+
+ // Create physics represenation of graphics mesh
+ void CreateFromMesh(const MeshComponent& mesh);
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct ArmatureComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = EMPTY;
+
+ wi::vector boneCollection;
+ wi::vector inverseBindMatrices;
+
+ // Non-serialized attributes:
+ wi::primitive::AABB aabb;
+
+ wi::vector boneData;
+ wi::graphics::GPUBuffer boneBuffer;
+ int descriptor_srv = -1;
+
+ void CreateRenderData();
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct LightComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ CAST_SHADOW = 1 << 0,
+ VOLUMETRICS = 1 << 1,
+ VISUALIZER = 1 << 2,
+ LIGHTMAPONLY_STATIC = 1 << 3,
+ VOLUMETRICCLOUDS = 1 << 4,
+ };
+ uint32_t _flags = EMPTY;
+
+ enum LightType
+ {
+ DIRECTIONAL = ENTITY_TYPE_DIRECTIONALLIGHT,
+ POINT = ENTITY_TYPE_POINTLIGHT,
+ SPOT = ENTITY_TYPE_SPOTLIGHT,
+ //SPHERE = ENTITY_TYPE_SPHERELIGHT,
+ //DISC = ENTITY_TYPE_DISCLIGHT,
+ //RECTANGLE = ENTITY_TYPE_RECTANGLELIGHT,
+ //TUBE = ENTITY_TYPE_TUBELIGHT,
+ LIGHTTYPE_COUNT,
+ ENUM_FORCE_UINT32 = 0xFFFFFFFF,
+ };
+ LightType type = POINT;
+
+ XMFLOAT3 color = XMFLOAT3(1, 1, 1);
+ float intensity = 1.0f; // Brightness of light in. The units that this is defined in depend on the type of light. Point and spot lights use luminous intensity in candela (lm/sr) while directional lights use illuminance in lux (lm/m2). https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_lights_punctual
+ float range = 10.0f;
+ float outerConeAngle = XM_PIDIV4;
+ float innerConeAngle = 0; // default value is 0, means only outer cone angle is used
+
+ wi::vector lensFlareNames;
+
+ int forced_shadow_resolution = -1; // -1: disabled, greater: fixed shadow map resolution
+
+ // Non-serialized attributes:
+ XMFLOAT3 position;
+ XMFLOAT3 direction;
+ XMFLOAT4 rotation;
+ XMFLOAT3 scale;
+ XMFLOAT3 front;
+ XMFLOAT3 right;
+ mutable int occlusionquery = -1;
+ wi::rectpacker::Rect shadow_rect = {};
+
+ wi::vector lensFlareRimTextures;
+
+ inline void SetCastShadow(bool value) { if (value) { _flags |= CAST_SHADOW; } else { _flags &= ~CAST_SHADOW; } }
+ inline void SetVolumetricsEnabled(bool value) { if (value) { _flags |= VOLUMETRICS; } else { _flags &= ~VOLUMETRICS; } }
+ inline void SetVisualizerEnabled(bool value) { if (value) { _flags |= VISUALIZER; } else { _flags &= ~VISUALIZER; } }
+ inline void SetStatic(bool value) { if (value) { _flags |= LIGHTMAPONLY_STATIC; } else { _flags &= ~LIGHTMAPONLY_STATIC; } }
+ inline void SetVolumetricCloudsEnabled(bool value) { if (value) { _flags |= VOLUMETRICCLOUDS; } else { _flags &= ~VOLUMETRICCLOUDS; } }
+
+ inline bool IsCastingShadow() const { return _flags & CAST_SHADOW; }
+ inline bool IsVolumetricsEnabled() const { return _flags & VOLUMETRICS; }
+ inline bool IsVisualizerEnabled() const { return _flags & VISUALIZER; }
+ inline bool IsStatic() const { return _flags & LIGHTMAPONLY_STATIC; }
+ inline bool IsVolumetricCloudsEnabled() const { return _flags & VOLUMETRICCLOUDS; }
+
+ inline float GetRange() const
+ {
+ float retval = range;
+ retval = std::max(0.001f, retval);
+ retval = std::min(retval, 65504.0f); // clamp to 16-bit float max value
+ return retval;
+ }
+
+ inline void SetType(LightType val) { type = val; }
+ inline LightType GetType() const { return type; }
+
+ // Set energy amount with non physical light units (from before version 0.70.0):
+ inline void BackCompatSetEnergy(float energy)
+ {
+ switch (type)
+ {
+ case wi::scene::LightComponent::POINT:
+ intensity = energy * 20;
+ break;
+ case wi::scene::LightComponent::SPOT:
+ intensity = energy * 200;
+ break;
+ default:
+ break;
+ }
+ }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct CameraComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DIRTY = 1 << 0,
+ CUSTOM_PROJECTION = 1 << 1,
+ };
+ uint32_t _flags = EMPTY;
+
+ float width = 0.0f;
+ float height = 0.0f;
+ float zNearP = 0.1f;
+ float zFarP = 5000.0f;
+ float fov = XM_PI / 3.0f;
+ float focal_length = 1;
+ float aperture_size = 0;
+ XMFLOAT2 aperture_shape = XMFLOAT2(1, 1);
+
+ // Non-serialized attributes:
+ XMFLOAT3 Eye = XMFLOAT3(0, 0, 0);
+ XMFLOAT3 At = XMFLOAT3(0, 0, 1);
+ XMFLOAT3 Up = XMFLOAT3(0, 1, 0);
+ XMFLOAT3X3 rotationMatrix;
+ XMFLOAT4X4 View, Projection, VP;
+ wi::primitive::Frustum frustum;
+ XMFLOAT4X4 InvView, InvProjection, InvVP;
+ XMFLOAT2 jitter;
+ XMFLOAT4 clipPlane = XMFLOAT4(0, 0, 0, 0); // default: no clip plane
+ wi::Canvas canvas;
+ uint32_t sample_count = 1;
+ int texture_primitiveID_index = -1;
+ int texture_depth_index = -1;
+ int texture_lineardepth_index = -1;
+ int texture_velocity_index = -1;
+ int texture_normal_index = -1;
+ int texture_roughness_index = -1;
+ int texture_reflection_index = -1;
+ int texture_refraction_index = -1;
+ int texture_waterriples_index = -1;
+ int texture_ao_index = -1;
+ int texture_ssr_index = -1;
+ int texture_rtshadow_index = -1;
+ int texture_surfelgi_index = -1;
+ int buffer_entitytiles_opaque_index = -1;
+ int buffer_entitytiles_transparent_index = -1;
+
+ void CreatePerspective(float newWidth, float newHeight, float newNear, float newFar, float newFOV = XM_PI / 3.0f);
+ void UpdateCamera();
+ void TransformCamera(const TransformComponent& transform);
+ void Reflect(const XMFLOAT4& plane = XMFLOAT4(0, 1, 0, 0));
+
+ inline XMVECTOR GetEye() const { return XMLoadFloat3(&Eye); }
+ inline XMVECTOR GetAt() const { return XMLoadFloat3(&At); }
+ inline XMVECTOR GetUp() const { return XMLoadFloat3(&Up); }
+ inline XMVECTOR GetRight() const { return XMVector3Cross(GetAt(), GetUp()); }
+ inline XMMATRIX GetView() const { return XMLoadFloat4x4(&View); }
+ inline XMMATRIX GetInvView() const { return XMLoadFloat4x4(&InvView); }
+ inline XMMATRIX GetProjection() const { return XMLoadFloat4x4(&Projection); }
+ inline XMMATRIX GetInvProjection() const { return XMLoadFloat4x4(&InvProjection); }
+ inline XMMATRIX GetViewProjection() const { return XMLoadFloat4x4(&VP); }
+ inline XMMATRIX GetInvViewProjection() const { return XMLoadFloat4x4(&InvVP); }
+
+ inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
+ inline void SetCustomProjectionEnabled(bool value = true) { if (value) { _flags |= CUSTOM_PROJECTION; } else { _flags &= ~CUSTOM_PROJECTION; } }
+ inline bool IsDirty() const { return _flags & DIRTY; }
+ inline bool IsCustomProjectionEnabled() const { return _flags & CUSTOM_PROJECTION; }
+
+ void Lerp(const CameraComponent& a, const CameraComponent& b, float t);
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct EnvironmentProbeComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DIRTY = 1 << 0,
+ REALTIME = 1 << 1,
+ MSAA = 1 << 2,
+ };
+ uint32_t _flags = DIRTY;
+
+ // Non-serialized attributes:
+ int textureIndex = -1;
+ XMFLOAT3 position;
+ float range;
+ XMFLOAT4X4 inverseMatrix;
+ mutable bool render_dirty = false;
+
+ inline void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
+ inline void SetRealTime(bool value) { if (value) { _flags |= REALTIME; } else { _flags &= ~REALTIME; } }
+ inline void SetMSAA(bool value) { if (value) { _flags |= MSAA; } else { _flags &= ~MSAA; } }
+
+ inline bool IsDirty() const { return _flags & DIRTY; }
+ inline bool IsRealTime() const { return _flags & REALTIME; }
+ inline bool IsMSAA() const { return _flags & MSAA; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct ForceFieldComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = EMPTY;
+
+ enum class Type
+ {
+ Point,
+ Plane,
+ } type = Type::Point;
+
+ float gravity = 0; // negative = deflector, positive = attractor
+ float range = 0; // affection range
+
+ // Non-serialized attributes:
+ XMFLOAT3 position;
+ XMFLOAT3 direction;
+
+ inline float GetRange() const { return range; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct DecalComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = EMPTY;
+
+ // Non-serialized attributes:
+ XMFLOAT4 color;
+ float emissive;
+ XMFLOAT3 front;
+ XMFLOAT3 position;
+ float range;
+ XMFLOAT4X4 world;
+
+ wi::Resource texture;
+ wi::Resource normal;
+
+ inline float GetOpacity() const { return color.w; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct AnimationDataComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = EMPTY;
+
+ wi::vector keyframe_times;
+ wi::vector keyframe_data;
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct AnimationComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ PLAYING = 1 << 0,
+ LOOPED = 1 << 1,
+ };
+ uint32_t _flags = LOOPED;
+ float start = 0;
+ float end = 0;
+ float timer = 0;
+ float amount = 1; // blend amount
+ float speed = 1;
+
+ struct AnimationChannel
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = LOOPED;
+
+ wi::ecs::Entity target = wi::ecs::INVALID_ENTITY;
+ int samplerIndex = -1;
+
+ enum class Path
+ {
+ TRANSLATION,
+ ROTATION,
+ SCALE,
+ WEIGHTS,
+
+ LIGHT_COLOR,
+ LIGHT_INTENSITY,
+ LIGHT_RANGE,
+ LIGHT_INNERCONE,
+ LIGHT_OUTERCONE,
+ // additional light paths can go here...
+ _LIGHT_RANGE_END = LIGHT_COLOR + 1000,
+
+ SOUND_PLAY,
+ SOUND_STOP,
+ SOUND_VOLUME,
+ // additional sound paths can go here...
+ _SOUND_RANGE_END = SOUND_PLAY + 1000,
+
+ EMITTER_EMITCOUNT,
+ // additional emitter paths can go here...
+ _EMITTER_RANGE_END = EMITTER_EMITCOUNT + 1000,
+
+ CAMERA_FOV,
+ CAMERA_FOCAL_LENGTH,
+ CAMERA_APERTURE_SIZE,
+ CAMERA_APERTURE_SHAPE,
+ // additional camera paths can go here...
+ _CAMERA_RANGE_END = CAMERA_FOV + 1000,
+
+ SCRIPT_PLAY,
+ SCRIPT_STOP,
+ // additional script paths can go here...
+ _SCRIPT_RANGE_END = SCRIPT_PLAY + 1000,
+
+ MATERIAL_COLOR,
+ MATERIAL_EMISSIVE,
+ MATERIAL_ROUGHNESS,
+ MATERIAL_METALNESS,
+ MATERIAL_REFLECTANCE,
+ MATERIAL_TEXMULADD,
+ // additional material paths can go here...
+ _MATERIAL_RANGE_END = MATERIAL_COLOR + 1000,
+
+ UNKNOWN,
+ } path = Path::UNKNOWN;
+
+ enum class PathDataType
+ {
+ Event,
+ Float,
+ Float2,
+ Float3,
+ Float4,
+ Weights,
+
+ Count,
+ };
+ PathDataType GetPathDataType() const;
+
+ // Non-serialized attributes:
+ mutable int next_event = 0;
+ };
+ struct AnimationSampler
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = LOOPED;
+
+ wi::ecs::Entity data = wi::ecs::INVALID_ENTITY;
+
+ enum Mode
+ {
+ LINEAR,
+ STEP,
+ CUBICSPLINE,
+ MODE_FORCE_UINT32 = 0xFFFFFFFF
+ } mode = LINEAR;
+
+ // The data is now not part of the sampler, so it can be shared. This is kept only for backwards compatibility with previous versions.
+ AnimationDataComponent backwards_compatibility_data;
+ };
+ wi::vector channels;
+ wi::vector samplers;
+
+ // Non-serialzied attributes:
+ wi::vector morph_weights_temp;
+ float last_update_time = 0;
+
+ inline bool IsPlaying() const { return _flags & PLAYING; }
+ inline bool IsLooped() const { return _flags & LOOPED; }
+ inline float GetLength() const { return end - start; }
+ inline bool IsEnded() const { return timer >= end; }
+
+ inline void Play() { _flags |= PLAYING; }
+ inline void Pause() { _flags &= ~PLAYING; }
+ inline void Stop() { Pause(); timer = 0.0f; last_update_time = timer; }
+ inline void SetLooped(bool value = true) { if (value) { _flags |= LOOPED; } else { _flags &= ~LOOPED; } }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct WeatherComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ OCEAN_ENABLED = 1 << 0,
+ _DEPRECATED_SIMPLE_SKY = 1 << 1,
+ REALISTIC_SKY = 1 << 2,
+ VOLUMETRIC_CLOUDS = 1 << 3,
+ HEIGHT_FOG = 1 << 4,
+ VOLUMETRIC_CLOUDS_SHADOWS = 1 << 5,
+ };
+ uint32_t _flags = EMPTY;
+
+ inline bool IsOceanEnabled() const { return _flags & OCEAN_ENABLED; }
+ inline bool IsRealisticSky() const { return _flags & REALISTIC_SKY; }
+ inline bool IsVolumetricClouds() const { return _flags & VOLUMETRIC_CLOUDS; }
+ inline bool IsHeightFog() const { return _flags & HEIGHT_FOG; }
+ inline bool IsVolumetricCloudsShadows() const { return _flags & VOLUMETRIC_CLOUDS_SHADOWS; }
+
+ inline void SetOceanEnabled(bool value = true) { if (value) { _flags |= OCEAN_ENABLED; } else { _flags &= ~OCEAN_ENABLED; } }
+ inline void SetRealisticSky(bool value = true) { if (value) { _flags |= REALISTIC_SKY; } else { _flags &= ~REALISTIC_SKY; } }
+ inline void SetVolumetricClouds(bool value = true) { if (value) { _flags |= VOLUMETRIC_CLOUDS; } else { _flags &= ~VOLUMETRIC_CLOUDS; } }
+ inline void SetHeightFog(bool value = true) { if (value) { _flags |= HEIGHT_FOG; } else { _flags &= ~HEIGHT_FOG; } }
+ inline void SetVolumetricCloudsShadows(bool value = true) { if (value) { _flags |= VOLUMETRIC_CLOUDS_SHADOWS; } else { _flags &= ~VOLUMETRIC_CLOUDS_SHADOWS; } }
+
+ XMFLOAT3 sunColor = XMFLOAT3(0, 0, 0);
+ XMFLOAT3 sunDirection = XMFLOAT3(0, 1, 0);
+ float skyExposure = 1;
+ XMFLOAT3 horizon = XMFLOAT3(0.0f, 0.0f, 0.0f);
+ XMFLOAT3 zenith = XMFLOAT3(0.0f, 0.0f, 0.0f);
+ XMFLOAT3 ambient = XMFLOAT3(0.2f, 0.2f, 0.2f);
+ float fogStart = 100;
+ float fogEnd = 1000;
+ float fogHeightStart = 1;
+ float fogHeightEnd = 3;
+ XMFLOAT3 windDirection = XMFLOAT3(0, 0, 0);
+ float windRandomness = 5;
+ float windWaveSize = 1;
+ float windSpeed = 1;
+ float stars = 0.5f;
+
+ wi::Ocean::OceanParameters oceanParameters;
+ AtmosphereParameters atmosphereParameters;
+ VolumetricCloudParameters volumetricCloudParameters;
+
+ std::string skyMapName;
+ std::string colorGradingMapName;
+ std::string volumetricCloudsWeatherMapName;
+
+ // Non-serialized attributes:
+ uint32_t most_important_light_index = ~0u;
+ wi::Resource skyMap;
+ wi::Resource colorGradingMap;
+ wi::Resource volumetricCloudsWeatherMap;
+ XMFLOAT4 stars_rotation_quaternion = XMFLOAT4(0, 0, 0, 1);
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct SoundComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ PLAYING = 1 << 0,
+ LOOPED = 1 << 1,
+ DISABLE_3D = 1 << 2,
+ };
+ uint32_t _flags = LOOPED;
+
+ std::string filename;
+ wi::Resource soundResource;
+ wi::audio::SoundInstance soundinstance;
+ float volume = 1;
+
+ inline bool IsPlaying() const { return _flags & PLAYING; }
+ inline bool IsLooped() const { return _flags & LOOPED; }
+ inline bool IsDisable3D() const { return _flags & DISABLE_3D; }
+
+ inline void Play() { _flags |= PLAYING; }
+ inline void Stop() { _flags &= ~PLAYING; }
+ inline void SetLooped(bool value = true) { if (value) { _flags |= LOOPED; } else { _flags &= ~LOOPED; } }
+ inline void SetDisable3D(bool value = true) { if (value) { _flags |= DISABLE_3D; } else { _flags &= ~DISABLE_3D; } }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct InverseKinematicsComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DISABLED = 1 << 0,
+ };
+ uint32_t _flags = EMPTY;
+
+ wi::ecs::Entity target = wi::ecs::INVALID_ENTITY; // which entity to follow (must have a transform component)
+ uint32_t chain_length = 0; // recursive depth
+ uint32_t iteration_count = 1; // computation step count. Increase this too for greater chain length
+
+ inline void SetDisabled(bool value = true) { if (value) { _flags |= DISABLED; } else { _flags &= ~DISABLED; } }
+ inline bool IsDisabled() const { return _flags & DISABLED; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct SpringComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ RESET = 1 << 0,
+ DISABLED = 1 << 1,
+ STRETCH_ENABLED = 1 << 2,
+ GRAVITY_ENABLED = 1 << 3,
+ };
+ uint32_t _flags = RESET | GRAVITY_ENABLED;
+
+ float stiffnessForce = 0.5f;
+ float dragForce = 0.5f;
+ float windForce = 0.5f;
+ float hitRadius = 0;
+ float gravityPower = 0.5f;
+ XMFLOAT3 gravityDir = {};
+
+ // Non-serialized attributes:
+ XMFLOAT3 prevTail = {};
+ XMFLOAT3 currentTail = {};
+ XMFLOAT3 boneAxis = {};
+ float boneLength = 0;
+
+ inline void Reset(bool value = true) { if (value) { _flags |= RESET; } else { _flags &= ~RESET; } }
+ inline void SetDisabled(bool value = true) { if (value) { _flags |= DISABLED; } else { _flags &= ~DISABLED; } }
+ inline void SetStretchEnabled(bool value) { if (value) { _flags |= STRETCH_ENABLED; } else { _flags &= ~STRETCH_ENABLED; } }
+ inline void SetGravityEnabled(bool value) { if (value) { _flags |= GRAVITY_ENABLED; } else { _flags &= ~GRAVITY_ENABLED; } }
+
+ inline bool IsResetting() const { return _flags & RESET; }
+ inline bool IsDisabled() const { return _flags & DISABLED; }
+ inline bool IsStretchEnabled() const { return _flags & STRETCH_ENABLED; }
+ inline bool IsGravityEnabled() const { return _flags & GRAVITY_ENABLED; }
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct ColliderComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = EMPTY;
+
+ enum class Shape
+ {
+ Sphere,
+ Capsule,
+ Plane,
+ };
+ Shape shape;
+
+ float radius = 0;
+ XMFLOAT3 offset = {};
+ XMFLOAT3 tail = {};
+
+ // Non-serialized attributes:
+ wi::primitive::Sphere sphere;
+ wi::primitive::Capsule capsule;
+ XMFLOAT3 planeOrigin = {};
+ XMFLOAT3 planeNormal = {};
+ XMFLOAT4X4 planeProjection = wi::math::IDENTITY_MATRIX;
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct ScriptComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ PLAYING = 1 << 0,
+ PLAY_ONCE = 1 << 1,
+ };
+ uint32_t _flags = EMPTY;
+
+ std::string filename;
+
+ // Non-serialized attributes:
+ std::string script;
+ wi::Resource resource;
+
+ inline void Play() { _flags |= PLAYING; }
+ inline void SetPlayOnce(bool once = true) { if (once) { _flags |= PLAY_ONCE; } else { _flags &= ~PLAY_ONCE; } }
+ inline void Stop() { _flags &= ~PLAYING; }
+
+ inline bool IsPlaying() const { return _flags & PLAYING; }
+ inline bool IsPlayingOnlyOnce() const { return _flags & PLAY_ONCE; }
+
+ void CreateFromFile(const std::string& filename);
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct ExpressionComponent
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ };
+ uint32_t _flags = EMPTY;
+
+ // Preset expressions can have common behaviours assigned:
+ // https://github.com/vrm-c/vrm-specification/blob/bd205a6c3839993f2729e4e7c3a74af89877cfce/specification/VRMC_vrm-1.0-beta/expressions.md#preset-expressions
+ enum class Preset
+ {
+ // Emotions:
+ Happy, // Changed from joy
+ Angry, // anger
+ Sad, // Changed from sorrow
+ Relaxed, // Comfortable. Changed from fun
+ Surprised, // surprised. Added new in VRM 1.0
+
+ // Lip sync procedural:
+ // Procedural: A value that can be automatically generated by the system.
+ // - Analyze microphone input, generate from text, etc.
+ Aa, // aa
+ Ih, // i
+ Ou, // u
+ Ee, // eh
+ Oh, // oh
+
+ // Blink procedural:
+ // Procedural: A value that can be automatically generated by the system.
+ // - Randomly blink, etc.
+ Blink, // close both eyelids
+ BlinkLeft, // Close the left eyelid
+ BlinkRight, // Close right eyelid
+
+ // Gaze procedural:
+ // Procedural: A value that can be automatically generated by the system.
+ // - The VRM LookAt will generate a value for the gaze point from time to time (see LookAt Expression Type).
+ LookUp, // For models where the line of sight moves with Expression instead of bone. See eye control.
+ LookDown, // For models where the line of sight moves with Expression instead of bone. See eye control.
+ LookLeft, // For models whose line of sight moves with Expression instead of bone. See eye control.
+ LookRight, // For models where the line of sight moves with Expression instead of bone. See eye control.
+
+ // Other:
+ Neutral, // left for backwards compatibility.
+
+ Count,
+ };
+ int presets[size_t(Preset::Count)] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
+
+ enum class Override
+ {
+ None,
+ Block,
+ Blend,
+ };
+
+ float blink_frequency = 0.3f; // number of blinks per second
+ float blink_length = 0.1f; // blink's completion time in seconds
+ int blink_count = 2;
+ float look_frequency = 0; // number of lookAt changes per second
+ float look_length = 0.6f; // lookAt's completion time in seconds
+
+ struct Expression
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ DIRTY = 1 << 0,
+ BINARY = 1 << 1,
+ };
+ uint32_t _flags = EMPTY;
+
+ std::string name;
+ float weight = 0;
+
+ Preset preset = Preset::Count;
+ Override override_mouth = Override::None;
+ Override override_blink = Override::None;
+ Override override_look = Override::None;
+
+ struct MorphTargetBinding
+ {
+ wi::ecs::Entity meshID = wi::ecs::INVALID_ENTITY;
+ int index = 0;
+ float weight = 0;
+ };
+ wi::vector morph_target_bindings;
+
+ constexpr bool IsDirty() const { return _flags & DIRTY; }
+ constexpr bool IsBinary() const { return _flags & BINARY; }
+
+ constexpr void SetDirty(bool value = true) { if (value) { _flags |= DIRTY; } else { _flags &= ~DIRTY; } }
+ constexpr void SetBinary(bool value = true) { if (value) { _flags |= BINARY; } else { _flags &= ~BINARY; } }
+ };
+ wi::vector expressions;
+
+ // Non-serialized attributes:
+ float blink_timer = 0;
+ float look_timer = 0;
+ float look_weights[4] = {};
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+}
diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp
index 1b1515a4d..08a09be7a 100644
--- a/WickedEngine/wiScene_Serializers.cpp
+++ b/WickedEngine/wiScene_Serializers.cpp
@@ -1721,6 +1721,13 @@ namespace wi::scene
archive >> frame_index;
archive >> grid_dimensions;
+ if (archive.GetVersion() >= 87)
+ {
+ archive >> grid_min;
+ archive >> grid_max;
+ archive >> smooth_backface;
+ }
+
wi::vector data;
// color texture:
@@ -1778,6 +1785,13 @@ namespace wi::scene
archive << frame_index;
archive << grid_dimensions;
+ if (archive.GetVersion() >= 87)
+ {
+ archive << grid_min;
+ archive << grid_max;
+ archive << smooth_backface;
+ }
+
wi::vector data;
if (color_texture[0].IsValid())
{
diff --git a/WickedEngine/wiTerrain.cpp b/WickedEngine/wiTerrain.cpp
new file mode 100644
index 000000000..4f1b16ecb
--- /dev/null
+++ b/WickedEngine/wiTerrain.cpp
@@ -0,0 +1,1145 @@
+#include "wiTerrain.h"
+#include "wiProfiler.h"
+#include "wiTimer.h"
+#include "wiRenderer.h"
+#include "wiHelper.h"
+#include "wiScene.h"
+
+using namespace wi::ecs;
+using namespace wi::scene;
+using namespace wi::graphics;
+
+namespace wi::terrain
+{
+ struct ChunkIndices
+ {
+ wi::vector indices;
+ struct LOD
+ {
+ uint32_t indexOffset = 0;
+ uint32_t indexCount = 0;
+ };
+ wi::vector lods;
+
+ ChunkIndices()
+ {
+ const int max_lod = (int)std::log2(chunk_width - 3) + 1;
+ lods.resize(max_lod);
+ for (int lod = 0; lod < max_lod; ++lod)
+ {
+ lods[lod].indexOffset = (uint32_t)indices.size();
+
+ if (lod == 0)
+ {
+ for (int x = 0; x < chunk_width - 1; x++)
+ {
+ for (int z = 0; z < chunk_width - 1; z++)
+ {
+ int lowerLeft = x + z * chunk_width;
+ int lowerRight = (x + 1) + z * chunk_width;
+ int topLeft = x + (z + 1) * chunk_width;
+ int topRight = (x + 1) + (z + 1) * chunk_width;
+
+ indices.push_back(topLeft);
+ indices.push_back(lowerLeft);
+ indices.push_back(lowerRight);
+
+ indices.push_back(topLeft);
+ indices.push_back(lowerRight);
+ indices.push_back(topRight);
+ }
+ }
+ }
+ else
+ {
+ const int step = 1 << lod;
+ // inner grid:
+ for (int x = 1; x < chunk_width - 2; x += step)
+ {
+ for (int z = 1; z < chunk_width - 2; z += step)
+ {
+ int lowerLeft = x + z * chunk_width;
+ int lowerRight = (x + step) + z * chunk_width;
+ int topLeft = x + (z + step) * chunk_width;
+ int topRight = (x + step) + (z + step) * chunk_width;
+
+ indices.push_back(topLeft);
+ indices.push_back(lowerLeft);
+ indices.push_back(lowerRight);
+
+ indices.push_back(topLeft);
+ indices.push_back(lowerRight);
+ indices.push_back(topRight);
+ }
+ }
+ // bottom border:
+ for (int x = 0; x < chunk_width - 1; ++x)
+ {
+ const int z = 0;
+ int current = x + z * chunk_width;
+ int neighbor = x + 1 + z * chunk_width;
+ int connection = 1 + ((x + (step + 1) / 2 - 1) / step) * step + (z + 1) * chunk_width;
+
+ indices.push_back(current);
+ indices.push_back(neighbor);
+ indices.push_back(connection);
+
+ if (((x - 1) % (step)) == step / 2) // halfway fill triangle
+ {
+ int connection1 = 1 + (((x - 1) + (step + 1) / 2 - 1) / step) * step + (z + 1) * chunk_width;
+
+ indices.push_back(current);
+ indices.push_back(connection);
+ indices.push_back(connection1);
+ }
+ }
+ // top border:
+ for (int x = 0; x < chunk_width - 1; ++x)
+ {
+ const int z = chunk_width - 1;
+ int current = x + z * chunk_width;
+ int neighbor = x + 1 + z * chunk_width;
+ int connection = 1 + ((x + (step + 1) / 2 - 1) / step) * step + (z - 1) * chunk_width;
+
+ indices.push_back(current);
+ indices.push_back(connection);
+ indices.push_back(neighbor);
+
+ if (((x - 1) % (step)) == step / 2) // halfway fill triangle
+ {
+ int connection1 = 1 + (((x - 1) + (step + 1) / 2 - 1) / step) * step + (z - 1) * chunk_width;
+
+ indices.push_back(current);
+ indices.push_back(connection1);
+ indices.push_back(connection);
+ }
+ }
+ // left border:
+ for (int z = 0; z < chunk_width - 1; ++z)
+ {
+ const int x = 0;
+ int current = x + z * chunk_width;
+ int neighbor = x + (z + 1) * chunk_width;
+ int connection = x + 1 + (((z + (step + 1) / 2 - 1) / step) * step + 1) * chunk_width;
+
+ indices.push_back(current);
+ indices.push_back(connection);
+ indices.push_back(neighbor);
+
+ if (((z - 1) % (step)) == step / 2) // halfway fill triangle
+ {
+ int connection1 = x + 1 + ((((z - 1) + (step + 1) / 2 - 1) / step) * step + 1) * chunk_width;
+
+ indices.push_back(current);
+ indices.push_back(connection1);
+ indices.push_back(connection);
+ }
+ }
+ // right border:
+ for (int z = 0; z < chunk_width - 1; ++z)
+ {
+ const int x = chunk_width - 1;
+ int current = x + z * chunk_width;
+ int neighbor = x + (z + 1) * chunk_width;
+ int connection = x - 1 + (((z + (step + 1) / 2 - 1) / step) * step + 1) * chunk_width;
+
+ indices.push_back(current);
+ indices.push_back(neighbor);
+ indices.push_back(connection);
+
+ if (((z - 1) % (step)) == step / 2) // halfway fill triangle
+ {
+ int connection1 = x - 1 + ((((z - 1) + (step + 1) / 2 - 1) / step) * step + 1) * chunk_width;
+
+ indices.push_back(current);
+ indices.push_back(connection);
+ indices.push_back(connection1);
+ }
+ }
+ }
+
+ lods[lod].indexCount = (uint32_t)indices.size() - lods[lod].indexOffset;
+ }
+ }
+ };
+ static ChunkIndices chunk_indices;
+
+ struct Generator
+ {
+ wi::scene::Scene scene; // The background generation thread can safely add things to this, it will be merged into the main scene when it is safe to do so
+ wi::jobsystem::context workload;
+ std::atomic_bool cancelled;
+ };
+
+ Terrain::Terrain()
+ {
+ weather.ambient = XMFLOAT3(0.2f, 0.2f, 0.2f);
+ weather.SetRealisticSky(true);
+ weather.SetVolumetricClouds(true);
+ weather.volumetricCloudParameters.CoverageAmount = 0.65f;
+ weather.volumetricCloudParameters.CoverageMinimum = 0.15f;
+ weather.oceanParameters.waterHeight = -40;
+ weather.oceanParameters.wave_amplitude = 120;
+ weather.fogStart = 300;
+ weather.fogEnd = 100000;
+ weather.SetHeightFog(true);
+ weather.fogHeightStart = 0;
+ weather.fogHeightEnd = 100;
+ weather.windDirection = XMFLOAT3(0.05f, 0.05f, 0.05f);
+ weather.windSpeed = 4;
+ weather.stars = 0.6f;
+
+ generator = std::make_shared();
+ }
+ Terrain::~Terrain()
+ {
+ Generation_Cancel();
+ }
+
+ void Terrain::Generation_Restart()
+ {
+ SetGenerationStarted(true);
+ Generation_Cancel();
+ generator->scene.Clear();
+
+ chunks.clear();
+
+ wi::vector entities_to_remove;
+ for (size_t i = 0; i < scene->hierarchy.GetCount(); ++i)
+ {
+ const HierarchyComponent& hier = scene->hierarchy[i];
+ if (hier.parentID == terrainEntity)
+ {
+ entities_to_remove.push_back(scene->hierarchy.GetEntity(i));
+ }
+ }
+ for (Entity x : entities_to_remove)
+ {
+ scene->Entity_Remove(x);
+ }
+
+ perlin_noise.init(seed);
+ for (auto& modifier : modifiers)
+ {
+ modifier->Seed(seed);
+ }
+
+ // Add some nice weather and lighting:
+ if (!scene->weathers.Contains(terrainEntity))
+ {
+ scene->weathers.Create(terrainEntity);
+ }
+ *scene->weathers.GetComponent(terrainEntity) = weather;
+ if (!weather.IsOceanEnabled())
+ {
+ scene->ocean = {};
+ }
+
+ {
+ Entity sunEntity = scene->Entity_CreateLight("terrainSun");
+ scene->Component_Attach(sunEntity, terrainEntity);
+ LightComponent& light = *scene->lights.GetComponent(sunEntity);
+ light.SetType(LightComponent::LightType::DIRECTIONAL);
+ light.intensity = 16;
+ light.SetCastShadow(true);
+ TransformComponent& transform = *scene->transforms.GetComponent(sunEntity);
+ transform.RotateRollPitchYaw(XMFLOAT3(XM_PIDIV4, 0, XM_PIDIV4));
+ transform.Translate(XMFLOAT3(0, 4, 0));
+ }
+ }
+
+ void Terrain::Generation_Update(const wi::scene::CameraComponent& camera)
+ {
+ // The generation task is always cancelled every frame so we are sure that generation is not running at this point
+ Generation_Cancel();
+
+ if (!IsGenerationStarted())
+ {
+ Generation_Restart();
+ }
+
+ // Check whether any modifiers need to be removed, and we will really remove them here if so:
+ if (!modifiers_to_remove.empty())
+ {
+ for (auto& modifier : modifiers_to_remove)
+ {
+ for (auto it = modifiers.begin(); it != modifiers.end(); ++it)
+ {
+ if (it->get() == modifier)
+ {
+ modifiers.erase(it);
+ break;
+ }
+ }
+ }
+ Generation_Restart();
+ modifiers_to_remove.clear();
+ }
+
+ if (terrainEntity == INVALID_ENTITY)
+ {
+ chunks.clear();
+ return;
+ }
+
+ // What was generated, will be merged in to the main scene
+ scene->Merge(generator->scene);
+
+ const float chunk_scale_rcp = 1.0f / chunk_scale;
+
+ if (IsCenterToCamEnabled())
+ {
+ center_chunk.x = (int)std::floor((camera.Eye.x + chunk_half_width) * chunk_width_rcp * chunk_scale_rcp);
+ center_chunk.z = (int)std::floor((camera.Eye.z + chunk_half_width) * chunk_width_rcp * chunk_scale_rcp);
+ }
+
+ const int removal_threshold = generation + 2;
+ const float texlodMultiplier = texlod;
+ GraphicsDevice* device = GetDevice();
+ virtual_texture_updates.clear();
+ virtual_texture_barriers_begin.clear();
+ virtual_texture_barriers_end.clear();
+
+ // Check whether there are any materials that would write to virtual textures:
+ uint32_t max_texture_resolution = 0;
+ bool virtual_texture_available[MaterialComponent::TEXTURESLOT_COUNT] = {};
+ virtual_texture_available[MaterialComponent::SURFACEMAP] = true; // this is always needed to bake individual material properties
+ MaterialComponent* virtual_materials[4] = {
+ &material_Base,
+ &material_Slope,
+ &material_LowAltitude,
+ &material_HighAltitude,
+ };
+ for (auto& material : virtual_materials)
+ {
+ for (int i = 0; i < MaterialComponent::TEXTURESLOT_COUNT; ++i)
+ {
+ switch (i)
+ {
+ case MaterialComponent::BASECOLORMAP:
+ case MaterialComponent::NORMALMAP:
+ case MaterialComponent::SURFACEMAP:
+ if (material->textures[i].resource.IsValid())
+ {
+ virtual_texture_available[i] = true;
+ const TextureDesc& desc = material->textures[i].resource.GetTexture().GetDesc();
+ max_texture_resolution = std::max(max_texture_resolution, desc.width);
+ max_texture_resolution = std::max(max_texture_resolution, desc.height);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ for (auto it = chunks.begin(); it != chunks.end();)
+ {
+ const Chunk& chunk = it->first;
+ ChunkData& chunk_data = it->second;
+ const int dist = std::max(std::abs(center_chunk.x - chunk.x), std::abs(center_chunk.z - chunk.z));
+
+ // pointer refresh:
+ MeshComponent* chunk_mesh = scene->meshes.GetComponent(chunk_data.entity);
+ if (chunk_mesh != nullptr)
+ {
+ chunk_data.mesh_vertex_positions = chunk_mesh->vertex_positions.data();
+ }
+ else
+ {
+ chunk_data.mesh_vertex_positions = nullptr;
+ }
+
+ // chunk removal:
+ if (IsRemovalEnabled())
+ {
+ if (dist > removal_threshold)
+ {
+ scene->Entity_Remove(it->second.entity);
+ it = chunks.erase(it);
+ continue; // don't increment iterator
+ }
+ else
+ {
+ // Grass patch removal:
+ if (chunk_data.grass.meshID != INVALID_ENTITY && (dist > 1 || !IsGrassEnabled()))
+ {
+ scene->Entity_Remove(chunk_data.grass_entity);
+ chunk_data.grass_entity = INVALID_ENTITY; // grass can be generated here by generation thread...
+ }
+
+ // Prop removal:
+ if (chunk_data.props_entity != INVALID_ENTITY && (dist > prop_generation || std::abs(chunk_data.prop_density_current - prop_density) > std::numeric_limits::epsilon()))
+ {
+ scene->Entity_Remove(chunk_data.props_entity);
+ chunk_data.props_entity = INVALID_ENTITY; // prop can be generated here by generation thread...
+ }
+ }
+ }
+
+ // Grass density modification:
+ if (chunk_data.grass_entity != INVALID_ENTITY && std::abs(chunk_data.grass_density_current - grass_density) > std::numeric_limits::epsilon())
+ {
+ wi::HairParticleSystem* grass = scene->hairs.GetComponent(chunk_data.grass_entity);
+ if (grass != nullptr)
+ {
+ chunk_data.grass_density_current = grass_density;
+ grass->strandCount = uint32_t(chunk_data.grass.strandCount * chunk_data.grass_density_current);
+ }
+ }
+
+ // Collect virtual texture update requests:
+ if (max_texture_resolution > 0)
+ {
+ uint32_t texture_lod = 0;
+ const float distsq = wi::math::DistanceSquared(camera.Eye, chunk_data.sphere.center);
+ const float radius = chunk_data.sphere.radius;
+ const float radiussq = radius * radius;
+ if (distsq < radiussq)
+ {
+ texture_lod = 0;
+ }
+ else
+ {
+ const float dist = std::sqrt(distsq);
+ const float dist_to_sphere = dist - radius;
+ texture_lod = uint32_t(dist_to_sphere * texlodMultiplier);
+ }
+
+ chunk_data.required_texture_resolution = uint32_t(max_texture_resolution / std::pow(2.0f, (float)std::max(0u, texture_lod)));
+ chunk_data.required_texture_resolution = std::max(8u, chunk_data.required_texture_resolution);
+ MaterialComponent* material = scene->materials.GetComponent(chunk_data.entity);
+
+ if (material != nullptr)
+ {
+ bool need_update = false;
+ for (int i = 0; i < MaterialComponent::TEXTURESLOT_COUNT; ++i)
+ {
+ if (virtual_texture_available[i])
+ {
+ uint32_t current_resolution = 0;
+ if (material->textures[i].resource.IsValid())
+ {
+ current_resolution = material->textures[i].resource.GetTexture().GetDesc().width;
+ }
+
+ if (current_resolution != chunk_data.required_texture_resolution)
+ {
+ need_update = true;
+ TextureDesc desc;
+ desc.width = chunk_data.required_texture_resolution;
+ desc.height = chunk_data.required_texture_resolution;
+ desc.format = Format::R8G8B8A8_UNORM;
+ desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
+ Texture texture;
+ bool success = device->CreateTexture(&desc, nullptr, &texture);
+ assert(success);
+
+ material->textures[i].resource.SetTexture(texture);
+ virtual_texture_barriers_begin.push_back(GPUBarrier::Image(&material->textures[i].resource.GetTexture(), desc.layout, ResourceState::UNORDERED_ACCESS));
+ virtual_texture_barriers_end.push_back(GPUBarrier::Image(&material->textures[i].resource.GetTexture(), ResourceState::UNORDERED_ACCESS, desc.layout));
+ }
+ }
+ }
+
+ if (need_update)
+ {
+ virtual_texture_updates.push_back(chunk);
+ }
+
+ }
+ }
+
+ it++;
+ }
+
+ // Execute batched virtual texture updates:
+ if (!virtual_texture_updates.empty())
+ {
+ CommandList cmd = device->BeginCommandList();
+ device->EventBegin("TerrainVirtualTextureUpdate", cmd);
+ auto range = wi::profiler::BeginRangeGPU("TerrainVirtualTextureUpdate", cmd);
+ device->Barrier(virtual_texture_barriers_begin.data(), (uint32_t)virtual_texture_barriers_begin.size(), cmd);
+
+ device->BindComputeShader(wi::renderer::GetShader(wi::enums::CSTYPE_TERRAIN_VIRTUALTEXTURE_UPDATE), cmd);
+
+ ShaderMaterial materials[4];
+ material_Base.WriteShaderMaterial(&materials[0]);
+ material_Slope.WriteShaderMaterial(&materials[1]);
+ material_LowAltitude.WriteShaderMaterial(&materials[2]);
+ material_HighAltitude.WriteShaderMaterial(&materials[3]);
+ device->BindDynamicConstantBuffer(materials, 10, cmd);
+
+ for (auto& chunk : virtual_texture_updates)
+ {
+ auto it = chunks.find(chunk);
+ if (it == chunks.end())
+ continue;
+ ChunkData& chunk_data = it->second;
+
+ const GPUResource* res[] = {
+ &chunk_data.region_weights_texture,
+ };
+ device->BindResources(res, 0, arraysize(res), cmd);
+
+ MaterialComponent* material = scene->materials.GetComponent(chunk_data.entity);
+ if (material != nullptr)
+ {
+ // Shrink the uvs to avoid wrap sampling across edge by object rendering shaders:
+ float virtual_texture_resolution_rcp = 1.0f / float(chunk_data.required_texture_resolution);
+ material->texMulAdd.x = float(chunk_data.required_texture_resolution - 1) * virtual_texture_resolution_rcp;
+ material->texMulAdd.y = float(chunk_data.required_texture_resolution - 1) * virtual_texture_resolution_rcp;
+ material->texMulAdd.z = 0.5f * virtual_texture_resolution_rcp;
+ material->texMulAdd.w = 0.5f * virtual_texture_resolution_rcp;
+
+ for (int i = 0; i < MaterialComponent::TEXTURESLOT_COUNT; ++i)
+ {
+ if (virtual_texture_available[i])
+ {
+ device->BindUAV(material->textures[i].GetGPUResource(), i, cmd);
+ }
+ }
+ }
+
+ device->Dispatch(chunk_data.required_texture_resolution / 8u, chunk_data.required_texture_resolution / 8u, 1, cmd);
+ }
+
+ device->Barrier(virtual_texture_barriers_end.data(), (uint32_t)virtual_texture_barriers_end.size(), cmd);
+ wi::profiler::EndRange(range);
+ device->EventEnd(cmd);
+ }
+
+ // Start the generation on a background thread and keep it running until the next frame
+ wi::jobsystem::Execute(generator->workload, [=](wi::jobsystem::JobArgs args) {
+
+ wi::Timer timer;
+ bool generated_something = false;
+
+ auto request_chunk = [&](int offset_x, int offset_z)
+ {
+ Chunk chunk = center_chunk;
+ chunk.x += offset_x;
+ chunk.z += offset_z;
+ auto it = chunks.find(chunk);
+ if (it == chunks.end() || it->second.entity == INVALID_ENTITY)
+ {
+ // Generate a new chunk:
+ ChunkData& chunk_data = chunks[chunk];
+
+ chunk_data.entity = generator->scene.Entity_CreateObject("chunk_" + std::to_string(chunk.x) + "_" + std::to_string(chunk.z));
+ ObjectComponent& object = *generator->scene.objects.GetComponent(chunk_data.entity);
+ object.lod_distance_multiplier = lod_multiplier;
+ generator->scene.Component_Attach(chunk_data.entity, terrainEntity);
+
+ TransformComponent& transform = *generator->scene.transforms.GetComponent(chunk_data.entity);
+ transform.ClearTransform();
+ chunk_data.position = XMFLOAT3(float(chunk.x * (chunk_width - 1)) * chunk_scale, 0, float(chunk.z * (chunk_width - 1)) * chunk_scale);
+ transform.Translate(chunk_data.position);
+ transform.UpdateTransform();
+
+ MaterialComponent& material = generator->scene.materials.Create(chunk_data.entity);
+ // material params will be 1 because they will be created from only texture maps
+ // because region materials are blended together into one texture
+ material.SetRoughness(1);
+ material.SetMetalness(1);
+ material.SetReflectance(1);
+
+ MeshComponent& mesh = generator->scene.meshes.Create(chunk_data.entity);
+ object.meshID = chunk_data.entity;
+ mesh.indices = chunk_indices.indices;
+ for (auto& lod : chunk_indices.lods)
+ {
+ mesh.subsets.emplace_back();
+ mesh.subsets.back().materialID = chunk_data.entity;
+ mesh.subsets.back().indexCount = lod.indexCount;
+ mesh.subsets.back().indexOffset = lod.indexOffset;
+ }
+ mesh.subsets_per_lod = 1;
+ mesh.vertex_positions.resize(vertexCount);
+ mesh.vertex_normals.resize(vertexCount);
+ mesh.vertex_uvset_0.resize(vertexCount);
+ chunk_data.region_weights.resize(vertexCount);
+
+ chunk_data.mesh_vertex_positions = mesh.vertex_positions.data();
+
+ wi::HairParticleSystem grass = grass_properties;
+ grass.vertex_lengths.resize(vertexCount);
+ std::atomic grass_valid_vertex_count{ 0 };
+
+ // Do a parallel for loop over all the chunk's vertices and compute their properties:
+ wi::jobsystem::context ctx;
+ wi::jobsystem::Dispatch(ctx, vertexCount, chunk_width, [&](wi::jobsystem::JobArgs args) {
+ uint32_t index = args.jobIndex;
+ const float x = (float(index % chunk_width) - chunk_half_width) * chunk_scale;
+ const float z = (float(index / chunk_width) - chunk_half_width) * chunk_scale;
+ XMVECTOR corners[3];
+ XMFLOAT2 corner_offsets[3] = {
+ XMFLOAT2(0, 0),
+ XMFLOAT2(1, 0),
+ XMFLOAT2(0, 1),
+ };
+ for (int i = 0; i < arraysize(corners); ++i)
+ {
+ float height = 0;
+ const XMFLOAT2 world_pos = XMFLOAT2(chunk_data.position.x + x + corner_offsets[i].x, chunk_data.position.z + z + corner_offsets[i].y);
+ for (auto& modifier : modifiers)
+ {
+ modifier->Apply(world_pos, height);
+ }
+ height = wi::math::Lerp(bottomLevel, topLevel, height);
+ corners[i] = XMVectorSet(world_pos.x, height, world_pos.y, 0);
+ }
+ const float height = XMVectorGetY(corners[0]);
+ const XMVECTOR T = XMVectorSubtract(corners[2], corners[1]);
+ const XMVECTOR B = XMVectorSubtract(corners[1], corners[0]);
+ const XMVECTOR N = XMVector3Normalize(XMVector3Cross(T, B));
+ XMFLOAT3 normal;
+ XMStoreFloat3(&normal, N);
+
+ const float region_base = 1;
+ const float region_slope = std::pow(1.0f - wi::math::saturate(normal.y), region1);
+ const float region_low_altitude = bottomLevel == 0 ? 0 : std::pow(wi::math::saturate(wi::math::InverseLerp(0, bottomLevel, height)), region2);
+ const float region_high_altitude = topLevel == 0 ? 0 : std::pow(wi::math::saturate(wi::math::InverseLerp(0, topLevel, height)), region3);
+
+ XMFLOAT4 materialBlendWeights(region_base, 0, 0, 0);
+ materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 1, 0, 0), region_slope);
+ materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 0, 1, 0), region_low_altitude);
+ materialBlendWeights = wi::math::Lerp(materialBlendWeights, XMFLOAT4(0, 0, 0, 1), region_high_altitude);
+ const float weight_norm = 1.0f / (materialBlendWeights.x + materialBlendWeights.y + materialBlendWeights.z + materialBlendWeights.w);
+ materialBlendWeights.x *= weight_norm;
+ materialBlendWeights.y *= weight_norm;
+ materialBlendWeights.z *= weight_norm;
+ materialBlendWeights.w *= weight_norm;
+
+ chunk_data.region_weights[index] = wi::Color::fromFloat4(materialBlendWeights);
+
+ mesh.vertex_positions[index] = XMFLOAT3(x, height, z);
+ mesh.vertex_normals[index] = normal;
+ const XMFLOAT2 uv = XMFLOAT2(x * chunk_scale_rcp * chunk_width_rcp + 0.5f, z * chunk_scale_rcp * chunk_width_rcp + 0.5f);
+ mesh.vertex_uvset_0[index] = uv;
+
+ XMFLOAT3 vertex_pos(chunk_data.position.x + x, height, chunk_data.position.z + z);
+
+ const float grass_noise_frequency = 0.1f;
+ const float grass_noise = perlin_noise.compute(vertex_pos.x * grass_noise_frequency, vertex_pos.y * grass_noise_frequency, vertex_pos.z * grass_noise_frequency) * 0.5f + 0.5f;
+ const float region_grass = std::pow(materialBlendWeights.x * (1 - materialBlendWeights.w), 8.0f) * grass_noise;
+ if (region_grass > 0.1f)
+ {
+ grass_valid_vertex_count.fetch_add(1);
+ grass.vertex_lengths[index] = region_grass;
+ }
+ else
+ {
+ grass.vertex_lengths[index] = 0;
+ }
+ });
+ wi::jobsystem::Wait(ctx); // wait until chunk's vertex buffer is fully generated
+
+ wi::jobsystem::Execute(ctx, [&](wi::jobsystem::JobArgs args) {
+ mesh.CreateRenderData();
+ chunk_data.sphere.center = mesh.aabb.getCenter();
+ chunk_data.sphere.center.x += chunk_data.position.x;
+ chunk_data.sphere.center.y += chunk_data.position.y;
+ chunk_data.sphere.center.z += chunk_data.position.z;
+ chunk_data.sphere.radius = mesh.aabb.getRadius();
+ });
+
+ // If there were any vertices in this chunk that could be valid for grass, store the grass particle system:
+ if (grass_valid_vertex_count.load() > 0)
+ {
+ chunk_data.grass = std::move(grass); // the grass will be added to the scene later, only when the chunk is close to the camera (center chunk's neighbors)
+ chunk_data.grass.meshID = chunk_data.entity;
+ chunk_data.grass.strandCount = uint32_t(grass_valid_vertex_count.load() * 3 * chunk_scale * chunk_scale); // chunk_scale * chunk_scale : grass density increases with squared amount with chunk scale (x*z)
+ chunk_data.grass.viewDistance = chunk_width * chunk_scale;
+ }
+
+ // Create the blend weights texture for virtual texture update:
+ CreateChunkRegionTexture(chunk_data);
+
+ wi::jobsystem::Wait(ctx); // wait until mesh.CreateRenderData() async task finishes
+ generated_something = true;
+ }
+
+ const int dist = std::max(std::abs(center_chunk.x - chunk.x), std::abs(center_chunk.z - chunk.z));
+
+ // Grass patch placement:
+ if (dist <= 1 && IsGrassEnabled())
+ {
+ it = chunks.find(chunk);
+ if (it != chunks.end() && it->second.entity != INVALID_ENTITY)
+ {
+ ChunkData& chunk_data = it->second;
+ if (chunk_data.grass_entity == INVALID_ENTITY && chunk_data.grass.meshID != INVALID_ENTITY)
+ {
+ // add patch for this chunk
+ chunk_data.grass_entity = CreateEntity();
+ wi::HairParticleSystem& grass = generator->scene.hairs.Create(chunk_data.grass_entity);
+ grass = chunk_data.grass;
+ chunk_data.grass_density_current = grass_density;
+ grass.strandCount = uint32_t(grass.strandCount * chunk_data.grass_density_current);
+ generator->scene.materials.Create(chunk_data.grass_entity) = material_GrassParticle;
+ generator->scene.transforms.Create(chunk_data.grass_entity);
+ generator->scene.names.Create(chunk_data.grass_entity) = "grass";
+ generator->scene.Component_Attach(chunk_data.grass_entity, chunk_data.entity, true);
+ generated_something = true;
+ }
+ }
+ }
+
+ // Prop placement:
+ if (dist <= prop_generation)
+ {
+ it = chunks.find(chunk);
+ if (it != chunks.end() && it->second.entity != INVALID_ENTITY)
+ {
+ ChunkData& chunk_data = it->second;
+
+ if (chunk_data.props_entity == INVALID_ENTITY && chunk_data.mesh_vertex_positions != nullptr)
+ {
+ chunk_data.props_entity = CreateEntity();
+ generator->scene.transforms.Create(chunk_data.props_entity);
+ generator->scene.names.Create(chunk_data.props_entity) = "props";
+ generator->scene.Component_Attach(chunk_data.props_entity, chunk_data.entity, true);
+ chunk_data.prop_density_current = prop_density;
+
+ std::mt19937 prop_rand;
+ prop_rand.seed((uint32_t)chunk.compute_hash() ^ seed);
+
+ for (auto& prop : props)
+ {
+ std::uniform_int_distribution gen_distr(
+ uint32_t(prop.min_count_per_chunk * chunk_data.prop_density_current),
+ uint32_t(prop.max_count_per_chunk * chunk_data.prop_density_current)
+ );
+ int gen_count = gen_distr(prop_rand);
+ for (int i = 0; i < gen_count; ++i)
+ {
+ std::uniform_real_distribution float_distr(0.0f, 1.0f);
+ std::uniform_int_distribution ind_distr(0, chunk_indices.lods[0].indexCount / 3 - 1);
+ uint32_t tri = ind_distr(prop_rand); // random triangle on the chunk mesh
+ uint32_t ind0 = chunk_indices.indices[tri * 3 + 0];
+ uint32_t ind1 = chunk_indices.indices[tri * 3 + 1];
+ uint32_t ind2 = chunk_indices.indices[tri * 3 + 2];
+ const XMFLOAT3& pos0 = chunk_data.mesh_vertex_positions[ind0];
+ const XMFLOAT3& pos1 = chunk_data.mesh_vertex_positions[ind1];
+ const XMFLOAT3& pos2 = chunk_data.mesh_vertex_positions[ind2];
+ const XMFLOAT4 region0 = chunk_data.region_weights[ind0];
+ const XMFLOAT4 region1 = chunk_data.region_weights[ind1];
+ const XMFLOAT4 region2 = chunk_data.region_weights[ind2];
+ // random barycentric coords on the triangle:
+ float f = float_distr(prop_rand);
+ float g = float_distr(prop_rand);
+ if (f + g > 1)
+ {
+ f = 1 - f;
+ g = 1 - g;
+ }
+ XMFLOAT3 vertex_pos;
+ vertex_pos.x = pos0.x + f * (pos1.x - pos0.x) + g * (pos2.x - pos0.x);
+ vertex_pos.y = pos0.y + f * (pos1.y - pos0.y) + g * (pos2.y - pos0.y);
+ vertex_pos.z = pos0.z + f * (pos1.z - pos0.z) + g * (pos2.z - pos0.z);
+ XMFLOAT4 region;
+ region.x = region0.x + f * (region1.x - region0.x) + g * (region2.x - region0.x);
+ region.y = region0.y + f * (region1.y - region0.y) + g * (region2.y - region0.y);
+ region.z = region0.z + f * (region1.z - region0.z) + g * (region2.z - region0.z);
+ region.w = region0.w + f * (region1.w - region0.w) + g * (region2.w - region0.w);
+
+ const float noise = std::pow(perlin_noise.compute((vertex_pos.x + chunk_data.position.x) * prop.noise_frequency, vertex_pos.y * prop.noise_frequency, (vertex_pos.z + chunk_data.position.z) * prop.noise_frequency) * 0.5f + 0.5f, prop.noise_power);
+ const float chance = std::pow(((float*)®ion)[prop.region], prop.region_power) * noise;
+ if (chance > prop.threshold)
+ {
+ Entity entity = generator->scene.Entity_CreateObject(prop.name + std::to_string(i));
+ ObjectComponent* object = generator->scene.objects.GetComponent(entity);
+ *object = prop.object;
+ TransformComponent* transform = generator->scene.transforms.GetComponent(entity);
+ XMFLOAT3 offset = vertex_pos;
+ offset.y += wi::math::Lerp(prop.min_y_offset, prop.max_y_offset, float_distr(prop_rand));
+ transform->Translate(offset);
+ const float scaling = wi::math::Lerp(prop.min_size, prop.max_size, float_distr(prop_rand));
+ transform->Scale(XMFLOAT3(scaling, scaling, scaling));
+ transform->RotateRollPitchYaw(XMFLOAT3(0, XM_2PI * float_distr(prop_rand), 0));
+ transform->UpdateTransform();
+ generator->scene.Component_Attach(entity, chunk_data.props_entity, true);
+ generated_something = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (generated_something && timer.elapsed_milliseconds() > generation_time_budget_milliseconds)
+ {
+ generator->cancelled.store(true);
+ }
+
+ };
+
+ // generate center chunk first:
+ request_chunk(0, 0);
+ if (generator->cancelled.load()) return;
+
+ // then generate neighbor chunks in outward spiral:
+ for (int growth = 0; growth < generation; ++growth)
+ {
+ const int side = 2 * (growth + 1);
+ int x = -growth - 1;
+ int z = -growth - 1;
+ for (int i = 0; i < side; ++i)
+ {
+ request_chunk(x, z);
+ if (generator->cancelled.load()) return;
+ x++;
+ }
+ for (int i = 0; i < side; ++i)
+ {
+ request_chunk(x, z);
+ if (generator->cancelled.load()) return;
+ z++;
+ }
+ for (int i = 0; i < side; ++i)
+ {
+ request_chunk(x, z);
+ if (generator->cancelled.load()) return;
+ x--;
+ }
+ for (int i = 0; i < side; ++i)
+ {
+ request_chunk(x, z);
+ if (generator->cancelled.load()) return;
+ z--;
+ }
+ }
+
+ });
+
+ }
+
+ void Terrain::Generation_Cancel()
+ {
+ if (generator == nullptr)
+ return;
+ generator->cancelled.store(true); // tell the generation thread that work must be stopped
+ wi::jobsystem::Wait(generator->workload); // waits until generation thread exits
+ generator->cancelled.store(false); // the next generation can run
+ }
+
+ void Terrain::BakeVirtualTexturesToFiles()
+ {
+ if (terrainEntity == INVALID_ENTITY)
+ {
+ return;
+ }
+
+ wi::jobsystem::context ctx;
+
+ static const std::string extension = "PNG";
+
+ for (auto it = chunks.begin(); it != chunks.end(); it++)
+ {
+ const Chunk& chunk = it->first;
+ ChunkData& chunk_data = it->second;
+ MaterialComponent* material = scene->materials.GetComponent(chunk_data.entity);
+ if (material != nullptr)
+ {
+ for (int i = 0; i < MaterialComponent::TEXTURESLOT_COUNT; ++i)
+ {
+ auto& tex = material->textures[i];
+ switch (i)
+ {
+ case MaterialComponent::BASECOLORMAP:
+ case MaterialComponent::SURFACEMAP:
+ case MaterialComponent::NORMALMAP:
+ if (tex.name.empty() && tex.GetGPUResource() != nullptr)
+ {
+ wi::vector filedata;
+ if (wi::helper::saveTextureToMemory(tex.resource.GetTexture(), filedata))
+ {
+ tex.resource.SetFileData(std::move(filedata));
+ wi::jobsystem::Execute(ctx, [i, &tex, chunk](wi::jobsystem::JobArgs args) {
+ wi::vector filedata_ktx2;
+ if (wi::helper::saveTextureToMemoryFile(tex.resource.GetFileData(), tex.resource.GetTexture().desc, extension, filedata_ktx2))
+ {
+ tex.name = std::to_string(chunk.x) + "_" + std::to_string(chunk.z);
+ switch (i)
+ {
+ case MaterialComponent::BASECOLORMAP:
+ tex.name += "_basecolormap";
+ break;
+ case MaterialComponent::SURFACEMAP:
+ tex.name += "_surfacemap";
+ break;
+ case MaterialComponent::NORMALMAP:
+ tex.name += "_normalmap";
+ break;
+ default:
+ break;
+ }
+ tex.name += "." + extension;
+ tex.resource = wi::resourcemanager::Load(tex.name, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA, filedata_ktx2.data(), filedata_ktx2.size());
+ }
+ });
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ wi::helper::messageBox("Baking terrain virtual textures to static textures, this could take a while!", "Attention!");
+
+ wi::jobsystem::Wait(ctx);
+
+ for (auto it = chunks.begin(); it != chunks.end(); it++)
+ {
+ const Chunk& chunk = it->first;
+ ChunkData& chunk_data = it->second;
+ MaterialComponent* material = scene->materials.GetComponent(chunk_data.entity);
+ if (material != nullptr)
+ {
+ material->CreateRenderData();
+ }
+ }
+ }
+
+ void Terrain::CreateChunkRegionTexture(ChunkData& chunk_data)
+ {
+ TextureDesc desc;
+ desc.width = (uint32_t)chunk_width;
+ desc.height = (uint32_t)chunk_width;
+ desc.format = Format::R8G8B8A8_UNORM;
+ desc.bind_flags = BindFlag::SHADER_RESOURCE;
+ SubresourceData data;
+ data.data_ptr = chunk_data.region_weights.data();
+ data.row_pitch = chunk_width * sizeof(chunk_data.region_weights[0]);
+ bool success = GetDevice()->CreateTexture(&desc, &data, &chunk_data.region_weights_texture);
+ assert(success);
+ }
+
+ void Terrain::Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri)
+ {
+ Generation_Cancel();
+
+ if (archive.IsReadMode())
+ {
+ archive >> _flags;
+ archive >> lod_multiplier;
+ archive >> texlod;
+ archive >> generation;
+ archive >> prop_generation;
+ archive >> prop_density;
+ archive >> grass_density;
+ archive >> chunk_scale;
+ archive >> seed;
+ archive >> bottomLevel;
+ archive >> topLevel;
+ archive >> region1;
+ archive >> region2;
+ archive >> region3;
+
+ archive >> center_chunk.x;
+ archive >> center_chunk.z;
+
+ size_t count = 0;
+ archive >> count;
+ props.resize(count);
+ for (size_t i = 0; i < props.size(); ++i)
+ {
+ Prop& prop = props[i];
+ archive >> prop.name;
+ SerializeEntity(archive, prop.mesh_entity, seri);
+ prop.object.Serialize(archive, seri);
+ archive >> prop.min_count_per_chunk;
+ archive >> prop.max_count_per_chunk;
+ archive >> prop.region;
+ archive >> prop.region_power;
+ archive >> prop.noise_frequency;
+ archive >> prop.noise_power;
+ archive >> prop.threshold;
+ archive >> prop.min_size;
+ archive >> prop.max_size;
+ archive >> prop.min_y_offset;
+ archive >> prop.max_y_offset;
+ }
+
+ archive >> count;
+ chunks.reserve(count);
+ for (size_t i = 0; i < count; ++i)
+ {
+ Chunk chunk;
+ archive >> chunk.x;
+ archive >> chunk.z;
+ ChunkData& chunk_data = chunks[chunk];
+ SerializeEntity(archive, chunk_data.entity, seri);
+ SerializeEntity(archive, chunk_data.grass_entity, seri);
+ SerializeEntity(archive, chunk_data.props_entity, seri);
+ archive >> chunk_data.prop_density_current;
+ chunk_data.grass.Serialize(archive, seri);
+ archive >> chunk_data.grass_density_current;
+ archive >> chunk_data.region_weights;
+ CreateChunkRegionTexture(chunk_data);
+ archive >> chunk_data.sphere.center;
+ archive >> chunk_data.sphere.radius;
+ archive >> chunk_data.position;
+ }
+
+ archive >> count;
+ modifiers.resize(count);
+ for (size_t i = 0; i < modifiers.size(); ++i)
+ {
+ uint32_t value;
+ archive >> value;
+ Modifier::Type type = (Modifier::Type)value;
+
+ switch (type)
+ {
+ default:
+ case Modifier::Type::Perlin:
+ {
+ std::shared_ptr modifier = std::make_shared();
+ modifiers[i] = modifier;
+ archive >> modifier->octaves;
+ archive >> modifier->seed;
+ modifier->perlin_noise.Serialize(archive);
+ }
+ break;
+ case Modifier::Type::Voronoi:
+ {
+ std::shared_ptr modifier = std::make_shared();
+ modifiers[i] = modifier;
+ archive >> modifier->fade;
+ archive >> modifier->shape;
+ archive >> modifier->falloff;
+ archive >> modifier->perturbation;
+ archive >> modifier->seed;
+ modifier->perlin_noise.Serialize(archive);
+ }
+ break;
+ case Modifier::Type::Heightmap:
+ {
+ std::shared_ptr modifier = std::make_shared();
+ modifiers[i] = modifier;
+ archive >> modifier->scale;
+ archive >> modifier->data;
+ archive >> modifier->width;
+ archive >> modifier->height;
+ }
+ break;
+ }
+
+ Modifier* modifier = modifiers[i].get();
+ archive >> value;
+ modifier->blend = (Modifier::BlendMode)value;
+ archive >> modifier->weight;
+ archive >> modifier->frequency;
+ }
+ }
+ else
+ {
+ BakeVirtualTexturesToFiles();
+
+ archive << _flags;
+ archive << lod_multiplier;
+ archive << texlod;
+ archive << generation;
+ archive << prop_generation;
+ archive << prop_density;
+ archive << grass_density;
+ archive << chunk_scale;
+ archive << seed;
+ archive << bottomLevel;
+ archive << topLevel;
+ archive << region1;
+ archive << region2;
+ archive << region3;
+
+ archive << center_chunk.x;
+ archive << center_chunk.z;
+
+ archive << props.size();
+ for (size_t i = 0; i < props.size(); ++i)
+ {
+ Prop& prop = props[i];
+ archive << prop.name;
+ SerializeEntity(archive, prop.mesh_entity, seri);
+ prop.object.Serialize(archive, seri);
+ archive << prop.min_count_per_chunk;
+ archive << prop.max_count_per_chunk;
+ archive << prop.region;
+ archive << prop.region_power;
+ archive << prop.noise_frequency;
+ archive << prop.noise_power;
+ archive << prop.threshold;
+ archive << prop.min_size;
+ archive << prop.max_size;
+ archive << prop.min_y_offset;
+ archive << prop.max_y_offset;
+ }
+
+ archive << chunks.size();
+ for (auto& it : chunks)
+ {
+ Chunk& chunk = it.first;
+ archive << chunk.x;
+ archive << chunk.z;
+ ChunkData& chunk_data = it.second;
+ SerializeEntity(archive, chunk_data.entity, seri);
+ SerializeEntity(archive, chunk_data.grass_entity, seri);
+ SerializeEntity(archive, chunk_data.props_entity, seri);
+ archive << chunk_data.prop_density_current;
+ chunk_data.grass.Serialize(archive, seri);
+ archive << chunk_data.grass_density_current;
+ archive << chunk_data.region_weights;
+ archive << chunk_data.sphere.center;
+ archive << chunk_data.sphere.radius;
+ archive << chunk_data.position;
+ }
+
+ archive << modifiers.size();
+ for (auto& modifier : modifiers)
+ {
+ archive << (uint32_t)modifier->type;
+ switch (modifier->type)
+ {
+ default:
+ case Modifier::Type::Perlin:
+ archive << ((PerlinModifier*)modifier.get())->octaves;
+ archive << ((PerlinModifier*)modifier.get())->seed;
+ ((PerlinModifier*)modifier.get())->perlin_noise.Serialize(archive);
+ break;
+ case Modifier::Type::Voronoi:
+ archive << ((VoronoiModifier*)modifier.get())->fade;
+ archive << ((VoronoiModifier*)modifier.get())->shape;
+ archive << ((VoronoiModifier*)modifier.get())->falloff;
+ archive << ((VoronoiModifier*)modifier.get())->perturbation;
+ archive << ((VoronoiModifier*)modifier.get())->seed;
+ ((VoronoiModifier*)modifier.get())->perlin_noise.Serialize(archive);
+ break;
+ case Modifier::Type::Heightmap:
+ archive << ((HeightmapModifier*)modifier.get())->scale;
+ archive << ((HeightmapModifier*)modifier.get())->data;
+ archive << ((HeightmapModifier*)modifier.get())->width;
+ archive << ((HeightmapModifier*)modifier.get())->height;
+ break;
+ }
+
+ archive << (uint32_t)modifier->blend;
+ archive << modifier->weight;
+ archive << modifier->frequency;
+ }
+ }
+
+ material_Base.Serialize(archive, seri);
+ material_Slope.Serialize(archive, seri);
+ material_LowAltitude.Serialize(archive, seri);
+ material_HighAltitude.Serialize(archive, seri);
+ material_GrassParticle.Serialize(archive, seri);
+ weather.Serialize(archive, seri);
+ grass_properties.Serialize(archive, seri);
+ perlin_noise.Serialize(archive);
+ }
+
+}
diff --git a/WickedEngine/wiTerrain.h b/WickedEngine/wiTerrain.h
new file mode 100644
index 000000000..f15292f07
--- /dev/null
+++ b/WickedEngine/wiTerrain.h
@@ -0,0 +1,281 @@
+#pragma once
+#include "CommonInclude.h"
+#include "wiScene_Decl.h"
+#include "wiScene_Components.h"
+#include "wiNoise.h"
+#include "wiECS.h"
+#include "wiColor.h"
+#include "wiHairParticle.h"
+
+#include
+#include
+#include
+
+namespace wi::terrain
+{
+ struct Chunk
+ {
+ int x, z;
+ constexpr bool operator==(const Chunk& other) const
+ {
+ return (x == other.x) && (z == other.z);
+ }
+ inline size_t compute_hash() const
+ {
+ return ((std::hash()(x) ^ (std::hash()(z) << 1)) >> 1);
+ }
+ };
+}
+
+namespace std
+{
+ template <>
+ struct hash
+ {
+ inline size_t operator()(const wi::terrain::Chunk& chunk) const
+ {
+ return chunk.compute_hash();
+ }
+ };
+}
+
+namespace wi::terrain
+{
+ static constexpr int chunk_width = 64 + 3; // + 3: filler vertices for lod apron and grid perimeter
+ static constexpr float chunk_half_width = (chunk_width - 1) * 0.5f;
+ static constexpr float chunk_width_rcp = 1.0f / (chunk_width - 1);
+ static constexpr uint32_t vertexCount = chunk_width * chunk_width;
+
+ struct ChunkData
+ {
+ wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY;
+ wi::ecs::Entity grass_entity = wi::ecs::INVALID_ENTITY;
+ wi::ecs::Entity props_entity = wi::ecs::INVALID_ENTITY;
+ const XMFLOAT3* mesh_vertex_positions = nullptr;
+ float prop_density_current = 1;
+ wi::HairParticleSystem grass;
+ float grass_density_current = 1;
+ wi::vector region_weights;
+ wi::graphics::Texture region_weights_texture;
+ uint32_t required_texture_resolution = 0;
+ wi::primitive::Sphere sphere;
+ XMFLOAT3 position = XMFLOAT3(0, 0, 0);
+ };
+
+ struct Prop
+ {
+ std::string name = "prop";
+ wi::ecs::Entity mesh_entity = wi::ecs::INVALID_ENTITY;
+ wi::scene::ObjectComponent object;
+ int min_count_per_chunk = 0; // a chunk will try to generate min this many props of this type
+ int max_count_per_chunk = 10; // a chunk will try to generate max this many props of this type
+ int region = 0; // region selection in range [0,3] (0: base/grass, 1: slopes, 2: low altitude (bottom level-0), 3: high altitude (0-top level))
+ float region_power = 1; // region weight affection power factor
+ float noise_frequency = 1; // perlin noise's frequency for placement factor
+ float noise_power = 1; // perlin noise's power
+ float threshold = 0.5f; // the chance of placement (higher is less chance)
+ float min_size = 1; // scaling randomization range min
+ float max_size = 1; // scaling randomization range max
+ float min_y_offset = 0; // min randomized offset on Y axis
+ float max_y_offset = 0; // max randomized offset on Y axis
+ };
+
+ struct Modifier;
+ struct Generator;
+
+ struct Terrain
+ {
+ enum FLAGS
+ {
+ EMPTY = 0,
+ CENTER_TO_CAM = 1 << 0,
+ REMOVAL = 1 << 1,
+ GRASS = 1 << 2,
+ GENERATION_STARTED = 1 << 4,
+ };
+ uint32_t _flags = CENTER_TO_CAM | REMOVAL | GRASS;
+
+ wi::ecs::Entity terrainEntity = wi::ecs::INVALID_ENTITY;
+ wi::scene::Scene* scene = nullptr;
+ wi::scene::MaterialComponent material_Base;
+ wi::scene::MaterialComponent material_Slope;
+ wi::scene::MaterialComponent material_LowAltitude;
+ wi::scene::MaterialComponent material_HighAltitude;
+ wi::scene::MaterialComponent material_GrassParticle;
+ wi::scene::WeatherComponent weather;
+ wi::HairParticleSystem grass_properties;
+ wi::unordered_map chunks;
+ Chunk center_chunk = {};
+ wi::noise::Perlin perlin_noise;
+ wi::vector props;
+
+ // For generating scene on a background thread:
+ std::shared_ptr generator;
+ float generation_time_budget_milliseconds = 12; // after this much time, the generation thread will exit. This can help avoid a very long running, resource consuming and slow cancellation generation
+
+ // Virtual texture updates will be batched like:
+ // 1) Execute all barriers (dst: UNORDERED_ACCESS)
+ // 2) Execute all compute shaders
+ // 3) Execute all barriers (dst: SHADER_RESOURCE)
+ wi::vector virtual_texture_updates;
+ wi::vector virtual_texture_barriers_begin;
+ wi::vector virtual_texture_barriers_end;
+
+ constexpr bool IsCenterToCamEnabled() const { return _flags & CENTER_TO_CAM; }
+ constexpr bool IsRemovalEnabled() const { return _flags & REMOVAL; }
+ constexpr bool IsGrassEnabled() const { return _flags & GRASS; }
+ constexpr bool IsGenerationStarted() const { return _flags & GENERATION_STARTED; }
+
+ constexpr void SetCenterToCamEnabled(bool value) { if (value) { _flags |= CENTER_TO_CAM; } else { _flags &= ~CENTER_TO_CAM; } }
+ constexpr void SetRemovalEnabled(bool value) { if (value) { _flags |= REMOVAL; } else { _flags &= ~REMOVAL; } }
+ constexpr void SetGrassEnabled(bool value) { if (value) { _flags |= GRASS; } else { _flags &= ~GRASS; } }
+ constexpr void SetGenerationStarted(bool value) { if (value) { _flags |= GENERATION_STARTED; } else { _flags &= ~GENERATION_STARTED; } }
+
+ float lod_multiplier = 0.005f;
+ float texlod = 0.01f;
+ int generation = 12;
+ int prop_generation = 10;
+ float prop_density = 1;
+ float grass_density = 1;
+ float chunk_scale = 1;
+ uint32_t seed = 3926;
+ float bottomLevel = -60;
+ float topLevel = 380;
+ float region1 = 1;
+ float region2 = 2;
+ float region3 = 8;
+
+ wi::vector> modifiers;
+ wi::vector modifiers_to_remove;
+
+ Terrain();
+ ~Terrain();
+
+ // Restarts the terrain generation from scratch
+ // This will remove previously existing terrain
+ void Generation_Restart();
+ // This will run the actual generation tasks, call it once per frame
+ void Generation_Update(const wi::scene::CameraComponent& camera);
+ // Tells the generation thread that it should be cancelled and blocks until that is confirmed
+ void Generation_Cancel();
+ // The virtual textures will be compressed and saved into resources. They can be serialized from there
+ void BakeVirtualTexturesToFiles();
+ // Creates the blend weight texture for a chunk data
+ void CreateChunkRegionTexture(ChunkData& chunk_data);
+
+ void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
+ };
+
+ struct Modifier
+ {
+ enum class Type
+ {
+ Perlin,
+ Voronoi,
+ Heightmap,
+ } type = Type::Perlin;
+
+ enum class BlendMode
+ {
+ Normal,
+ Additive,
+ Multiply,
+ } blend = BlendMode::Normal;
+
+ float weight = 0.5f;
+ float frequency = 0.0008f;
+
+ virtual void Seed(uint32_t seed) {}
+ virtual void Apply(const XMFLOAT2& world_pos, float& height) = 0;
+ constexpr void Blend(float& height, float value)
+ {
+ switch (blend)
+ {
+ default:
+ case BlendMode::Normal:
+ height = wi::math::Lerp(height, value, weight);
+ break;
+ case BlendMode::Multiply:
+ height *= value * weight;
+ break;
+ case BlendMode::Additive:
+ height += value * weight;
+ break;
+ }
+ }
+ };
+ struct PerlinModifier : public Modifier
+ {
+ int octaves = 6;
+ uint32_t seed = 0;
+ wi::noise::Perlin perlin_noise;
+
+ PerlinModifier() { type = Type::Perlin; }
+ void Seed(uint32_t seed) override
+ {
+ this->seed = seed;
+ perlin_noise.init(seed);
+ }
+ void Apply(const XMFLOAT2& world_pos, float& height) override
+ {
+ XMFLOAT2 p = world_pos;
+ p.x *= frequency;
+ p.y *= frequency;
+ Blend(height, perlin_noise.compute(p.x, p.y, 0, octaves) * 0.5f + 0.5f);
+ }
+ };
+ struct VoronoiModifier : public Modifier
+ {
+ float fade = 2.59f;
+ float shape = 0.7f;
+ float falloff = 6;
+ float perturbation = 0.1f;
+ uint32_t seed = 0;
+ wi::noise::Perlin perlin_noise;
+
+ VoronoiModifier() { type = Type::Voronoi; }
+ void Seed(uint32_t seed) override
+ {
+ this->seed = seed;
+ perlin_noise.init(seed);
+ }
+ void Apply(const XMFLOAT2& world_pos, float& height) override
+ {
+ XMFLOAT2 p = world_pos;
+ p.x *= frequency;
+ p.y *= frequency;
+ if (perturbation > 0)
+ {
+ const float angle = perlin_noise.compute(p.x, p.y, 0, 6) * XM_2PI;
+ p.x += std::sin(angle) * perturbation;
+ p.y += std::cos(angle) * perturbation;
+ }
+ wi::noise::voronoi::Result res = wi::noise::voronoi::compute(p.x, p.y, (float)seed);
+ float weight = std::pow(1 - wi::math::saturate((res.distance - shape) * fade), std::max(0.0001f, falloff));
+ Blend(height, weight);
+ }
+ };
+ struct HeightmapModifier : public Modifier
+ {
+ float scale = 0.1f;
+
+ wi::vector data;
+ int width = 0;
+ int height = 0;
+
+ HeightmapModifier() { type = Type::Heightmap; }
+ void Apply(const XMFLOAT2& world_pos, float& height) override
+ {
+ XMFLOAT2 p = world_pos;
+ p.x *= frequency;
+ p.y *= frequency;
+ XMFLOAT2 pixel = XMFLOAT2(p.x + width * 0.5f, p.y + this->height * 0.5f);
+ if (pixel.x >= 0 && pixel.x < width && pixel.y >= 0 && pixel.y < this->height)
+ {
+ const int idx = int(pixel.x) + int(pixel.y) * width;
+ Blend(height, ((float)data[idx] / 255.0f) * scale);
+ }
+ }
+ };
+
+}
diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp
index a84989454..0a573a88a 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 = 71;
// minor bug fixes, alterations, refactors, updates
- const int revision = 33;
+ const int revision = 34;
const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);