diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml
index 7d96eb4ce..757be35cb 100644
--- a/.github/workflows/build-nightly.yml
+++ b/.github/workflows/build-nightly.yml
@@ -40,6 +40,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -59,6 +60,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
@@ -108,6 +110,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -127,6 +130,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
@@ -175,6 +179,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -194,6 +199,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
index 05b3b5803..5e8144704 100644
--- a/.github/workflows/build-pr.yml
+++ b/.github/workflows/build-pr.yml
@@ -39,6 +39,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -58,6 +59,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
@@ -108,6 +110,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -127,6 +130,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
@@ -175,6 +179,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -194,6 +199,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6b9a26bf5..25f1bcb71 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -40,6 +40,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -59,6 +60,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
@@ -109,6 +111,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -128,6 +131,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
@@ -176,6 +180,7 @@ jobs:
other_licenses.txt
features.txt
Editor/images/
+ Editor/terrain/
Editor/sound/
Editor/*.ini
Editor/*.ico
@@ -195,6 +200,7 @@ jobs:
Tests/images/
Tests/sound/
Tests/*.ini
+ Tests/*.txt
Tests/*.ico
Tests/*.lua
Tests/*.ttf
diff --git a/Content/scripts/character_controller_tps.lua b/Content/scripts/character_controller_tps.lua
index 007f32448..6944e49a0 100644
--- a/Content/scripts/character_controller_tps.lua
+++ b/Content/scripts/character_controller_tps.lua
@@ -360,8 +360,10 @@ ThirdPersonCamera = {
local corner = vector.TransformCoord(coord, unproj)
local target_to_corner = vector.Subtract(corner, targetPos)
local corner_to_campos = vector.Subtract(camPos, corner)
+ local TMin = 0
+ local TMax = target_to_corner.Length() -- optimization: limit the ray tracing distance
- local ray = Ray(targetPos, target_to_corner.Normalize())
+ local ray = Ray(targetPos, target_to_corner.Normalize(), TMin, TMax)
local collObj,collPos,collNor = Pick(ray, PICK_OPAQUE, ~self.character.layerMask)
if(collObj ~= INVALID_ENTITY) then
@@ -425,7 +427,6 @@ runProcess(function()
while true do
player:Update()
- camera:Update()
fixedupdate()
if(input.Press(KEYBOARD_BUTTON_ESCAPE)) then
@@ -451,6 +452,7 @@ end)
runProcess(function()
while true do
+ camera:Update()
player:Input()
update()
diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt
index 9a5ccd3d0..aa2f03e83 100644
--- a/Editor/CMakeLists.txt
+++ b/Editor/CMakeLists.txt
@@ -28,6 +28,7 @@ set (SOURCE_FILES
TransformWindow.cpp
Translator.cpp
WeatherWindow.cpp
+ TerrainGenerator.cpp
xatlas.cpp
)
diff --git a/Editor/CameraWindow.cpp b/Editor/CameraWindow.cpp
index 8abd65520..86f303914 100644
--- a/Editor/CameraWindow.cpp
+++ b/Editor/CameraWindow.cpp
@@ -41,7 +41,7 @@ void CameraWindow::Create(EditorComponent* editor)
float hei = 18;
float step = hei + 2;
- farPlaneSlider.Create(1, 5000, 1000, 100000, "Far Plane: ");
+ farPlaneSlider.Create(0.1f, 10000, 5000, 100000, "Far Plane: ");
farPlaneSlider.SetTooltip("Controls the camera's far clip plane, geometry farther than this will be clipped.");
farPlaneSlider.SetSize(XMFLOAT2(100, hei));
farPlaneSlider.SetPos(XMFLOAT2(x, y += step));
diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp
index befe438c8..185d861fb 100644
--- a/Editor/Editor.cpp
+++ b/Editor/Editor.cpp
@@ -202,6 +202,9 @@ void EditorComponent::ResizeLayout()
paintToolWnd_Toggle.SetPos(XMFLOAT2(x += hstep, y));
paintToolWnd_Toggle.SetSize(option_size);
+ terrainWnd_Toggle.SetPos(XMFLOAT2(x += hstep, y));
+ terrainWnd_Toggle.SetSize(option_size);
+
/////////////////////////
option_size.y = 16;
@@ -364,6 +367,135 @@ void EditorComponent::Load()
});
GetGUI().AddWidget(&paintToolWnd_Toggle);
+ terrainWnd_Toggle.Create("Terrain");
+ terrainWnd_Toggle.SetTooltip("Terrain Generator");
+ terrainWnd_Toggle.OnClick([&](wi::gui::EventArgs args) {
+
+ if (terragen.terrainEntity == INVALID_ENTITY)
+ {
+ // Customize terrain generator before it's initialized:
+ terragen.material_Base.SetUseVertexColors(true);
+ terragen.material_Base.SetRoughness(1);
+ terragen.material_Slope.SetRoughness(0.5f);
+ 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.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");
+ 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
+ wi::scene::GetScene().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
+ }
+ props_scene.Entity_Remove(object_entity); // The objects will be placed by terrain generator, we don't need the default object that the scene has anymore
+ wi::scene::GetScene().Merge(props_scene);
+ }
+ // 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
+ }
+ props_scene.Entity_Remove(object_entity); // The objects will be placed by terrain generator, we don't need the default object that the scene has anymore
+ wi::scene::GetScene().Merge(props_scene);
+ }
+ terragen.init();
+ RefreshSceneGraphView();
+ }
+
+ terragen.SetVisible(!terragen.IsVisible());
+ if (terragen.IsVisible() && !wi::scene::GetScene().transforms.Contains(terragen.terrainEntity))
+ {
+ terragen.Generation_Restart();
+ RefreshSceneGraphView();
+ }
+
+ });
+ GetGUI().AddWidget(&terrainWnd_Toggle);
+ GetGUI().AddWidget(&terragen);
+
///////////////////////
wi::Color option_color_idle = wi::Color(255, 145, 145, 100);
@@ -736,8 +868,15 @@ void EditorComponent::Load()
clearButton.SetColor(wi::Color(255, 173, 43, 180), wi::gui::WIDGETSTATE::IDLE);
clearButton.SetColor(wi::Color(255, 235, 173, 255), wi::gui::WIDGETSTATE::FOCUS);
clearButton.OnClick([&](wi::gui::EventArgs args) {
+
+ terragen.Generation_Cancel();
+ // This is to recreate the terragen from scratch, but it has implicitly deleted copy ctor so it's weird:
+ terragen.~TerrainGenerator();
+ new (&terragen) TerrainGenerator;
+
translator.selected.clear();
- wi::renderer::ClearWorld(wi::scene::GetScene());
+ wi::scene::Scene& scene = wi::scene::GetScene();
+ wi::renderer::ClearWorld(scene);
objectWnd.SetEntity(INVALID_ENTITY);
meshWnd.SetEntity(INVALID_ENTITY, -1);
lightWnd.SetEntity(INVALID_ENTITY);
@@ -813,6 +952,7 @@ void EditorComponent::Load()
exitButton.SetColor(wi::Color(190, 0, 0, 180), wi::gui::WIDGETSTATE::IDLE);
exitButton.SetColor(wi::Color(255, 0, 0, 255), wi::gui::WIDGETSTATE::FOCUS);
exitButton.OnClick([this](wi::gui::EventArgs args) {
+ terragen.Generation_Cancel();
wi::platform::Exit();
});
GetGUI().AddWidget(&exitButton);
@@ -1314,7 +1454,7 @@ void EditorComponent::Update(float dt)
}
}
- if (pickMask & PICK_OBJECT && hovered.entity == INVALID_ENTITY)
+ if ((pickMask & PICK_OBJECT) && hovered.entity == INVALID_ENTITY)
{
// Object picking only when mouse button down, because it can be slow with high polycount
if (
@@ -1519,12 +1659,22 @@ void EditorComponent::Update(float dt)
for (size_t i = 0; i < count; ++i)
{
Entity entity = scene.Entity_Serialize(clipboard, seri, INVALID_ENTITY, Scene::EntitySerializeFlags::RECURSIVE | Scene::EntitySerializeFlags::KEEP_INTERNAL_ENTITY_REFERENCES);
+ const HierarchyComponent* hier = scene.hierarchy.GetComponent(entity);
+ if (hier != nullptr)
+ {
+ scene.Component_Detach(entity);
+ }
TransformComponent* transform = scene.transforms.GetComponent(entity);
if (transform != nullptr)
{
transform->translation_local = {};
//transform->MatrixTransform(hovered.orientation);
transform->Translate(hovered.position);
+ transform->UpdateTransform();
+ }
+ if (hier != nullptr)
+ {
+ scene.Component_Attach(entity, hier->parentID);
}
addedEntities.push_back(entity);
}
@@ -1729,6 +1879,8 @@ void EditorComponent::Update(float dt)
pathTraceStatisticsLabel.SetText(ss);
}
+ terragen.Generation_Update();
+
wi::profiler::EndRange(profrange);
RenderPath2D::Update(dt);
diff --git a/Editor/Editor.h b/Editor/Editor.h
index 84af0bcc3..8326fad9c 100644
--- a/Editor/Editor.h
+++ b/Editor/Editor.h
@@ -1,6 +1,7 @@
#pragma once
#include "WickedEngine.h"
#include "Translator.h"
+#include "TerrainGenerator.h"
#include "MaterialWindow.h"
#include "PostprocessWindow.h"
@@ -61,12 +62,14 @@ public:
TransformWindow transformWnd;
LayerWindow layerWnd;
NameWindow nameWnd;
+ TerrainGenerator terragen;
Editor* main = nullptr;
wi::gui::Button rendererWnd_Toggle;
wi::gui::Button postprocessWnd_Toggle;
wi::gui::Button paintToolWnd_Toggle;
+ wi::gui::Button terrainWnd_Toggle;
wi::gui::Button weatherWnd_Toggle;
wi::gui::Button objectWnd_Toggle;
wi::gui::Button meshWnd_Toggle;
diff --git a/Editor/Editor_SOURCE.vcxitems b/Editor/Editor_SOURCE.vcxitems
index 2057bf38f..ffc0758de 100644
--- a/Editor/Editor_SOURCE.vcxitems
+++ b/Editor/Editor_SOURCE.vcxitems
@@ -106,6 +106,7 @@
Create
Create
+
@@ -145,6 +146,7 @@
+
@@ -163,6 +165,18 @@
true
true
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
@@ -222,5 +236,71 @@
true
true
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ true
+ true
+
\ No newline at end of file
diff --git a/Editor/Editor_SOURCE.vcxitems.filters b/Editor/Editor_SOURCE.vcxitems.filters
index 5cd26018f..7f48632cb 100644
--- a/Editor/Editor_SOURCE.vcxitems.filters
+++ b/Editor/Editor_SOURCE.vcxitems.filters
@@ -73,6 +73,7 @@
meshoptimizer
+
@@ -107,9 +108,19 @@
meshoptimizer
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
@@ -118,6 +129,9 @@
{49c521c0-5dca-4fce-b8c5-a334d1746d98}
+
+ {9a5b997d-f07e-45c5-849a-717f15cba0ab}
+
@@ -165,5 +179,55 @@
images
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
+
+ terrain
+
\ No newline at end of file
diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp
index 9f4edc7ed..141327fad 100644
--- a/Editor/MeshWindow.cpp
+++ b/Editor/MeshWindow.cpp
@@ -11,73 +11,10 @@
using namespace wi::ecs;
using namespace wi::scene;
-struct TerraGen : public wi::gui::Window
-{
- wi::gui::Slider dimXSlider;
- wi::gui::Slider dimYSlider;
- wi::gui::Slider dimZSlider;
- wi::gui::Button heightmapButton;
-
- // heightmap texture:
- unsigned char* rgb = nullptr;
- const int channelCount = 4;
- int width = 0, height = 0;
-
- TerraGen()
- {
- wi::gui::Window::Create("TerraGen");
- SetSize(XMFLOAT2(260, 130));
-
- float xx = 20;
- float yy = 0;
- float stepstep = 25;
- float heihei = 20;
-
- dimXSlider.Create(16, 1024, 128, 1024 - 16, "X: ");
- dimXSlider.SetTooltip("Terrain mesh grid resolution on X axis");
- dimXSlider.SetSize(XMFLOAT2(200, heihei));
- dimXSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
- AddWidget(&dimXSlider);
-
- dimYSlider.Create(0, 1, 0.5f, 10000, "Y: ");
- dimYSlider.SetTooltip("Terrain mesh grid heightmap scale on Y axis");
- dimYSlider.SetSize(XMFLOAT2(200, heihei));
- dimYSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
- AddWidget(&dimYSlider);
-
- dimZSlider.Create(16, 1024, 128, 1024 - 16, "Z: ");
- dimZSlider.SetTooltip("Terrain mesh grid resolution on Z axis");
- dimZSlider.SetSize(XMFLOAT2(200, heihei));
- dimZSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
- AddWidget(&dimZSlider);
-
-
- heightmapButton.Create("Load Heightmap...");
- heightmapButton.SetTooltip("Load a heightmap texture, where the red channel corresponds to terrain height and the resolution to dimensions");
- heightmapButton.SetSize(XMFLOAT2(200, heihei));
- heightmapButton.SetPos(XMFLOAT2(xx, yy += stepstep));
-
- AddWidget(&heightmapButton);
- }
- ~TerraGen()
- {
- Cleanup();
- }
-
- void Cleanup()
- {
- if (rgb != nullptr)
- {
- stbi_image_free(rgb);
- rgb = nullptr;
- }
- }
-} terragen;
-
void MeshWindow::Create(EditorComponent* editor)
{
wi::gui::Window::Create("Mesh Window");
- SetSize(XMFLOAT2(580, 580));
+ SetSize(XMFLOAT2(580, 600));
float x = 150;
float y = 0;
@@ -225,7 +162,7 @@ void MeshWindow::Create(EditorComponent* editor)
impostorCreateButton.Create("Create Impostor");
impostorCreateButton.SetTooltip("Create an impostor image of the mesh. The mesh will be replaced by this image when far away, to render faster.");
- impostorCreateButton.SetSize(XMFLOAT2(240, hei));
+ impostorCreateButton.SetSize(XMFLOAT2(200, hei));
impostorCreateButton.SetPos(XMFLOAT2(x - 50, y += step));
impostorCreateButton.OnClick([&](wi::gui::EventArgs args) {
Scene& scene = wi::scene::GetScene();
@@ -271,7 +208,7 @@ void MeshWindow::Create(EditorComponent* editor)
flipCullingButton.Create("Flip Culling");
flipCullingButton.SetTooltip("Flip faces to reverse triangle culling order.");
- flipCullingButton.SetSize(XMFLOAT2(240, hei));
+ flipCullingButton.SetSize(XMFLOAT2(200, hei));
flipCullingButton.SetPos(XMFLOAT2(x - 50, y += step));
flipCullingButton.OnClick([&](wi::gui::EventArgs args) {
MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity);
@@ -285,7 +222,7 @@ void MeshWindow::Create(EditorComponent* editor)
flipNormalsButton.Create("Flip Normals");
flipNormalsButton.SetTooltip("Flip surface normals.");
- flipNormalsButton.SetSize(XMFLOAT2(240, hei));
+ flipNormalsButton.SetSize(XMFLOAT2(200, hei));
flipNormalsButton.SetPos(XMFLOAT2(x - 50, y += step));
flipNormalsButton.OnClick([&](wi::gui::EventArgs args) {
MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity);
@@ -299,7 +236,7 @@ void MeshWindow::Create(EditorComponent* editor)
computeNormalsSmoothButton.Create("Compute Normals [SMOOTH]");
computeNormalsSmoothButton.SetTooltip("Compute surface normals of the mesh. Resulting normals will be unique per vertex. This can reduce vertex count, but is slow.");
- computeNormalsSmoothButton.SetSize(XMFLOAT2(240, hei));
+ computeNormalsSmoothButton.SetSize(XMFLOAT2(200, hei));
computeNormalsSmoothButton.SetPos(XMFLOAT2(x - 50, y += step));
computeNormalsSmoothButton.OnClick([&](wi::gui::EventArgs args) {
MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity);
@@ -313,7 +250,7 @@ void MeshWindow::Create(EditorComponent* editor)
computeNormalsHardButton.Create("Compute Normals [HARD]");
computeNormalsHardButton.SetTooltip("Compute surface normals of the mesh. Resulting normals will be unique per face. This can increase vertex count.");
- computeNormalsHardButton.SetSize(XMFLOAT2(240, hei));
+ computeNormalsHardButton.SetSize(XMFLOAT2(200, hei));
computeNormalsHardButton.SetPos(XMFLOAT2(x - 50, y += step));
computeNormalsHardButton.OnClick([&](wi::gui::EventArgs args) {
MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity);
@@ -327,7 +264,7 @@ void MeshWindow::Create(EditorComponent* editor)
recenterButton.Create("Recenter");
recenterButton.SetTooltip("Recenter mesh to AABB center.");
- recenterButton.SetSize(XMFLOAT2(240, hei));
+ recenterButton.SetSize(XMFLOAT2(200, hei));
recenterButton.SetPos(XMFLOAT2(x - 50, y += step));
recenterButton.OnClick([&](wi::gui::EventArgs args) {
MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity);
@@ -341,7 +278,7 @@ void MeshWindow::Create(EditorComponent* editor)
recenterToBottomButton.Create("RecenterToBottom");
recenterToBottomButton.SetTooltip("Recenter mesh to AABB bottom.");
- recenterToBottomButton.SetSize(XMFLOAT2(240, hei));
+ recenterToBottomButton.SetSize(XMFLOAT2(200, hei));
recenterToBottomButton.SetPos(XMFLOAT2(x - 50, y += step));
recenterToBottomButton.OnClick([&](wi::gui::EventArgs args) {
MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity);
@@ -353,9 +290,191 @@ void MeshWindow::Create(EditorComponent* editor)
});
AddWidget(&recenterToBottomButton);
+ mergeButton.Create("Merge Selected");
+ mergeButton.SetTooltip("Merges selected objects/meshes into one.");
+ mergeButton.SetSize(XMFLOAT2(200, hei));
+ mergeButton.SetPos(XMFLOAT2(x - 50, y += step));
+ mergeButton.OnClick([=](wi::gui::EventArgs args) {
+ Scene& scene = wi::scene::GetScene();
+ MeshComponent merged_mesh;
+ bool valid_normals = false;
+ bool valid_uvset_0 = false;
+ bool valid_uvset_1 = false;
+ bool valid_atlas = false;
+ bool valid_boneindices = false;
+ bool valid_boneweights = false;
+ bool valid_colors = false;
+ bool valid_windweights = false;
+ wi::unordered_set entities_to_remove;
+ Entity prev_subset_material = INVALID_ENTITY;
+ for (auto& picked : editor->translator.selected)
+ {
+ ObjectComponent* object = scene.objects.GetComponent(picked.entity);
+ if (object == nullptr)
+ continue;
+ MeshComponent* mesh = scene.meshes.GetComponent(object->meshID);
+ if (mesh == nullptr)
+ continue;
+ const TransformComponent* transform = scene.transforms.GetComponent(picked.entity);
+ XMMATRIX W = XMLoadFloat4x4(&transform->world);
+ uint32_t vertexOffset = (uint32_t)merged_mesh.vertex_positions.size();
+ uint32_t indexOffset = (uint32_t)merged_mesh.indices.size();
+ for (auto& ind : mesh->indices)
+ {
+ merged_mesh.indices.push_back(vertexOffset + ind);
+ }
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ mesh->GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
+ {
+ const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex];
+ if (subset.materialID != prev_subset_material)
+ {
+ // new subset
+ prev_subset_material = subset.materialID;
+ merged_mesh.subsets.push_back(subset);
+ merged_mesh.subsets.back().indexOffset += indexOffset;
+ }
+ else
+ {
+ // append to previous subset
+ merged_mesh.subsets.back().indexCount += subset.indexCount;
+ }
+ }
+ for (size_t i = 0; i < mesh->vertex_positions.size(); ++i)
+ {
+ merged_mesh.vertex_positions.push_back(mesh->vertex_positions[i]);
+ XMStoreFloat3(&merged_mesh.vertex_positions.back(), XMVector3Transform(XMLoadFloat3(&merged_mesh.vertex_positions.back()), W));
+
+ if (mesh->vertex_normals.empty())
+ {
+ merged_mesh.vertex_normals.emplace_back();
+ }
+ else
+ {
+ valid_normals = true;
+ merged_mesh.vertex_normals.push_back(mesh->vertex_normals[i]);
+ XMStoreFloat3(&merged_mesh.vertex_normals.back(), XMVector3TransformNormal(XMLoadFloat3(&merged_mesh.vertex_normals.back()), W));
+ }
+
+ if (mesh->vertex_uvset_0.empty())
+ {
+ merged_mesh.vertex_uvset_0.emplace_back();
+ }
+ else
+ {
+ valid_uvset_0 = true;
+ merged_mesh.vertex_uvset_0.push_back(mesh->vertex_uvset_0[i]);
+ }
+
+ if (mesh->vertex_uvset_1.empty())
+ {
+ merged_mesh.vertex_uvset_1.emplace_back();
+ }
+ else
+ {
+ valid_uvset_1 = true;
+ merged_mesh.vertex_uvset_1.push_back(mesh->vertex_uvset_1[i]);
+ }
+
+ if (mesh->vertex_atlas.empty())
+ {
+ merged_mesh.vertex_atlas.emplace_back();
+ }
+ else
+ {
+ valid_atlas = true;
+ merged_mesh.vertex_atlas.push_back(mesh->vertex_atlas[i]);
+ }
+
+ if (mesh->vertex_boneindices.empty())
+ {
+ merged_mesh.vertex_boneindices.emplace_back();
+ }
+ else
+ {
+ valid_boneindices = true;
+ merged_mesh.vertex_boneindices.push_back(mesh->vertex_boneindices[i]);
+ }
+
+ if (mesh->vertex_boneweights.empty())
+ {
+ merged_mesh.vertex_boneweights.emplace_back();
+ }
+ else
+ {
+ valid_boneweights = true;
+ merged_mesh.vertex_boneweights.push_back(mesh->vertex_boneweights[i]);
+ }
+
+ if (mesh->vertex_colors.empty())
+ {
+ merged_mesh.vertex_colors.push_back(~0u);
+ }
+ else
+ {
+ valid_colors = true;
+ merged_mesh.vertex_colors.push_back(mesh->vertex_colors[i]);
+ }
+
+ if (mesh->vertex_windweights.empty())
+ {
+ merged_mesh.vertex_windweights.emplace_back();
+ }
+ else
+ {
+ valid_windweights = true;
+ merged_mesh.vertex_windweights.push_back(mesh->vertex_windweights[i]);
+ }
+ }
+ if (merged_mesh.armatureID == INVALID_ENTITY)
+ {
+ merged_mesh.armatureID = mesh->armatureID;
+ }
+ entities_to_remove.insert(object->meshID);
+ entities_to_remove.insert(picked.entity);
+ }
+
+ if (!merged_mesh.vertex_positions.empty())
+ {
+ if (!valid_normals)
+ merged_mesh.vertex_normals.clear();
+ if (!valid_uvset_0)
+ merged_mesh.vertex_uvset_0.clear();
+ if (!valid_uvset_1)
+ merged_mesh.vertex_uvset_1.clear();
+ if (!valid_atlas)
+ merged_mesh.vertex_atlas.clear();
+ if (!valid_boneindices)
+ merged_mesh.vertex_boneindices.clear();
+ if (!valid_boneweights)
+ merged_mesh.vertex_boneweights.clear();
+ if (!valid_colors)
+ merged_mesh.vertex_colors.clear();
+ if (!valid_windweights)
+ merged_mesh.vertex_windweights.clear();
+
+ Entity merged_object_entity = scene.Entity_CreateObject("mergedObject");
+ Entity merged_mesh_entity = scene.Entity_CreateMesh("mergedMesh");
+ ObjectComponent* object = scene.objects.GetComponent(merged_object_entity);
+ object->meshID = merged_mesh_entity;
+ MeshComponent* mesh = scene.meshes.GetComponent(merged_mesh_entity);
+ *mesh = std::move(merged_mesh);
+ mesh->CreateRenderData();
+ }
+
+ for (auto& x : entities_to_remove)
+ {
+ scene.Entity_Remove(x);
+ }
+
+ });
+ AddWidget(&mergeButton);
+
optimizeButton.Create("Optimize");
optimizeButton.SetTooltip("Run the meshoptimizer library.");
- optimizeButton.SetSize(XMFLOAT2(240, hei));
+ optimizeButton.SetSize(XMFLOAT2(200, hei));
optimizeButton.SetPos(XMFLOAT2(x - 50, y += step));
optimizeButton.OnClick([&](wi::gui::EventArgs args) {
MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity);
@@ -473,147 +592,6 @@ void MeshWindow::Create(EditorComponent* editor)
terrainMat3Combo.SetTooltip("Choose a sub terrain blend material. (ALPHA vertex color mask)");
AddWidget(&terrainMat3Combo);
- terrainGenButton.Create("Generate Terrain...");
- terrainGenButton.SetTooltip("Generate terrain meshes.");
- terrainGenButton.SetSize(XMFLOAT2(200, hei));
- terrainGenButton.SetPos(XMFLOAT2(x + 180, y += step));
- terrainGenButton.OnClick([=](wi::gui::EventArgs args) {
-
- terragen.Cleanup();
- terragen.SetVisible(true);
-
- editor->GetGUI().RemoveWidget(&terragen);
- editor->GetGUI().AddWidget(&terragen);
-
- terragen.SetPos(XMFLOAT2(
- terrainGenButton.translation.x + terrainGenButton.scale.x + 10,
- terrainGenButton.translation.y)
- );
-
- Scene& scene = wi::scene::GetScene();
- Entity entity = scene.Entity_CreateObject("editorTerrain");
- ObjectComponent& object = *scene.objects.GetComponent(entity);
- object.meshID = scene.Entity_CreateMesh("terrainMesh");
- MeshComponent* mesh = scene.meshes.GetComponent(object.meshID);
- mesh->SetTerrain(true);
- mesh->subsets.emplace_back();
- mesh->subsets.back().materialID = scene.Entity_CreateMaterial("terrainMaterial");
- mesh->subsets.back().indexOffset = 0;
- MaterialComponent* material = scene.materials.GetComponent(mesh->subsets.back().materialID);
- material->SetUseVertexColors(true);
-
- auto generate_mesh = [=] (int width, int height, unsigned char* rgb = nullptr,
- int channelCount = 4, float heightmap_scale = 1)
- {
- mesh->vertex_positions.resize(width * height);
- mesh->vertex_normals.resize(width * height);
- mesh->vertex_colors.resize(width * height);
- mesh->vertex_uvset_0.resize(width* height);
- mesh->vertex_uvset_1.resize(width* height);
- mesh->vertex_atlas.resize(width* height);
- for (int i = 0; i < width; ++i)
- {
- for (int j = 0; j < height; ++j)
- {
- size_t index = size_t(i + j * width);
- mesh->vertex_positions[index] = XMFLOAT3((float)i - (float)width * 0.5f, 0, (float)j - (float)height * 0.5f);
- if (rgb != nullptr)
- mesh->vertex_positions[index].y = ((float)rgb[index * channelCount] - 127.0f) * heightmap_scale;
- mesh->vertex_colors[index] = wi::Color::Red().rgba;
- XMFLOAT2 uv = XMFLOAT2((float)i / (float)width, (float)j / (float)height);
- mesh->vertex_uvset_0[index] = uv;
- mesh->vertex_uvset_1[index] = uv;
- mesh->vertex_atlas[index] = uv;
- }
- }
- mesh->indices.resize((width - 1) * (height - 1) * 6);
- size_t counter = 0;
- for (int x = 0; x < width - 1; x++)
- {
- for (int y = 0; y < height - 1; y++)
- {
- int lowerLeft = x + y * width;
- int lowerRight = (x + 1) + y * width;
- int topLeft = x + (y + 1) * width;
- int topRight = (x + 1) + (y + 1) * width;
-
- mesh->indices[counter++] = topLeft;
- mesh->indices[counter++] = lowerLeft;
- mesh->indices[counter++] = lowerRight;
-
- mesh->indices[counter++] = topLeft;
- mesh->indices[counter++] = lowerRight;
- mesh->indices[counter++] = topRight;
- }
- }
- mesh->subsets.back().indexCount = (uint32_t)mesh->indices.size();
-
- mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_SMOOTH_FAST);
- };
- generate_mesh(128, 128);
-
- wi::Archive& archive = editor->AdvanceHistory();
- archive << EditorComponent::HISTORYOP_ADD;
- editor->RecordSelection(archive);
-
- editor->ClearSelected();
- wi::scene::PickResult pick;
- pick.entity = entity;
- pick.subsetIndex = 0;
- editor->AddSelected(pick);
-
- editor->RecordSelection(archive);
- editor->RecordAddedEntity(archive, entity);
-
- SetEntity(object.meshID, pick.subsetIndex);
-
- editor->RefreshSceneGraphView();
-
-
-
-
-
- terragen.dimXSlider.OnSlide([=](wi::gui::EventArgs args) {
- terragen.width = (int)terragen.dimXSlider.GetValue();
- terragen.height = (int)terragen.dimZSlider.GetValue();
- generate_mesh(terragen.width, terragen.height);
- });
- terragen.dimZSlider.OnSlide([=](wi::gui::EventArgs args) {
- terragen.width = (int)terragen.dimXSlider.GetValue();
- terragen.height = (int)terragen.dimZSlider.GetValue();
- generate_mesh(terragen.width, terragen.height);
- });
- terragen.dimYSlider.OnSlide([=](wi::gui::EventArgs args) {
- terragen.width = (int)terragen.dimXSlider.GetValue();
- terragen.height = (int)terragen.dimZSlider.GetValue();
- generate_mesh(terragen.width, terragen.height, terragen.rgb, terragen.channelCount, args.fValue);
- });
-
- terragen.heightmapButton.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) {
- if (terragen.rgb != nullptr)
- {
- stbi_image_free(terragen.rgb);
- terragen.rgb = nullptr;
- }
-
- int bpp;
- terragen.rgb = stbi_load(fileName.c_str(), &terragen.width, &terragen.height, &bpp, terragen.channelCount);
-
- generate_mesh(terragen.width, terragen.height, terragen.rgb, terragen.channelCount, terragen.dimYSlider.GetValue());
- });
- });
- });
-
- });
- AddWidget(&terrainGenButton);
-
morphTargetCombo.Create("Morph Target:");
morphTargetCombo.SetSize(XMFLOAT2(100, hei));
@@ -642,6 +620,142 @@ void MeshWindow::Create(EditorComponent* editor)
});
AddWidget(&morphTargetSlider);
+ lodgenButton.Create("LOD Gen");
+ lodgenButton.SetTooltip("Generate LODs (levels of detail).");
+ lodgenButton.SetSize(XMFLOAT2(200, hei));
+ lodgenButton.SetPos(XMFLOAT2(x + 180, y += step));
+ lodgenButton.OnClick([&](wi::gui::EventArgs args) {
+ MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity);
+ if (mesh != nullptr)
+ {
+ if (mesh->subsets_per_lod == 0)
+ {
+ // if there were no lods before, record the subset count without lods:
+ mesh->subsets_per_lod = (uint32_t)mesh->subsets.size();
+ }
+
+ // https://github.com/zeux/meshoptimizer/blob/bedaaaf6e710d3b42d49260ca738c15d171b1a8f/demo/main.cpp
+ size_t index_count = mesh->indices.size();
+ size_t vertex_count = mesh->vertex_positions.size();
+
+ const size_t lod_count = (size_t)lodCountSlider.GetValue();
+ struct LOD
+ {
+ struct Subset
+ {
+ wi::vector indices;
+ };
+ wi::vector subsets;
+ };
+ wi::vector lods(lod_count);
+
+ const float target_error = lodErrorSlider.GetValue();
+
+ for (size_t i = 0; i < lod_count; ++i)
+ {
+ lods[i].subsets.resize(mesh->subsets_per_lod);
+ for (uint32_t subsetIndex = 0; subsetIndex < mesh->subsets_per_lod; ++subsetIndex)
+ {
+ const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex];
+ lods[i].subsets[subsetIndex].indices.resize(subset.indexCount);
+ for (uint32_t ind = 0; ind < subset.indexCount; ++ind)
+ {
+ lods[i].subsets[subsetIndex].indices[ind] = mesh->indices[subset.indexOffset + ind];
+ }
+ }
+ }
+
+ for (uint32_t subsetIndex = 0; subsetIndex < mesh->subsets_per_lod; ++subsetIndex)
+ {
+ const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex];
+
+ float threshold = wi::math::Lerp(0, 0.9f, wi::math::saturate(lodQualitySlider.GetValue()));
+ for (size_t i = 1; i < lod_count; ++i)
+ {
+ wi::vector& lod = lods[i].subsets[subsetIndex].indices;
+
+ size_t target_index_count = size_t(mesh->indices.size() * threshold) / 3 * 3;
+
+ // we can simplify all the way from base level or from the last result
+ // simplifying from the base level sometimes produces better results, but simplifying from last level is faster
+ //const wi::vector& source = lods[0].subsets[subsetIndex].indices;
+ const wi::vector& source = lods[i - 1].subsets[subsetIndex].indices;
+
+ if (source.size() < target_index_count)
+ target_index_count = source.size();
+
+ lod.resize(source.size());
+ if (lodSloppyCheckBox.GetCheck())
+ {
+ lod.resize(meshopt_simplifySloppy(&lod[0], &source[0], source.size(), &mesh->vertex_positions[0].x, mesh->vertex_positions.size(), sizeof(XMFLOAT3), target_index_count, target_error));
+ }
+ else
+ {
+ lod.resize(meshopt_simplify(&lod[0], &source[0], source.size(), &mesh->vertex_positions[0].x, mesh->vertex_positions.size(), sizeof(XMFLOAT3), target_index_count, target_error));
+ }
+
+ threshold *= threshold;
+ }
+
+ // optimize each individual LOD for vertex cache & overdraw
+ for (size_t i = 0; i < lod_count; ++i)
+ {
+ wi::vector& lod = lods[i].subsets[subsetIndex].indices;
+
+ meshopt_optimizeVertexCache(&lod[0], &lod[0], lod.size(), mesh->vertex_positions.size());
+ meshopt_optimizeOverdraw(&lod[0], &lod[0], lod.size(), &mesh->vertex_positions[0].x, mesh->vertex_positions.size(), sizeof(XMFLOAT3), 1.0f);
+ }
+ }
+
+ mesh->indices.clear();
+ wi::vector subsets;
+ for (size_t i = 0; i < lod_count; ++i)
+ {
+ for (uint32_t subsetIndex = 0; subsetIndex < mesh->subsets_per_lod; ++subsetIndex)
+ {
+ const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex];
+ subsets.emplace_back();
+ subsets.back() = subset;
+ subsets.back().indexOffset = (uint32_t)mesh->indices.size();
+ subsets.back().indexCount = (uint32_t)lods[i].subsets[subsetIndex].indices.size();
+ for (auto& x : lods[i].subsets[subsetIndex].indices)
+ {
+ mesh->indices.push_back(x);
+ }
+ }
+ }
+ mesh->subsets = subsets;
+
+ mesh->CreateRenderData();
+ SetEntity(entity, subset);
+ }
+ });
+ AddWidget(&lodgenButton);
+
+ lodCountSlider.Create(2, 10, 6, 8, "LOD Count: ");
+ lodCountSlider.SetTooltip("This is how many levels of detail will be created.");
+ lodCountSlider.SetSize(XMFLOAT2(100, hei));
+ lodCountSlider.SetPos(XMFLOAT2(x + 280, y += step));
+ AddWidget(&lodCountSlider);
+
+ lodQualitySlider.Create(0.1f, 1.0f, 0.5f, 10000, "LOD Quality: ");
+ lodQualitySlider.SetTooltip("Lower values will make LODs more agressively simplified.");
+ lodQualitySlider.SetSize(XMFLOAT2(100, hei));
+ lodQualitySlider.SetPos(XMFLOAT2(x + 280, y += step));
+ AddWidget(&lodQualitySlider);
+
+ lodErrorSlider.Create(0.01f, 0.1f, 0.03f, 10000, "LOD Error: ");
+ lodErrorSlider.SetTooltip("Lower values will make more precise levels of detail.");
+ lodErrorSlider.SetSize(XMFLOAT2(100, hei));
+ lodErrorSlider.SetPos(XMFLOAT2(x + 280, y += step));
+ AddWidget(&lodErrorSlider);
+
+ lodSloppyCheckBox.Create("Sloppy LOD: ");
+ lodSloppyCheckBox.SetTooltip("Use the sloppy simplification algorithm, which is faster but doesn't preserve shape well.");
+ lodSloppyCheckBox.SetSize(XMFLOAT2(hei, hei));
+ lodSloppyCheckBox.SetPos(XMFLOAT2(x + 280, y += step));
+ AddWidget(&lodSloppyCheckBox);
+
Translate(XMFLOAT3((float)editor->GetLogicalWidth() - 1000, 80, 0));
SetVisible(false);
@@ -659,12 +773,6 @@ void MeshWindow::SetEntity(Entity entity, int subset)
const MeshComponent* mesh = scene.meshes.GetComponent(entity);
- if (mesh == nullptr || !mesh->IsTerrain())
- {
- terragen.Cleanup();
- terragen.SetVisible(false);
- }
-
if (mesh != nullptr)
{
const NameComponent& name = *scene.names.GetComponent(entity);
@@ -673,7 +781,7 @@ void MeshWindow::SetEntity(Entity entity, int subset)
ss += "Mesh name: " + name.name + "\n";
ss += "Vertex count: " + std::to_string(mesh->vertex_positions.size()) + "\n";
ss += "Index count: " + std::to_string(mesh->indices.size()) + "\n";
- ss += "Subset count: " + std::to_string(mesh->subsets.size()) + "\n";
+ ss += "Subset count: " + std::to_string(mesh->subsets.size()) + " (" + std::to_string(mesh->GetLODCount()) + " LODs)\n";
ss += "GPU memory: " + std::to_string((mesh->generalBuffer.GetDesc().size + mesh->streamoutBuffer.GetDesc().size) / 1024.0f / 1024.0f) + " MB\n";
ss += "\nVertex buffers: ";
if (mesh->vb_pos_nor_wind.IsValid()) ss += "position; ";
@@ -697,7 +805,7 @@ void MeshWindow::SetEntity(Entity entity, int subset)
}
if (subset >= 0)
{
- subsetComboBox.SetSelected(subset);
+ subsetComboBox.SetSelectedWithoutCallback(subset);
}
subsetMaterialComboBox.ClearItems();
@@ -766,6 +874,17 @@ void MeshWindow::SetEntity(Entity entity, int subset)
morphTargetCombo.SetSelected(selected);
}
SetEnabled(true);
+
+ if (mesh->targets.empty())
+ {
+ morphTargetCombo.SetEnabled(false);
+ morphTargetSlider.SetEnabled(false);
+ }
+ else
+ {
+ morphTargetCombo.SetEnabled(true);
+ morphTargetSlider.SetEnabled(true);
+ }
}
else
{
@@ -773,5 +892,5 @@ void MeshWindow::SetEntity(Entity entity, int subset)
SetEnabled(false);
}
- terrainGenButton.SetEnabled(true);
+ mergeButton.SetEnabled(true);
}
diff --git a/Editor/MeshWindow.h b/Editor/MeshWindow.h
index 4d2b52830..07707aa80 100644
--- a/Editor/MeshWindow.h
+++ b/Editor/MeshWindow.h
@@ -5,6 +5,7 @@ class EditorComponent;
class MeshWindow : public wi::gui::Window
{
+ bool terragen_initialized = false;
public:
void Create(EditorComponent* editor);
@@ -29,15 +30,21 @@ public:
wi::gui::Button computeNormalsHardButton;
wi::gui::Button recenterButton;
wi::gui::Button recenterToBottomButton;
+ wi::gui::Button mergeButton;
wi::gui::Button optimizeButton;
wi::gui::CheckBox terrainCheckBox;
wi::gui::ComboBox terrainMat1Combo;
wi::gui::ComboBox terrainMat2Combo;
wi::gui::ComboBox terrainMat3Combo;
- wi::gui::Button terrainGenButton;
wi::gui::ComboBox morphTargetCombo;
wi::gui::Slider morphTargetSlider;
+
+ wi::gui::Button lodgenButton;
+ wi::gui::Slider lodCountSlider;
+ wi::gui::Slider lodQualitySlider;
+ wi::gui::Slider lodErrorSlider;
+ wi::gui::CheckBox lodSloppyCheckBox;
};
diff --git a/Editor/ObjectWindow.cpp b/Editor/ObjectWindow.cpp
index 145943f15..fd4738347 100644
--- a/Editor/ObjectWindow.cpp
+++ b/Editor/ObjectWindow.cpp
@@ -261,7 +261,7 @@ void ObjectWindow::Create(EditorComponent* editor)
this->editor = editor;
wi::gui::Window::Create("Object Window");
- SetSize(XMFLOAT2(660, 500));
+ SetSize(XMFLOAT2(660, 520));
float x = 200;
float y = 0;
@@ -328,6 +328,19 @@ void ObjectWindow::Create(EditorComponent* editor)
});
AddWidget(&cascadeMaskSlider);
+ lodSlider.Create(0.001f, 10, 1, 10000, "LOD Multiplier: ");
+ lodSlider.SetTooltip("How much the distance to camera will affect LOD selection. (If the mesh has lods)");
+ lodSlider.SetSize(XMFLOAT2(100, hei));
+ lodSlider.SetPos(XMFLOAT2(x, y += step));
+ lodSlider.OnSlide([&](wi::gui::EventArgs args) {
+ ObjectComponent* object = wi::scene::GetScene().objects.GetComponent(entity);
+ if (object != nullptr)
+ {
+ object->lod_distance_multiplier = args.fValue;
+ }
+ });
+ AddWidget(&lodSlider);
+
y += step;
physicsLabel.Create("PHYSICSLABEL");
@@ -819,6 +832,7 @@ void ObjectWindow::SetEntity(Entity entity)
shadowCheckBox.SetCheck(object->IsCastingShadow());
cascadeMaskSlider.SetValue((float)object->cascadeMask);
ditherSlider.SetValue(object->GetTransparency());
+ lodSlider.SetValue(object->lod_distance_multiplier);
switch (colorComboBox.GetSelected())
{
diff --git a/Editor/ObjectWindow.h b/Editor/ObjectWindow.h
index 206798e74..55c87a518 100644
--- a/Editor/ObjectWindow.h
+++ b/Editor/ObjectWindow.h
@@ -17,6 +17,7 @@ public:
wi::gui::CheckBox shadowCheckBox;
wi::gui::Slider ditherSlider;
wi::gui::Slider cascadeMaskSlider;
+ wi::gui::Slider lodSlider;
wi::gui::ComboBox colorComboBox;
wi::gui::ColorPicker colorPicker;
diff --git a/Editor/TerrainGenerator.cpp b/Editor/TerrainGenerator.cpp
new file mode 100644
index 000000000..30744d5fb
--- /dev/null
+++ b/Editor/TerrainGenerator.cpp
@@ -0,0 +1,947 @@
+#include "stdafx.h"
+#include "TerrainGenerator.h"
+
+#include "Utility/stb_image.h"
+
+using namespace wi::ecs;
+using namespace wi::scene;
+
+enum PRESET
+{
+ PRESET_HILLS,
+ PRESET_ISLANDS,
+ PRESET_MOUNTAINS,
+ PRESET_ARCTIC,
+};
+
+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 < width - 1; x++)
+ {
+ for (int z = 0; z < width - 1; z++)
+ {
+ int lowerLeft = x + z * width;
+ int lowerRight = (x + 1) + z * width;
+ int topLeft = x + (z + 1) * width;
+ int topRight = (x + 1) + (z + 1) * 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 < width - 2; x += step)
+ {
+ for (int z = 1; z < width - 2; z += step)
+ {
+ int lowerLeft = x + z * width;
+ int lowerRight = (x + step) + z * width;
+ int topLeft = x + (z + step) * width;
+ int topRight = (x + step) + (z + step) * 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 < width - 1; ++x)
+ {
+ const int z = 0;
+ int current = x + z * width;
+ int neighbor = x + 1 + z * width;
+ int connection = 1 + ((x + (step + 1) / 2 - 1) / step) * step + (z + 1) * 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) * width;
+
+ indices.push_back(current);
+ indices.push_back(connection);
+ indices.push_back(connection1);
+ }
+ }
+ // top border:
+ for (int x = 0; x < width - 1; ++x)
+ {
+ const int z = width - 1;
+ int current = x + z * width;
+ int neighbor = x + 1 + z * width;
+ int connection = 1 + ((x + (step + 1) / 2 - 1) / step) * step + (z - 1) * 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) * width;
+
+ indices.push_back(current);
+ indices.push_back(connection1);
+ indices.push_back(connection);
+ }
+ }
+ // left border:
+ for (int z = 0; z < width - 1; ++z)
+ {
+ const int x = 0;
+ int current = x + z * width;
+ int neighbor = x + (z + 1) * width;
+ int connection = x + 1 + (((z + (step + 1) / 2 - 1) / step) * step + 1) * 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) * width;
+
+ indices.push_back(current);
+ indices.push_back(connection1);
+ indices.push_back(connection);
+ }
+ }
+ // right border:
+ for (int z = 0; z < width - 1; ++z)
+ {
+ const int x = width - 1;
+ int current = x + z * width;
+ int neighbor = x + (z + 1) * width;
+ int connection = x - 1 + (((z + (step + 1) / 2 - 1) / step) * step + 1) * 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) * width;
+
+ indices.push_back(current);
+ indices.push_back(connection);
+ indices.push_back(connection1);
+ }
+ }
+ }
+
+ lods[lod].indexCount = (uint32_t)indices.size() - lods[lod].indexOffset;
+ }
+
+ RemoveWidgets();
+ ClearTransform();
+
+ wi::gui::Window::Create("TerraGen (Preview version)");
+ SetSize(XMFLOAT2(410, 560));
+
+ float xx = 150;
+ float yy = 0;
+ float stepstep = 25;
+ float heihei = 20;
+
+ centerToCamCheckBox.Create("Center to Cam: ");
+ centerToCamCheckBox.SetTooltip("Automatically generate chunks around camera. This sets the center chunk to camera position.");
+ centerToCamCheckBox.SetSize(XMFLOAT2(heihei, heihei));
+ centerToCamCheckBox.SetPos(XMFLOAT2(xx, yy += stepstep));
+ 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(heihei, heihei));
+ removalCheckBox.SetPos(XMFLOAT2(xx + 100, yy));
+ removalCheckBox.SetCheck(true);
+ AddWidget(&removalCheckBox);
+
+ lodSlider.Create(0.0001f, 0.01f, 0.005f, 10000, "LOD Distance: ");
+ lodSlider.SetTooltip("Set the LOD (Level Of Detail) distance multiplier.\nLow values increase LOD detail in distance");
+ lodSlider.SetSize(XMFLOAT2(200, heihei));
+ lodSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ 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);
+
+ 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(200, heihei));
+ generationSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&generationSlider);
+
+ presetCombo.Create("Preset: ");
+ presetCombo.SetTooltip("Select a terrain preset");
+ presetCombo.SetSize(XMFLOAT2(200, heihei));
+ presetCombo.SetPos(XMFLOAT2(xx, yy += stepstep));
+ 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) {
+ switch (args.userdata)
+ {
+ default:
+ case PRESET_HILLS:
+ seedSlider.SetValue(5333);
+ bottomLevelSlider.SetValue(-60);
+ topLevelSlider.SetValue(380);
+ perlinBlendSlider.SetValue(0.5f);
+ perlinFrequencySlider.SetValue(0.0008f);
+ perlinOctavesSlider.SetValue(6);
+ voronoiBlendSlider.SetValue(0.5f);
+ voronoiFrequencySlider.SetValue(0.001f);
+ voronoiFadeSlider.SetValue(2.59f);
+ voronoiShapeSlider.SetValue(0.7f);
+ voronoiFalloffSlider.SetValue(6);
+ voronoiPerturbationSlider.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);
+ perlinBlendSlider.SetValue(0.5f);
+ perlinFrequencySlider.SetValue(0.000991f);
+ perlinOctavesSlider.SetValue(6);
+ voronoiBlendSlider.SetValue(0.5f);
+ voronoiFrequencySlider.SetValue(0.000317f);
+ voronoiFadeSlider.SetValue(8.2f);
+ voronoiShapeSlider.SetValue(0.126f);
+ voronoiFalloffSlider.SetValue(1.392f);
+ voronoiPerturbationSlider.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);
+ perlinBlendSlider.SetValue(0.5f);
+ perlinFrequencySlider.SetValue(0.00279f);
+ perlinOctavesSlider.SetValue(8);
+ voronoiBlendSlider.SetValue(0.5f);
+ voronoiFrequencySlider.SetValue(0.000496f);
+ voronoiFadeSlider.SetValue(5.2f);
+ voronoiShapeSlider.SetValue(0.412f);
+ voronoiFalloffSlider.SetValue(1.456f);
+ voronoiPerturbationSlider.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);
+ perlinBlendSlider.SetValue(1);
+ perlinFrequencySlider.SetValue(0.002f);
+ perlinOctavesSlider.SetValue(4);
+ voronoiBlendSlider.SetValue(1);
+ voronoiFrequencySlider.SetValue(0.004f);
+ voronoiFadeSlider.SetValue(1.8f);
+ voronoiShapeSlider.SetValue(0.518f);
+ voronoiFalloffSlider.SetValue(0.2f);
+ voronoiPerturbationSlider.SetValue(0.298f);
+ region1Slider.SetValue(8);
+ region2Slider.SetValue(8);
+ region3Slider.SetValue(0);
+ break;
+ }
+ Generation_Restart();
+ });
+ AddWidget(&presetCombo);
+
+ seedSlider.Create(1, 12345, 3926, 12344, "Seed: ");
+ seedSlider.SetTooltip("Seed for terrain randomness");
+ seedSlider.SetSize(XMFLOAT2(200, heihei));
+ seedSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&seedSlider);
+
+ bottomLevelSlider.Create(-100, 0, -60, 10000, "Bottom Level: ");
+ bottomLevelSlider.SetTooltip("Terrain mesh grid lowest level");
+ bottomLevelSlider.SetSize(XMFLOAT2(200, heihei));
+ bottomLevelSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&bottomLevelSlider);
+
+ topLevelSlider.Create(0, 5000, 380, 10000, "Top Level: ");
+ topLevelSlider.SetTooltip("Terrain mesh grid topmost level");
+ topLevelSlider.SetSize(XMFLOAT2(200, heihei));
+ topLevelSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&topLevelSlider);
+
+ perlinBlendSlider.Create(0, 1, 0.5f, 10000, "Perlin Blend: ");
+ perlinBlendSlider.SetTooltip("Amount of perlin noise to use");
+ perlinBlendSlider.SetSize(XMFLOAT2(200, heihei));
+ perlinBlendSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&perlinBlendSlider);
+
+ perlinFrequencySlider.Create(0.0001f, 0.01f, 0.0008f, 10000, "Perlin Frequency: ");
+ perlinFrequencySlider.SetTooltip("Frequency for the perlin noise");
+ perlinFrequencySlider.SetSize(XMFLOAT2(200, heihei));
+ perlinFrequencySlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&perlinFrequencySlider);
+
+ perlinOctavesSlider.Create(1, 8, 6, 7, "Perlin Octaves: ");
+ perlinOctavesSlider.SetTooltip("Octave count for the perlin noise");
+ perlinOctavesSlider.SetSize(XMFLOAT2(200, heihei));
+ perlinOctavesSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&perlinOctavesSlider);
+
+ voronoiBlendSlider.Create(0, 1, 0.5f, 10000, "Voronoi Blend: ");
+ voronoiBlendSlider.SetTooltip("Amount of voronoi to use for elevation");
+ voronoiBlendSlider.SetSize(XMFLOAT2(200, heihei));
+ voronoiBlendSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&voronoiBlendSlider);
+
+ voronoiFrequencySlider.Create(0.0001f, 0.01f, 0.001f, 10000, "Voronoi Frequency: ");
+ voronoiFrequencySlider.SetTooltip("Voronoi can create distinctly elevated areas, the more cells there are, smaller the consecutive areas");
+ voronoiFrequencySlider.SetSize(XMFLOAT2(200, heihei));
+ voronoiFrequencySlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&voronoiFrequencySlider);
+
+ voronoiFadeSlider.Create(0, 100, 2.59f, 10000, "Voronoi Fade: ");
+ voronoiFadeSlider.SetTooltip("Fade out voronoi regions by distance from cell's center");
+ voronoiFadeSlider.SetSize(XMFLOAT2(200, heihei));
+ voronoiFadeSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&voronoiFadeSlider);
+
+ voronoiShapeSlider.Create(0, 1, 0.7f, 10000, "Voronoi Shape: ");
+ voronoiShapeSlider.SetTooltip("How much the voronoi shape will be kept");
+ voronoiShapeSlider.SetSize(XMFLOAT2(200, heihei));
+ voronoiShapeSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&voronoiShapeSlider);
+
+ voronoiFalloffSlider.Create(0, 8, 6, 10000, "Voronoi Falloff: ");
+ voronoiFalloffSlider.SetTooltip("Controls the falloff of the voronoi distance fade effect");
+ voronoiFalloffSlider.SetSize(XMFLOAT2(200, heihei));
+ voronoiFalloffSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&voronoiFalloffSlider);
+
+ voronoiPerturbationSlider.Create(0, 1, 0.1f, 10000, "Voronoi Perturbation: ");
+ voronoiPerturbationSlider.SetTooltip("Controls the random look of voronoi region edges");
+ voronoiPerturbationSlider.SetSize(XMFLOAT2(200, heihei));
+ voronoiPerturbationSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&voronoiPerturbationSlider);
+
+
+ heightmapButton.Create("Load Heightmap...");
+ heightmapButton.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.");
+ heightmapButton.SetSize(XMFLOAT2(200, heihei));
+ heightmapButton.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&heightmapButton);
+
+ heightmapBlendSlider.Create(0, 1, 1, 10000, "Heightmap Blend: ");
+ heightmapBlendSlider.SetTooltip("Amount of displacement coming from the heightmap texture");
+ heightmapBlendSlider.SetSize(XMFLOAT2(200, heihei));
+ heightmapBlendSlider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(&heightmapBlendSlider);
+
+ region1Slider.Create(0, 8, 1, 10000, "Slope Region: ");
+ region1Slider.SetTooltip("The region's falloff power");
+ region1Slider.SetSize(XMFLOAT2(200, heihei));
+ region1Slider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(®ion1Slider);
+
+ region2Slider.Create(0, 8, 2, 10000, "Low Altitude Region: ");
+ region2Slider.SetTooltip("The region's falloff power");
+ region2Slider.SetSize(XMFLOAT2(200, heihei));
+ region2Slider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(®ion2Slider);
+
+ region3Slider.Create(0, 8, 8, 10000, "High Altitude Region: ");
+ region3Slider.SetTooltip("The region's falloff power");
+ region3Slider.SetSize(XMFLOAT2(200, heihei));
+ region3Slider.SetPos(XMFLOAT2(xx, yy += stepstep));
+ AddWidget(®ion3Slider);
+
+
+ auto generate_callback = [=](wi::gui::EventArgs args) {
+ Generation_Restart();
+ };
+ seedSlider.OnSlide(generate_callback);
+ bottomLevelSlider.OnSlide(generate_callback);
+ topLevelSlider.OnSlide(generate_callback);
+ perlinFrequencySlider.OnSlide(generate_callback);
+ perlinBlendSlider.OnSlide(generate_callback);
+ perlinOctavesSlider.OnSlide(generate_callback);
+ voronoiBlendSlider.OnSlide(generate_callback);
+ voronoiFrequencySlider.OnSlide(generate_callback);
+ voronoiFadeSlider.OnSlide(generate_callback);
+ voronoiShapeSlider.OnSlide(generate_callback);
+ voronoiFalloffSlider.OnSlide(generate_callback);
+ voronoiPerturbationSlider.OnSlide(generate_callback);
+ heightmapBlendSlider.OnSlide(generate_callback);
+ region1Slider.OnSlide(generate_callback);
+ region2Slider.OnSlide(generate_callback);
+ region3Slider.OnSlide(generate_callback);
+
+ heightmapButton.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) {
+
+ heightmap = {};
+ int bpp = 0;
+ stbi_uc* rgba = stbi_load(fileName.c_str(), &heightmap.width, &heightmap.height, &bpp, 1);
+ if (rgba != nullptr)
+ {
+ heightmap.data.resize(heightmap.width * heightmap.height);
+ for (int i = 0; i < heightmap.width * heightmap.height; ++i)
+ {
+ heightmap.data[i] = rgba[i];
+ }
+ stbi_image_free(rgba);
+ Generation_Restart();
+ }
+ });
+ });
+ });
+
+ heightmap = {};
+
+ SetPos(XMFLOAT2(50, 110));
+ SetVisible(false);
+ SetEnabled(true);
+
+ presetCombo.SetSelectedByUserdata(PRESET_HILLS);
+}
+
+void TerrainGenerator::Generation_Restart()
+{
+ Generation_Cancel();
+ generation_scene.Clear();
+ generation_entities.clear();
+
+ // If these already exist, save them before recreating:
+ // (This is helpful if eg: someone edits them in the editor and then regenerates the terrain)
+ if (materialEntity_Base != INVALID_ENTITY)
+ {
+ MaterialComponent* material = scene->materials.GetComponent(materialEntity_Base);
+ if (material != nullptr)
+ {
+ material_Base = *material;
+ }
+ }
+ if (materialEntity_Slope != INVALID_ENTITY)
+ {
+ MaterialComponent* material = scene->materials.GetComponent(materialEntity_Slope);
+ if (material != nullptr)
+ {
+ material_Slope = *material;
+ }
+ }
+ if (materialEntity_LowAltitude != INVALID_ENTITY)
+ {
+ MaterialComponent* material = scene->materials.GetComponent(materialEntity_LowAltitude);
+ if (material != nullptr)
+ {
+ material_LowAltitude = *material;
+ }
+ }
+ if (materialEntity_HighAltitude != INVALID_ENTITY)
+ {
+ MaterialComponent* material = scene->materials.GetComponent(materialEntity_HighAltitude);
+ if (material != nullptr)
+ {
+ material_HighAltitude = *material;
+ }
+ }
+
+ chunks.clear();
+
+ scene->Entity_Remove(terrainEntity);
+ scene->transforms.Create(terrainEntity);
+ scene->names.Create(terrainEntity) = "terrain";
+
+ materialEntity_Base = scene->Entity_CreateMaterial("terrainMaterial_Base");
+ materialEntity_Slope = scene->Entity_CreateMaterial("terrainMaterial_Slope");
+ materialEntity_LowAltitude = scene->Entity_CreateMaterial("terrainMaterial_LowAltitude");
+ materialEntity_HighAltitude = scene->Entity_CreateMaterial("terrainMaterial_HighAltitude");
+ scene->Component_Attach(materialEntity_Base, terrainEntity);
+ scene->Component_Attach(materialEntity_Slope, terrainEntity);
+ scene->Component_Attach(materialEntity_LowAltitude, terrainEntity);
+ scene->Component_Attach(materialEntity_HighAltitude, terrainEntity);
+ // init/restore materials:
+ *scene->materials.GetComponent(materialEntity_Base) = material_Base;
+ *scene->materials.GetComponent(materialEntity_Slope) = material_Slope;
+ *scene->materials.GetComponent(materialEntity_LowAltitude) = material_LowAltitude;
+ *scene->materials.GetComponent(materialEntity_HighAltitude) = material_HighAltitude;
+
+ const uint32_t seed = (uint32_t)seedSlider.GetValue();
+ perlin.init(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.4f;
+ weather.volumetricCloudParameters.CoverageMinimum = 1.35f;
+ if (presetCombo.GetItemUserData(presetCombo.GetSelected()) == PRESET_ISLANDS)
+ {
+ weather.SetOceanEnabled(true);
+ }
+ else
+ {
+ scene->ocean = {};
+ }
+ weather.oceanParameters.waterHeight = -40;
+ weather.oceanParameters.wave_amplitude = 120;
+ weather.fogStart = 10;
+ weather.fogEnd = 100000;
+ weather.SetHeightFog(true);
+ weather.fogHeightStart = 0;
+ weather.fogHeightEnd = 100;
+ weather.windDirection = XMFLOAT3(0.05f, 0.05f, 0.05f);
+ weather.windSpeed = 4;
+ }
+ 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.energy = 6;
+ light.SetCastShadow(true);
+ //light.SetVolumetricsEnabled(true);
+ TransformComponent& transform = *scene->transforms.GetComponent(sunEntity);
+ transform.RotateRollPitchYaw(XMFLOAT3(XM_PIDIV4, 0, XM_PIDIV4));
+ }
+}
+
+void TerrainGenerator::Generation_Update()
+{
+ // The generation task is always cancelled every frame so we are sure that generation is not running at this point
+ Generation_Cancel();
+
+ if (terrainEntity == INVALID_ENTITY || !scene->transforms.Contains(terrainEntity))
+ {
+ chunks.clear();
+ return;
+ }
+
+ // What was generated, will be merged in to the main scene and parented to the terrain entity
+ scene->Merge(generation_scene);
+ for (Entity entity : generation_entities)
+ {
+ scene->Component_Attach(entity, terrainEntity);
+ }
+ generation_entities.clear();
+
+ if (centerToCamCheckBox.GetCheck())
+ {
+ const CameraComponent& camera = GetCamera();
+ center_chunk.x = (int)std::floor((camera.Eye.x + half_width) * width_rcp);
+ center_chunk.z = (int)std::floor((camera.Eye.z + half_width) * width_rcp);
+ }
+
+ // Chunk removal checks:
+ if (removalCheckBox.GetCheck())
+ {
+ const int removal_threshold = (int)generationSlider.GetValue() + 2;
+ 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));
+ 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)
+ {
+ if (dist > 1)
+ {
+ wi::HairParticleSystem* grass = scene->hairs.GetComponent(chunk_data.entity);
+ if (grass != nullptr)
+ {
+ // remove this chunk's grass patch from the scene
+ scene->hairs.Remove(chunk_data.entity);
+ chunk_data.grass_exists = false; // grass can be generated here by generation thread...
+ }
+ }
+ }
+ }
+ it++;
+ }
+ }
+
+ // Start the generation on a background thread and keep it running until the next frame
+ wi::jobsystem::Execute(generation_workload, [=](wi::jobsystem::JobArgs args) {
+
+ const float lodMultiplier = lodSlider.GetValue();
+ const int generation = (int)generationSlider.GetValue();
+ const float bottomLevel = bottomLevelSlider.GetValue();
+ const float topLevel = topLevelSlider.GetValue();
+ const float heightmapBlend = heightmapBlendSlider.GetValue();
+ const float perlinBlend = perlinBlendSlider.GetValue();
+ const uint32_t seed = (uint32_t)seedSlider.GetValue();
+ const int perlinOctaves = (int)perlinOctavesSlider.GetValue();
+ const float perlinFrequency = perlinFrequencySlider.GetValue();
+ const float voronoiBlend = voronoiBlendSlider.GetValue();
+ const float voronoiFrequency = voronoiFrequencySlider.GetValue();
+ const float voronoiFade = voronoiFadeSlider.GetValue();
+ const float voronoiShape = voronoiShapeSlider.GetValue();
+ const float voronoiFalloff = voronoiFalloffSlider.GetValue();
+ const float voronoiPerturbation = voronoiPerturbationSlider.GetValue();
+ const float region1 = region1Slider.GetValue();
+ const float region2 = region2Slider.GetValue();
+ const float region3 = region3Slider.GetValue();
+
+ 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_entities.push_back(chunk_data.entity);
+
+ TransformComponent& transform = *generation_scene.transforms.GetComponent(chunk_data.entity);
+ transform.ClearTransform();
+ const XMFLOAT3 chunk_pos = XMFLOAT3(float(chunk.x * (width - 1)), 0, float(chunk.z * (width - 1)));
+ transform.Translate(chunk_pos);
+ transform.UpdateTransform();
+
+ MeshComponent& mesh = generation_scene.meshes.Create(chunk_data.entity);
+ object.meshID = chunk_data.entity;
+ mesh.indices = indices;
+ mesh.SetTerrain(true);
+ mesh.terrain_material1 = materialEntity_Slope;
+ mesh.terrain_material2 = materialEntity_LowAltitude;
+ mesh.terrain_material3 = materialEntity_HighAltitude;
+ for (auto& lod : lods)
+ {
+ mesh.subsets.emplace_back();
+ mesh.subsets.back().materialID = materialEntity_Base;
+ 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_colors.resize(vertexCount);
+ mesh.vertex_uvset_0.resize(vertexCount);
+ mesh.vertex_uvset_1.resize(vertexCount);
+ mesh.vertex_atlas.resize(vertexCount);
+
+ wi::HairParticleSystem grass;
+ 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, width, [&](wi::jobsystem::JobArgs args) {
+ uint32_t index = args.jobIndex;
+ const float x = float(index % width) - half_width;
+ const float z = float(index / width) - half_width;
+ 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_pos.x + x + corner_offsets[i].x, chunk_pos.z + z + corner_offsets[i].y);
+ if (perlinBlend > 0)
+ {
+ XMFLOAT2 p = world_pos;
+ p.x *= perlinFrequency;
+ p.y *= perlinFrequency;
+ height += (perlin.compute(p.x, p.y, 0, perlinOctaves) * 0.5f + 0.5f) * perlinBlend;
+ }
+ if (voronoiBlend > 0)
+ {
+ XMFLOAT2 p = world_pos;
+ p.x *= voronoiFrequency;
+ p.y *= voronoiFrequency;
+ if (voronoiPerturbation > 0)
+ {
+ const float angle = perlin.compute(p.x, p.y, 0, 6) * XM_2PI;
+ p.x += std::sin(angle) * voronoiPerturbation;
+ p.y += std::cos(angle) * voronoiPerturbation;
+ }
+ 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 - voronoiShape) * voronoiFade), std::max(0.0001f, voronoiFalloff));
+ height *= weight * voronoiBlend;
+ }
+ if (!heightmap.data.empty())
+ {
+ XMFLOAT2 pixel = XMFLOAT2(world_pos.x + heightmap.width * 0.5f, world_pos.y + heightmap.height * 0.5f);
+ if (pixel.x >= 0 && pixel.x < heightmap.width && pixel.y >= 0 && pixel.y < heightmap.height)
+ {
+ const int idx = int(pixel.x) + int(pixel.y) * heightmap.width;
+ height = ((float)heightmap.data[idx] / 255.0f) * heightmapBlend;
+ }
+ }
+ 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;
+
+ mesh.vertex_positions[index] = XMFLOAT3(x, height, z);
+ mesh.vertex_normals[index] = normal;
+ mesh.vertex_colors[index] = wi::Color::fromFloat4(materialBlendWeights);
+ const XMFLOAT2 uv = XMFLOAT2(x * width_rcp, z * width_rcp);
+ mesh.vertex_uvset_0[index] = uv;
+ mesh.vertex_uvset_1[index] = uv;
+ mesh.vertex_atlas[index] = uv;
+
+ XMFLOAT3 vertex_pos(chunk_pos.x + x, height, chunk_pos.z + z);
+
+ const float grass_noise_frequency = 0.1f;
+ const float grass_noise = perlin.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), 4.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
+
+ mesh.CreateRenderData();
+
+ // 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)
+ {
+ grass.meshID = chunk_data.entity;
+ grass.length = 5;
+ grass.strandCount = grass_valid_vertex_count.load() * 3;
+ grass.viewDistance = 80;
+ grass.frameCount = 2;
+ grass.framesX = 1;
+ grass.framesY = 2;
+ grass.frameStart = 0;
+ generation_scene.materials.Create(chunk_data.entity) = material_GrassParticle;
+ 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)
+ }
+
+ // Prop placement:
+ chunk_data.prop_rand.seed((uint32_t)chunk.compute_hash() ^ seed);
+ for (auto& prop : props)
+ {
+ std::uniform_int_distribution gen_distr(prop.min_count_per_chunk, prop.max_count_per_chunk);
+ 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 = mesh.indices[tri * 3 + 0];
+ uint32_t ind1 = mesh.indices[tri * 3 + 1];
+ uint32_t ind2 = mesh.indices[tri * 3 + 2];
+ const XMFLOAT3& pos0 = mesh.vertex_positions[ind0];
+ const XMFLOAT3& pos1 = mesh.vertex_positions[ind1];
+ const XMFLOAT3& pos2 = mesh.vertex_positions[ind2];
+ const uint32_t& col0 = mesh.vertex_colors[ind0];
+ const uint32_t& col1 = mesh.vertex_colors[ind1];
+ const uint32_t& col2 = mesh.vertex_colors[ind2];
+ const XMFLOAT4 region0 = wi::Color(col0).toFloat4();
+ const XMFLOAT4 region1 = wi::Color(col1).toFloat4();
+ const XMFLOAT4 region2 = wi::Color(col2).toFloat4();
+ // 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);
+ vertex_pos.x += chunk_pos.x;
+ vertex_pos.z += chunk_pos.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.compute(vertex_pos.x * prop.noise_frequency, vertex_pos.y * prop.noise_frequency, vertex_pos.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.entity);
+ }
+ }
+ }
+
+ }
+
+ // Grass patch placement:
+ it = chunks.find(chunk);
+ if (it != chunks.end() && it->second.entity != INVALID_ENTITY)
+ {
+ ChunkData& chunk_data = it->second;
+ if (chunk_data.grass.meshID != INVALID_ENTITY)
+ {
+ const int dist = std::max(std::abs(center_chunk.x - chunk.x), std::abs(center_chunk.z - chunk.z));
+ if (dist <= 1)
+ {
+ if (!chunk_data.grass_exists)
+ {
+ // add patch for this chunk
+ wi::HairParticleSystem& grass = generation_scene.hairs.Create(chunk_data.entity);
+ grass = chunk_data.grass;
+ const MeshComponent* mesh = generation_scene.meshes.GetComponent(chunk_data.entity);
+ if (mesh != nullptr)
+ {
+ grass.CreateRenderData(*mesh);
+ }
+ chunk_data.grass_exists = true; // don't generate more grass here
+ }
+ }
+ }
+ }
+
+ };
+
+ // 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
+}
diff --git a/Editor/TerrainGenerator.h b/Editor/TerrainGenerator.h
new file mode 100644
index 000000000..25ea9d54a
--- /dev/null
+++ b/Editor/TerrainGenerator.h
@@ -0,0 +1,136 @@
+#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();
+ }
+ };
+}
+
+struct ChunkData
+{
+ wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY;
+ wi::HairParticleSystem grass;
+ bool grass_exists = false;
+ std::mt19937 prop_rand;
+};
+
+struct TerrainGenerator : public wi::gui::Window
+{
+ inline static const int width = 64 + 3; // + 3: filler vertices for lod apron and grid perimeter
+ inline static const float half_width = (width - 1) * 0.5f;
+ inline static const float width_rcp = 1.0f / (width - 1);
+ inline static const uint32_t vertexCount = width * width;
+ inline static const int max_lod = (int)std::log2(width - 3) + 1;
+ wi::scene::Scene* scene = &wi::scene::GetScene(); // by default it uses the global scene, but this can be changed
+ wi::ecs::Entity terrainEntity = wi::ecs::INVALID_ENTITY;
+ wi::ecs::Entity materialEntity_Base = wi::ecs::INVALID_ENTITY;
+ wi::ecs::Entity materialEntity_Slope = wi::ecs::INVALID_ENTITY;
+ wi::ecs::Entity materialEntity_LowAltitude = wi::ecs::INVALID_ENTITY;
+ wi::ecs::Entity materialEntity_HighAltitude = wi::ecs::INVALID_ENTITY;
+ 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::unordered_map chunks;
+ wi::vector indices;
+ struct LOD
+ {
+ uint32_t indexOffset = 0;
+ uint32_t indexCount = 0;
+ };
+ wi::vector lods;
+ wi::noise::Perlin perlin;
+ Chunk center_chunk = {};
+
+ struct HeightmapTexture
+ {
+ wi::vector data;
+ int width = 0;
+ int height = 0;
+ } heightmap;
+
+ 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;
+ wi::vector generation_entities; // because cannot parent to main scene on the background thread, parentable entities are collected
+
+ wi::gui::CheckBox centerToCamCheckBox;
+ wi::gui::CheckBox removalCheckBox;
+ wi::gui::Slider lodSlider;
+ wi::gui::Slider generationSlider;
+ wi::gui::ComboBox presetCombo;
+ wi::gui::Slider seedSlider;
+ wi::gui::Slider bottomLevelSlider;
+ wi::gui::Slider topLevelSlider;
+ wi::gui::Slider perlinBlendSlider;
+ wi::gui::Slider perlinFrequencySlider;
+ wi::gui::Slider perlinOctavesSlider;
+ wi::gui::Slider voronoiBlendSlider;
+ wi::gui::Slider voronoiFrequencySlider;
+ wi::gui::Slider voronoiFadeSlider;
+ wi::gui::Slider voronoiShapeSlider;
+ wi::gui::Slider voronoiFalloffSlider;
+ wi::gui::Slider voronoiPerturbationSlider;
+ wi::gui::Button heightmapButton;
+ wi::gui::Slider heightmapBlendSlider;
+
+ wi::gui::Slider region1Slider;
+ wi::gui::Slider region2Slider;
+ wi::gui::Slider region3Slider;
+
+ // 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();
+ // Tells the generation thread that it should be cancelled and blocks until that is confirmed
+ void Generation_Cancel();
+
+};
diff --git a/Editor/main_Windows.cpp b/Editor/main_Windows.cpp
index a99ef48d4..8076a70df 100644
--- a/Editor/main_Windows.cpp
+++ b/Editor/main_Windows.cpp
@@ -69,6 +69,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
}
}
+ editor.renderComponent.terragen.Generation_Cancel();
+
return (int) msg.wParam;
}
diff --git a/Editor/meshoptimizer/allocator.cpp b/Editor/meshoptimizer/allocator.cpp
index da7cc540b..072e8e51a 100644
--- a/Editor/meshoptimizer/allocator.cpp
+++ b/Editor/meshoptimizer/allocator.cpp
@@ -1,7 +1,7 @@
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
#include "meshoptimizer.h"
-void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*))
+void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*))
{
meshopt_Allocator::Storage::allocate = allocate;
meshopt_Allocator::Storage::deallocate = deallocate;
diff --git a/Editor/meshoptimizer/meshoptimizer.h b/Editor/meshoptimizer/meshoptimizer.h
index fe8d34973..be4b765d9 100644
--- a/Editor/meshoptimizer/meshoptimizer.h
+++ b/Editor/meshoptimizer/meshoptimizer.h
@@ -1,5 +1,5 @@
/**
- * meshoptimizer - version 0.16
+ * meshoptimizer - version 0.17
*
* Copyright (C) 2016-2021, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at https://github.com/zeux/meshoptimizer
@@ -12,13 +12,22 @@
#include
/* Version macro; major * 1000 + minor * 10 + patch */
-#define MESHOPTIMIZER_VERSION 160 /* 0.16 */
+#define MESHOPTIMIZER_VERSION 170 /* 0.17 */
/* If no API is defined, assume default */
#ifndef MESHOPTIMIZER_API
#define MESHOPTIMIZER_API
#endif
+/* Set the calling-convention for alloc/dealloc function pointers */
+#ifndef MESHOPTIMIZER_ALLOC_CALLCONV
+#ifdef _MSC_VER
+#define MESHOPTIMIZER_ALLOC_CALLCONV __cdecl
+#else
+#define MESHOPTIMIZER_ALLOC_CALLCONV
+#endif
+#endif
+
/* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */
#define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API
@@ -108,7 +117,7 @@ MESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti(unsigned int* dest
* destination must contain enough space for the resulting index buffer (index_count*2 elements)
* vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
*/
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+MESHOPTIMIZER_API void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
/**
* Generate index buffer that can be used for PN-AEN tessellation with crack-free displacement
@@ -124,7 +133,7 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateAdjacencyIndexBuffer(unsigned in
* destination must contain enough space for the resulting index buffer (index_count*4 elements)
* vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
*/
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+MESHOPTIMIZER_API void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
/**
* Vertex transform cache optimizer
@@ -201,10 +210,10 @@ MESHOPTIMIZER_API size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t
MESHOPTIMIZER_API size_t meshopt_encodeIndexBufferBound(size_t index_count, size_t vertex_count);
/**
- * Experimental: Set index encoder format version
+ * Set index encoder format version
* version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.14+)
*/
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeIndexVersion(int version);
+MESHOPTIMIZER_API void meshopt_encodeIndexVersion(int version);
/**
* Index buffer decoder
@@ -217,15 +226,15 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeIndexVersion(int version);
MESHOPTIMIZER_API int meshopt_decodeIndexBuffer(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size);
/**
- * Experimental: Index sequence encoder
+ * Index sequence encoder
* Encodes index sequence into an array of bytes that is generally smaller and compresses better compared to original.
* Input index sequence can represent arbitrary topology; for triangle lists meshopt_encodeIndexBuffer is likely to be better.
* Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space
*
* buffer must contain enough space for the encoded index sequence (use meshopt_encodeIndexSequenceBound to compute worst case size)
*/
-MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count);
-MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count);
+MESHOPTIMIZER_API size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count);
+MESHOPTIMIZER_API size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count);
/**
* Index sequence decoder
@@ -235,7 +244,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequenceBound(size_t index_
*
* destination must contain enough space for the resulting index sequence (index_count elements)
*/
-MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size);
+MESHOPTIMIZER_API int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size);
/**
* Vertex buffer encoder
@@ -250,10 +259,10 @@ MESHOPTIMIZER_API size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_
MESHOPTIMIZER_API size_t meshopt_encodeVertexBufferBound(size_t vertex_count, size_t vertex_size);
/**
- * Experimental: Set vertex encoder format version
+ * Set vertex encoder format version
* version must specify the data format version to encode; valid values are 0 (decodable by all library versions)
*/
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeVertexVersion(int version);
+MESHOPTIMIZER_API void meshopt_encodeVertexVersion(int version);
/**
* Vertex buffer decoder
@@ -278,9 +287,30 @@ MESHOPTIMIZER_API int meshopt_decodeVertexBuffer(void* destination, size_t verte
* meshopt_decodeFilterExp decodes exponential encoding of floating-point data with 8-bit exponent and 24-bit integer mantissa as 2^E*M.
* Each 32-bit component is decoded in isolation; stride must be divisible by 4.
*/
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterOct(void* buffer, size_t vertex_count, size_t vertex_size);
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterQuat(void* buffer, size_t vertex_count, size_t vertex_size);
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t vertex_count, size_t vertex_size);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride);
+
+/**
+ * Vertex buffer filter encoders
+ * These functions can be used to encode data in a format that meshopt_decodeFilter can decode
+ *
+ * meshopt_encodeFilterOct encodes unit vectors with K-bit (K <= 16) signed X/Y as an output.
+ * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. W is preserved as is.
+ * Input data must contain 4 floats for every vector (count*4 total).
+ *
+ * meshopt_encodeFilterQuat encodes unit quaternions with K-bit (4 <= K <= 16) component encoding.
+ * Each component is stored as an 16-bit integer; stride must be equal to 8.
+ * Input data must contain 4 floats for every quaternion (count*4 total).
+ *
+ * meshopt_encodeFilterExp encodes arbitrary (finite) floating-point data with 8-bit exponent and K-bit integer mantissa (1 <= K <= 24).
+ * Mantissa is shared between all components of a given vector as defined by stride; stride must be divisible by 4.
+ * Input data must contain stride/4 floats for every vector (count*stride/4 total).
+ * When individual (scalar) encoding is desired, simply pass stride=4 and adjust count accordingly.
+ */
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterQuat(void* destination, size_t count, size_t stride, int bits, const float* data);
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data);
/**
* Experimental: Mesh simplifier
@@ -300,7 +330,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplify(unsigned int* destination, co
/**
* Experimental: Mesh simplifier (sloppy)
- * Reduces the number of triangles in the mesh, sacrificing mesh apperance for simplification performance
+ * Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance
* The algorithm doesn't preserve mesh topology but can stop short of the target goal based on target error.
* Returns the number of indices after simplification, with destination containing new index data
* The resulting index buffer references vertices from the original vertex buffer.
@@ -327,7 +357,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(unsigned int* destinati
/**
* Experimental: Returns the error scaling factor used by the simplifier to convert between absolute and relative extents
- *
+ *
* Absolute error must be *divided* by the scaling factor before passing it to meshopt_simplify as target_error
* Relative error returned by meshopt_simplify via result_error must be *multiplied* by the scaling factor to get absolute error.
*/
@@ -412,7 +442,7 @@ struct meshopt_Meshlet
};
/**
- * Experimental: Meshlet builder
+ * Meshlet builder
* Splits the mesh into a set of meshlets where each meshlet has a micro index buffer indexing into meshlet vertices that refer to the original vertex buffer
* The resulting data can be used to render meshes using NVidia programmable mesh shading pipeline, or in other cluster-based renderers.
* When using buildMeshlets, vertex positions need to be provided to minimize the size of the resulting clusters.
@@ -425,9 +455,9 @@ struct meshopt_Meshlet
* max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 255 - not 256!, max_triangles <= 512)
* cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency
*/
-MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight);
-MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles);
-MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles);
+MESHOPTIMIZER_API size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight);
+MESHOPTIMIZER_API size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles);
+MESHOPTIMIZER_API size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles);
struct meshopt_Bounds
{
@@ -446,7 +476,7 @@ struct meshopt_Bounds
};
/**
- * Experimental: Cluster bounds generator
+ * Cluster bounds generator
* Creates bounding volumes that can be used for frustum, backface and occlusion culling.
*
* For backface culling with orthographic projection, use the following formula to reject backfacing clusters:
@@ -466,8 +496,8 @@ struct meshopt_Bounds
* vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
* index_count/3 should be less than or equal to 512 (the function assumes clusters of limited size)
*/
-MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
-MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
/**
* Experimental: Spatial sorter
@@ -493,7 +523,7 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortTriangles(unsigned int* desti
* Note that all algorithms only allocate memory for temporary use.
* allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first.
*/
-MESHOPTIMIZER_API void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*));
+MESHOPTIMIZER_API void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*));
#ifdef __cplusplus
} /* extern "C" */
@@ -675,8 +705,8 @@ public:
template
struct StorageT
{
- static void* (*allocate)(size_t);
- static void (*deallocate)(void*);
+ static void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t);
+ static void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*);
};
typedef StorageT Storage;
@@ -707,8 +737,8 @@ private:
};
// This makes sure that allocate/deallocate are lazily generated in translation units that need them and are deduplicated by the linker
-template void* (*meshopt_Allocator::StorageT::allocate)(size_t) = operator new;
-template void (*meshopt_Allocator::StorageT::deallocate)(void*) = operator delete;
+template void* (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT::allocate)(size_t) = operator new;
+template void (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT::deallocate)(void*) = operator delete;
#endif
/* Inline implementation for C++ templated wrappers */
diff --git a/Editor/meshoptimizer/simplifier.cpp b/Editor/meshoptimizer/simplifier.cpp
index 9f56cdac9..bf1431269 100644
--- a/Editor/meshoptimizer/simplifier.cpp
+++ b/Editor/meshoptimizer/simplifier.cpp
@@ -356,7 +356,7 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned
#if TRACE
printf("locked: many open edges %d, disconnected seam %d, many seam edges %d, many wedges %d\n",
- int(stats[0]), int(stats[1]), int(stats[2]), int(stats[3]));
+ int(stats[0]), int(stats[1]), int(stats[2]), int(stats[3]));
#endif
}
@@ -952,8 +952,8 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
float error_goal_perfect = edge_collapse_goal < collapse_count ? collapses[collapse_order[edge_collapse_goal]].error : 0.f;
printf("removed %d triangles, error %e (goal %e); evaluated %d/%d collapses (done %d, skipped %d, invalid %d)\n",
- int(triangle_collapses), sqrtf(result_error), sqrtf(error_goal_perfect),
- int(stats[0]), int(collapse_count), int(edge_collapses), int(stats[1]), int(stats[2]));
+ int(triangle_collapses), sqrtf(result_error), sqrtf(error_goal_perfect),
+ int(stats[0]), int(collapse_count), int(edge_collapses), int(stats[1]), int(stats[2]));
#endif
return edge_collapses;
@@ -1305,7 +1305,7 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices,
kinds[vertex_kind[i]] += remap[i] == i;
printf("kinds: manifold %d, border %d, seam %d, complex %d, locked %d\n",
- int(kinds[Kind_Manifold]), int(kinds[Kind_Border]), int(kinds[Kind_Seam]), int(kinds[Kind_Complex]), int(kinds[Kind_Locked]));
+ int(kinds[Kind_Manifold]), int(kinds[Kind_Border]), int(kinds[Kind_Seam]), int(kinds[Kind_Complex]), int(kinds[Kind_Locked]));
#endif
Vector3* vertex_positions = allocator.allocate(vertex_count);
@@ -1463,9 +1463,9 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
#if TRACE
printf("pass %d (%s): grid size %d, triangles %d, %s\n",
- pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary",
- grid_size, int(triangles),
- (triangles <= target_index_count / 3) ? "under" : "over");
+ pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary",
+ grid_size, int(triangles),
+ (triangles <= target_index_count / 3) ? "under" : "over");
#endif
float tip = interpolate(float(target_index_count / 3), float(min_grid), float(min_triangles), float(grid_size), float(triangles), float(max_grid), float(max_triangles));
@@ -1592,9 +1592,9 @@ size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_pos
#if TRACE
printf("pass %d (%s): grid size %d, vertices %d, %s\n",
- pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary",
- grid_size, int(vertices),
- (vertices <= target_vertex_count) ? "under" : "over");
+ pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary",
+ grid_size, int(vertices),
+ (vertices <= target_vertex_count) ? "under" : "over");
#endif
float tip = interpolate(float(target_vertex_count), float(min_grid), float(min_vertices), float(grid_size), float(vertices), float(max_grid), float(max_vertices));
diff --git a/Editor/meshoptimizer/vertexcodec.cpp b/Editor/meshoptimizer/vertexcodec.cpp
index 5f3ec204a..7925ea862 100644
--- a/Editor/meshoptimizer/vertexcodec.cpp
+++ b/Editor/meshoptimizer/vertexcodec.cpp
@@ -77,6 +77,8 @@
#endif
#ifdef SIMD_WASM
+#undef __DEPRECATED
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include
#endif
@@ -1028,7 +1030,7 @@ static unsigned int getCpuFeatures()
return cpuinfo[2];
}
-unsigned int cpuid = getCpuFeatures();
+static unsigned int cpuid = getCpuFeatures();
#endif
} // namespace meshopt
diff --git a/Editor/meshoptimizer/vertexfilter.cpp b/Editor/meshoptimizer/vertexfilter.cpp
index 669eff959..606a280aa 100644
--- a/Editor/meshoptimizer/vertexfilter.cpp
+++ b/Editor/meshoptimizer/vertexfilter.cpp
@@ -52,6 +52,7 @@
#endif
#ifdef SIMD_WASM
+#undef __DEPRECATED
#include
#endif
@@ -792,52 +793,170 @@ static void decodeFilterExpSimd(unsigned int* data, size_t count)
} // namespace meshopt
-void meshopt_decodeFilterOct(void* buffer, size_t vertex_count, size_t vertex_size)
+void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride)
{
using namespace meshopt;
- assert(vertex_size == 4 || vertex_size == 8);
+ assert(stride == 4 || stride == 8);
#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)
- if (vertex_size == 4)
- dispatchSimd(decodeFilterOctSimd, static_cast(buffer), vertex_count, 4);
+ if (stride == 4)
+ dispatchSimd(decodeFilterOctSimd, static_cast(buffer), count, 4);
else
- dispatchSimd(decodeFilterOctSimd, static_cast(buffer), vertex_count, 4);
+ dispatchSimd(decodeFilterOctSimd, static_cast(buffer), count, 4);
#else
- if (vertex_size == 4)
- decodeFilterOct(static_cast(buffer), vertex_count);
+ if (stride == 4)
+ decodeFilterOct(static_cast(buffer), count);
else
- decodeFilterOct(static_cast(buffer), vertex_count);
+ decodeFilterOct(static_cast(buffer), count);
#endif
}
-void meshopt_decodeFilterQuat(void* buffer, size_t vertex_count, size_t vertex_size)
+void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride)
{
using namespace meshopt;
- assert(vertex_size == 8);
- (void)vertex_size;
+ assert(stride == 8);
+ (void)stride;
#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)
- dispatchSimd(decodeFilterQuatSimd, static_cast(buffer), vertex_count, 4);
+ dispatchSimd(decodeFilterQuatSimd, static_cast(buffer), count, 4);
#else
- decodeFilterQuat(static_cast(buffer), vertex_count);
+ decodeFilterQuat(static_cast(buffer), count);
#endif
}
-void meshopt_decodeFilterExp(void* buffer, size_t vertex_count, size_t vertex_size)
+void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride)
{
using namespace meshopt;
- assert(vertex_size % 4 == 0);
+ assert(stride > 0 && stride % 4 == 0);
#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)
- dispatchSimd(decodeFilterExpSimd, static_cast(buffer), vertex_count * (vertex_size / 4), 1);
+ dispatchSimd(decodeFilterExpSimd, static_cast(buffer), count * (stride / 4), 1);
#else
- decodeFilterExp(static_cast(buffer), vertex_count * (vertex_size / 4));
+ decodeFilterExp(static_cast(buffer), count * (stride / 4));
#endif
}
+void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data)
+{
+ assert(stride == 4 || stride == 8);
+ assert(bits >= 1 && bits <= 16);
+
+ signed char* d8 = static_cast(destination);
+ short* d16 = static_cast(destination);
+
+ int bytebits = int(stride * 2);
+
+ for (size_t i = 0; i < count; ++i)
+ {
+ const float* n = &data[i * 4];
+
+ // octahedral encoding of a unit vector
+ float nx = n[0], ny = n[1], nz = n[2], nw = n[3];
+ float nl = fabsf(nx) + fabsf(ny) + fabsf(nz);
+ float ns = nl == 0.f ? 0.f : 1.f / nl;
+
+ nx *= ns;
+ ny *= ns;
+
+ float u = (nz >= 0.f) ? nx : (1 - fabsf(ny)) * (nx >= 0.f ? 1.f : -1.f);
+ float v = (nz >= 0.f) ? ny : (1 - fabsf(nx)) * (ny >= 0.f ? 1.f : -1.f);
+
+ int fu = meshopt_quantizeSnorm(u, bits);
+ int fv = meshopt_quantizeSnorm(v, bits);
+ int fo = meshopt_quantizeSnorm(1.f, bits);
+ int fw = meshopt_quantizeSnorm(nw, bytebits);
+
+ if (stride == 4)
+ {
+ d8[i * 4 + 0] = (signed char)(fu);
+ d8[i * 4 + 1] = (signed char)(fv);
+ d8[i * 4 + 2] = (signed char)(fo);
+ d8[i * 4 + 3] = (signed char)(fw);
+ }
+ else
+ {
+ d16[i * 4 + 0] = short(fu);
+ d16[i * 4 + 1] = short(fv);
+ d16[i * 4 + 2] = short(fo);
+ d16[i * 4 + 3] = short(fw);
+ }
+ }
+}
+
+void meshopt_encodeFilterQuat(void* destination_, size_t count, size_t stride, int bits, const float* data)
+{
+ assert(stride == 8);
+ assert(bits >= 4 && bits <= 16);
+ (void)stride;
+
+ short* destination = static_cast(destination_);
+
+ const float scaler = sqrtf(2.f);
+
+ for (size_t i = 0; i < count; ++i)
+ {
+ const float* q = &data[i * 4];
+ short* d = &destination[i * 4];
+
+ // establish maximum quaternion component
+ int qc = 0;
+ qc = fabsf(q[1]) > fabsf(q[qc]) ? 1 : qc;
+ qc = fabsf(q[2]) > fabsf(q[qc]) ? 2 : qc;
+ qc = fabsf(q[3]) > fabsf(q[qc]) ? 3 : qc;
+
+ // we use double-cover properties to discard the sign
+ float sign = q[qc] < 0.f ? -1.f : 1.f;
+
+ // note: we always encode a cyclical swizzle to be able to recover the order via rotation
+ d[0] = short(meshopt_quantizeSnorm(q[(qc + 1) & 3] * scaler * sign, bits));
+ d[1] = short(meshopt_quantizeSnorm(q[(qc + 2) & 3] * scaler * sign, bits));
+ d[2] = short(meshopt_quantizeSnorm(q[(qc + 3) & 3] * scaler * sign, bits));
+ d[3] = short((meshopt_quantizeSnorm(1.f, bits) & ~3) | qc);
+ }
+}
+
+void meshopt_encodeFilterExp(void* destination_, size_t count, size_t stride, int bits, const float* data)
+{
+ assert(stride > 0 && stride % 4 == 0);
+ assert(bits >= 1 && bits <= 24);
+
+ unsigned int* destination = static_cast(destination_);
+ size_t stride_float = stride / sizeof(float);
+
+ for (size_t i = 0; i < count; ++i)
+ {
+ const float* v = &data[i * stride_float];
+ unsigned int* d = &destination[i * stride_float];
+
+ // use maximum exponent to encode values; this guarantess that mantissa is [-1, 1]
+ int exp = -100;
+
+ for (size_t j = 0; j < stride_float; ++j)
+ {
+ int e;
+ frexp(v[j], &e);
+
+ exp = (exp < e) ? e : exp;
+ }
+
+ // note that we additionally scale the mantissa to make it a K-bit signed integer (K-1 bits for magnitude)
+ exp -= (bits - 1);
+
+ // compute renormalized rounded mantissa for each component
+ int mmask = (1 << 24) - 1;
+
+ for (size_t j = 0; j < stride_float; ++j)
+ {
+ int m = int(ldexp(v[j], -exp) + (v[j] >= 0 ? 0.5f : -0.5f));
+
+ d[j] = (m & mmask) | (unsigned(exp) << 24);
+ }
+ }
+}
+
#undef SIMD_SSE
#undef SIMD_NEON
#undef SIMD_WASM
diff --git a/Editor/terrain/base-LICENSE.txt b/Editor/terrain/base-LICENSE.txt
new file mode 100644
index 000000000..1b06d2c64
--- /dev/null
+++ b/Editor/terrain/base-LICENSE.txt
@@ -0,0 +1,4 @@
+ambientCG (ambientCG.com)
+CC0 1.0 Public Domain Dedication (https://creativecommons.org/publicdomain/zero/1.0/)
+Grass001 by ambientCG
+https://ambientcg.com/a/Grass001
\ No newline at end of file
diff --git a/Editor/terrain/base.jpg b/Editor/terrain/base.jpg
new file mode 100644
index 000000000..89b4df241
Binary files /dev/null and b/Editor/terrain/base.jpg differ
diff --git a/Editor/terrain/base_nor.jpg b/Editor/terrain/base_nor.jpg
new file mode 100644
index 000000000..c35eafbf3
Binary files /dev/null and b/Editor/terrain/base_nor.jpg differ
diff --git a/Editor/terrain/bush-LICENSE.txt b/Editor/terrain/bush-LICENSE.txt
new file mode 100644
index 000000000..666fa1848
--- /dev/null
+++ b/Editor/terrain/bush-LICENSE.txt
@@ -0,0 +1 @@
+https://www.cgtrader.com/free-3d-models/plant/bush/mediteranean-bush
\ No newline at end of file
diff --git a/Editor/terrain/bush.wiscene b/Editor/terrain/bush.wiscene
new file mode 100644
index 000000000..c43fbe9c2
Binary files /dev/null and b/Editor/terrain/bush.wiscene differ
diff --git a/Editor/terrain/grassparticle.png b/Editor/terrain/grassparticle.png
new file mode 100644
index 000000000..aa815a2d2
Binary files /dev/null and b/Editor/terrain/grassparticle.png differ
diff --git a/Editor/terrain/high_altitude-LICENSE.txt b/Editor/terrain/high_altitude-LICENSE.txt
new file mode 100644
index 000000000..1490a7370
--- /dev/null
+++ b/Editor/terrain/high_altitude-LICENSE.txt
@@ -0,0 +1 @@
+https://www.sharetextures.com/textures/ground/snow_ground_3/
\ No newline at end of file
diff --git a/Editor/terrain/high_altitude.jpg b/Editor/terrain/high_altitude.jpg
new file mode 100644
index 000000000..0267a5d58
Binary files /dev/null and b/Editor/terrain/high_altitude.jpg differ
diff --git a/Editor/terrain/high_altitude_nor.jpg b/Editor/terrain/high_altitude_nor.jpg
new file mode 100644
index 000000000..07dd1e9d5
Binary files /dev/null and b/Editor/terrain/high_altitude_nor.jpg differ
diff --git a/Editor/terrain/low_altitude-LICENSE.txt b/Editor/terrain/low_altitude-LICENSE.txt
new file mode 100644
index 000000000..a755f4192
--- /dev/null
+++ b/Editor/terrain/low_altitude-LICENSE.txt
@@ -0,0 +1 @@
+https://www.cgbookcase.com/textures/sandy-gravel-01/?source=3dassets.one
\ No newline at end of file
diff --git a/Editor/terrain/low_altitude.jpg b/Editor/terrain/low_altitude.jpg
new file mode 100644
index 000000000..8e7b21576
Binary files /dev/null and b/Editor/terrain/low_altitude.jpg differ
diff --git a/Editor/terrain/low_altitude_nor.jpg b/Editor/terrain/low_altitude_nor.jpg
new file mode 100644
index 000000000..4b6671a2f
Binary files /dev/null and b/Editor/terrain/low_altitude_nor.jpg differ
diff --git a/Editor/terrain/rock-LICENSE.txt b/Editor/terrain/rock-LICENSE.txt
new file mode 100644
index 000000000..5189c99e3
--- /dev/null
+++ b/Editor/terrain/rock-LICENSE.txt
@@ -0,0 +1 @@
+https://www.cgtrader.com/free-3d-models/plant/other/rocks-56c7a2d1-2e36-4b17-8d79-2a69ab29d860
\ No newline at end of file
diff --git a/Editor/terrain/rock.wiscene b/Editor/terrain/rock.wiscene
new file mode 100644
index 000000000..d193ebbe1
Binary files /dev/null and b/Editor/terrain/rock.wiscene differ
diff --git a/Editor/terrain/slope-LICENSE.txt b/Editor/terrain/slope-LICENSE.txt
new file mode 100644
index 000000000..42892115e
--- /dev/null
+++ b/Editor/terrain/slope-LICENSE.txt
@@ -0,0 +1,4 @@
+ambientCG (ambientCG.com)
+CC0 1.0 Public Domain Dedication (https://creativecommons.org/publicdomain/zero/1.0/)
+Rock048 by ambientCG
+https://ambientcg.com/a/Rock048
\ No newline at end of file
diff --git a/Editor/terrain/slope.jpg b/Editor/terrain/slope.jpg
new file mode 100644
index 000000000..0c0190b3c
Binary files /dev/null and b/Editor/terrain/slope.jpg differ
diff --git a/Editor/terrain/slope_nor.jpg b/Editor/terrain/slope_nor.jpg
new file mode 100644
index 000000000..e721784f7
Binary files /dev/null and b/Editor/terrain/slope_nor.jpg differ
diff --git a/Editor/terrain/tree-LICENSE.txt b/Editor/terrain/tree-LICENSE.txt
new file mode 100644
index 000000000..bbc15ae1e
--- /dev/null
+++ b/Editor/terrain/tree-LICENSE.txt
@@ -0,0 +1 @@
+https://free3d.com/3d-model/tree02-35663.html
\ No newline at end of file
diff --git a/Editor/terrain/tree.wiscene b/Editor/terrain/tree.wiscene
new file mode 100644
index 000000000..3b55a8717
Binary files /dev/null and b/Editor/terrain/tree.wiscene differ
diff --git a/README.md b/README.md
index 3b5166bae..0d328d1a3 100644
--- a/README.md
+++ b/README.md
@@ -248,6 +248,18 @@ If you are having trouble getting the applications to run, make sure that you sa
### Screenshots:
+Procedural terrain generator:
+
+
+Path tracing:
+
+
+Dynamic Diffuse Global Illumination (DDGI):
+
+
+Snow storm with particle systems:
+
+
Sponza scene with voxel GI enabled:

diff --git a/WickedEngine/ArchiveVersionHistory.txt b/WickedEngine/ArchiveVersionHistory.txt
index dfa57436a..8b8ab2082 100644
--- a/WickedEngine/ArchiveVersionHistory.txt
+++ b/WickedEngine/ArchiveVersionHistory.txt
@@ -1,5 +1,6 @@
This file contains changelog of wi::Archive versions
+76: serialized LOD parameters
75: PreviousFrameTransformComponent is no longer serialized
74: serialized emitter restitution
73: wi::Archive no longer saves null terminator for strings
diff --git a/WickedEngine/WickedEngine.h b/WickedEngine/WickedEngine.h
index 8224dca8e..eb5fe0022 100644
--- a/WickedEngine/WickedEngine.h
+++ b/WickedEngine/WickedEngine.h
@@ -68,6 +68,7 @@
#include "wiUnorderedMap.h"
#include "wiUnorderedSet.h"
#include "wiVector.h"
+#include "wiNoise.h"
#ifdef _WIN32
#ifdef PLATFORM_UWP
diff --git a/WickedEngine/WickedEngine_SOURCE.vcxitems b/WickedEngine/WickedEngine_SOURCE.vcxitems
index 98859c0b9..e48df2a0f 100644
--- a/WickedEngine/WickedEngine_SOURCE.vcxitems
+++ b/WickedEngine/WickedEngine_SOURCE.vcxitems
@@ -229,6 +229,7 @@
+
diff --git a/WickedEngine/WickedEngine_SOURCE.vcxitems.filters b/WickedEngine/WickedEngine_SOURCE.vcxitems.filters
index e9b23577c..5c3e2ff0e 100644
--- a/WickedEngine/WickedEngine_SOURCE.vcxitems.filters
+++ b/WickedEngine/WickedEngine_SOURCE.vcxitems.filters
@@ -1074,6 +1074,9 @@
ENGINE\System
+
+ ENGINE\Helpers
+
diff --git a/WickedEngine/shaders/brdf.hlsli b/WickedEngine/shaders/brdf.hlsli
index 8bad147f5..5e58d8a6e 100644
--- a/WickedEngine/shaders/brdf.hlsli
+++ b/WickedEngine/shaders/brdf.hlsli
@@ -272,11 +272,11 @@ struct Surface
clearcoat.roughness = clamp(clearcoat.roughness, 0.045, 1);
clearcoat.roughnessBRDF = clearcoat.roughness * clearcoat.roughness;
- NdotV = saturate(abs(dot(N, V)) + 1e-5);
+ NdotV = saturate(dot(N, V) + 1e-5);
f90 = saturate(50.0 * dot(f0, 0.33));
F = F_Schlick(f0, f90, NdotV);
- clearcoat.F = F_Schlick(f0, f90, saturate(abs(dot(clearcoat.N, V)) + 1e-5));
+ clearcoat.F = F_Schlick(f0, f90, saturate(dot(clearcoat.N, V) + 1e-5));
clearcoat.F *= clearcoat.factor;
R = -reflect(V, N);
diff --git a/WickedEngine/shaders/hairparticlePS_prepass.hlsl b/WickedEngine/shaders/hairparticlePS_prepass.hlsl
index 71a6368df..28db487de 100644
--- a/WickedEngine/shaders/hairparticlePS_prepass.hlsl
+++ b/WickedEngine/shaders/hairparticlePS_prepass.hlsl
@@ -16,10 +16,7 @@ uint2 main(VertexToPixel input, out uint coverage : SV_Coverage) : SV_Target
}
// Distance dithered fade:
- if (dither(input.pos.xy + GetTemporalAASampleRotation()) - input.fade < 0)
- {
- alpha = 0;
- }
+ clip(dither(input.pos.xy + GetTemporalAASampleRotation()) - input.fade);
coverage = AlphaToCoverage(alpha, material.alphaTest, input.pos);
diff --git a/WickedEngine/shaders/hairparticle_simulateCS.hlsl b/WickedEngine/shaders/hairparticle_simulateCS.hlsl
index 434a7e406..ca41018bc 100644
--- a/WickedEngine/shaders/hairparticle_simulateCS.hlsl
+++ b/WickedEngine/shaders/hairparticle_simulateCS.hlsl
@@ -85,7 +85,13 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
target = normalize(mul((float3x3)xHairWorld, target));
const float3 root = base;
+ const float3 diff = root - GetCamera().position;
+ const float distsq = dot(diff, diff);
+ const bool distance_culled = dot(diff, diff) > sqr(xHairViewDistance);
+
float3 normal = 0;
+
+ const float delta_time = clamp(GetFrame().delta_time, 0, 1.0 / 30.0); // clamp delta time to avoid simulation blowing up
for (uint segmentID = 0; segmentID < xHairSegmentCount; ++segmentID)
{
@@ -140,7 +146,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
// Pull back to rest position:
force += (target - normal) * xStiffness;
- force *= GetFrame().delta_time;
+ force *= delta_time;
// Simulation buffer load:
float3 velocity = simulationBuffer[particleID].velocity;
@@ -154,14 +160,15 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
// Apply forces:
velocity += force;
- normal += velocity * clamp(GetFrame().delta_time, 0, 0.1); // clamp delta time to avoid simulation blowing up
+ normal += velocity * delta_time;
+ normal = normalize(normal);
// Drag:
velocity *= 0.98f;
// Store particle:
simulationBuffer[particleID].position = base;
- simulationBuffer[particleID].normal = normalize(normal);
+ simulationBuffer[particleID].normal = normal;
// Store simulation data:
simulationBuffer[particleID].velocity = velocity;
@@ -205,6 +212,10 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
const float3 wind = sin(mad(GetFrame().time, GetWeather().wind.speed, waveoffset)) * wavedir;
float3 position = rootposition + patchPos + wind;
+ if (distance_culled)
+ {
+ position = 0;
+ }
uint4 data;
data.xyz = asuint(position);
@@ -218,7 +229,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
sphere.center = (base + tip) * 0.5;
sphere.radius = len;
- if (GetCamera().frustum.intersects(sphere))
+ if (!distance_culled && GetCamera().frustum.intersects(sphere))
{
uint prevCount;
counterBuffer.InterlockedAdd(0, 1, prevCount);
diff --git a/WickedEngine/shaders/objectHF.hlsli b/WickedEngine/shaders/objectHF.hlsli
index dc2f47ff9..d60d56ae9 100644
--- a/WickedEngine/shaders/objectHF.hlsli
+++ b/WickedEngine/shaders/objectHF.hlsli
@@ -248,7 +248,7 @@ struct VertexSurface
inline void create(in ShaderMaterial material, in VertexInput input)
{
position = input.GetPosition();
- color = material.baseColor * unpack_rgba(input.GetInstance().color);
+ color = unpack_rgba(input.GetInstance().color);
color.a *= 1 - input.GetInstancePointer().GetDither();
emissiveColor = input.GetInstance().emissive;
@@ -263,7 +263,6 @@ struct VertexSurface
tangent.xyz = normalize(mul((float3x3)input.GetInstance().transformInverseTranspose.GetMatrix(), tangent.xyz));
uvsets = input.GetUVSets();
- uvsets.xy = mad(uvsets.xy, material.texMulAdd.xy, material.texMulAdd.zw);
atlas = input.GetAtlasUV();
@@ -1077,6 +1076,11 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
const float2 ScreenCoord = pixel * GetCamera().internal_resolution_rcp;
float3 bumpColor = 0;
+#ifdef OBJECTSHADER_USE_UVSETS
+ float4 uvsets = input.uvsets;
+ uvsets.xy = mad(uvsets.xy, GetMaterial().texMulAdd.xy, GetMaterial().texMulAdd.zw);
+#endif // OBJECTSHADER_USE_UVSETS
+
#ifndef DISABLE_ALPHATEST
#ifndef TRANSPARENT
#ifndef ENVMAPRENDERING
@@ -1110,7 +1114,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
#ifdef OBJECTSHADER_USE_TANGENT
#if 0
- float3x3 TBN = compute_tangent_frame(surface.N, surface.P, input.uvsets.xy);
+ float3x3 TBN = compute_tangent_frame(surface.N, surface.P, uvsets.xy);
#else
float4 tangent = input.tan;
tangent.xyz = normalize(tangent.xyz);
@@ -1119,7 +1123,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
#endif
#ifdef POM
- ParallaxOcclusionMapping(input.uvsets, surface.V, TBN);
+ ParallaxOcclusionMapping(uvsets, surface.V, TBN);
#endif // POM
#endif // OBJECTSHADER_USE_TANGENT
@@ -1136,7 +1140,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
if (GetMaterial().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0)
#endif // PREPASS
{
- const float2 UV_baseColorMap = GetMaterial().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_baseColorMap = GetMaterial().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw;
float4 baseColorMap = texture_basecolormap.Sample(sampler_objectshader, UV_baseColorMap);
baseColorMap.rgb = DEGAMMA(baseColorMap.rgb);
color *= baseColorMap;
@@ -1145,7 +1149,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
#ifdef OBJECTSHADER_USE_COLOR
- color *= input.color;
+ color *= GetMaterial().baseColor * input.color;
#endif // OBJECTSHADER_USE_COLOR
@@ -1162,7 +1166,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
#ifndef WATER
#ifdef OBJECTSHADER_USE_TANGENT
- NormalMapping(input.uvsets, surface.N, TBN, bumpColor);
+ NormalMapping(uvsets, surface.N, TBN, bumpColor);
#endif // OBJECTSHADER_USE_TANGENT
#endif // WATER
@@ -1173,7 +1177,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().uvset_surfaceMap >= 0)
{
- const float2 UV_surfaceMap = GetMaterial().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_surfaceMap = GetMaterial().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw;
surfaceMap = texture_surfacemap.Sample(sampler_objectshader, UV_surfaceMap);
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1185,7 +1189,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().uvset_specularMap >= 0)
{
- const float2 UV_specularMap = GetMaterial().uvset_specularMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_specularMap = GetMaterial().uvset_specularMap == 0 ? uvsets.xy : uvsets.zw;
specularMap = texture_specularmap.Sample(sampler_objectshader, UV_specularMap);
specularMap.rgb = DEGAMMA(specularMap.rgb);
}
@@ -1203,7 +1207,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (any(surface.emissiveColor) && GetMaterial().uvset_emissiveMap >= 0)
{
- const float2 UV_emissiveMap = GetMaterial().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_emissiveMap = GetMaterial().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw;
float4 emissiveMap = texture_emissivemap.Sample(sampler_objectshader, UV_emissiveMap);
emissiveMap.rgb = DEGAMMA(emissiveMap.rgb);
surface.emissiveColor *= emissiveMap.rgb * emissiveMap.a;
@@ -1213,12 +1217,14 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
#ifdef TERRAIN
+ color = 1;
surface.N = 0;
surface.albedo = 0;
surface.f0 = 0;
surface.roughness = 0;
surface.occlusion = 0;
surface.emissiveColor = 0;
+ surface.opacity = 1;
float4 sam;
float4 blend_weights = input.color;
@@ -1228,15 +1234,16 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (blend_weights.x > 0)
{
- float4 color2 = 1;
+ float4 color2 = GetMaterial().baseColor;
#ifdef OBJECTSHADER_USE_UVSETS
[branch]
if (GetMaterial().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0)
{
- float2 uv = GetMaterial().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw;
- color2 = texture_basecolormap.Sample(sampler_objectshader, uv);
- color2.rgb = DEGAMMA(color2.rgb);
+ float2 uv = GetMaterial().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw;
+ float4 basecolorMap = texture_basecolormap.Sample(sampler_objectshader, uv);
+ basecolorMap.rgb = DEGAMMA(basecolorMap.rgb);
+ color2 *= basecolorMap;
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1246,7 +1253,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().uvset_surfaceMap >= 0)
{
- float2 uv = GetMaterial().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw;
surfaceMap2 = texture_surfacemap.Sample(sampler_objectshader, uv);
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1259,7 +1266,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().normalMapStrength > 0 && GetMaterial().uvset_normalMap >= 0)
{
- float2 uv = GetMaterial().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw;
sam.rgb = texture_normalmap.Sample(sampler_objectshader, uv).rgb;
sam.rgb = sam.rgb * 2 - 1;
surface2.N = lerp(baseN, mul(sam.rgb, TBN), GetMaterial().normalMapStrength);
@@ -1272,7 +1279,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().uvset_emissiveMap >= 0 && any(surface2.emissiveColor))
{
- float2 uv = GetMaterial().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw;
sam = texture_emissivemap.Sample(sampler_objectshader, uv);
sam.rgb = DEGAMMA(sam.rgb);
surface2.emissiveColor *= sam.rgb * sam.a;
@@ -1290,15 +1297,18 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (blend_weights.y > 0)
{
- float4 color2 = 1;
+ float4 color2 = GetMaterial1().baseColor;
+ float4 uvsets = input.uvsets;
+ uvsets.xy = mad(uvsets.xy, GetMaterial1().texMulAdd.xy, GetMaterial1().texMulAdd.zw);
#ifdef OBJECTSHADER_USE_UVSETS
[branch]
if (GetMaterial1().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0)
{
- float2 uv = GetMaterial1().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw;
- color2 = texture_blend1_basecolormap.Sample(sampler_objectshader, uv);
- color2.rgb = DEGAMMA(color2.rgb);
+ float2 uv = GetMaterial1().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw;
+ float4 basecolorMap = texture_blend1_basecolormap.Sample(sampler_objectshader, uv);
+ basecolorMap.rgb = DEGAMMA(basecolorMap.rgb);
+ color2 *= basecolorMap;
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1308,7 +1318,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial1().uvset_surfaceMap >= 0)
{
- float2 uv = GetMaterial1().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial1().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw;
surfaceMap2 = texture_blend1_surfacemap.Sample(sampler_objectshader, uv);
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1321,7 +1331,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial1().normalMapStrength > 0 && GetMaterial1().uvset_normalMap >= 0)
{
- float2 uv = GetMaterial1().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial1().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw;
sam.rgb = texture_blend1_normalmap.Sample(sampler_objectshader, uv).rgb;
sam.rgb = sam.rgb * 2 - 1;
surface2.N = lerp(baseN, mul(sam.rgb, TBN), GetMaterial1().normalMapStrength);
@@ -1334,7 +1344,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial1().uvset_emissiveMap >= 0 && any(surface2.emissiveColor))
{
- float2 uv = GetMaterial1().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial1().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw;
sam = texture_blend1_emissivemap.Sample(sampler_objectshader, uv);
sam.rgb = DEGAMMA(sam.rgb);
surface2.emissiveColor *= sam.rgb * sam.a;
@@ -1352,15 +1362,18 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (blend_weights.z > 0)
{
- float4 color2 = 1;
+ float4 color2 = GetMaterial2().baseColor;
+ float4 uvsets = input.uvsets;
+ uvsets.xy = mad(uvsets.xy, GetMaterial2().texMulAdd.xy, GetMaterial2().texMulAdd.zw);
#ifdef OBJECTSHADER_USE_UVSETS
[branch]
if (GetMaterial2().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0)
{
- float2 uv = GetMaterial2().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw;
- color2 = texture_blend2_basecolormap.Sample(sampler_objectshader, uv);
- color2.rgb = DEGAMMA(color2.rgb);
+ float2 uv = GetMaterial2().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw;
+ float4 basecolorMap = texture_blend2_basecolormap.Sample(sampler_objectshader, uv);
+ basecolorMap.rgb = DEGAMMA(basecolorMap.rgb);
+ color2 *= basecolorMap;
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1370,7 +1383,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial2().uvset_surfaceMap >= 0)
{
- float2 uv = GetMaterial2().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial2().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw;
surfaceMap2 = texture_blend2_surfacemap.Sample(sampler_objectshader, uv);
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1383,7 +1396,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial2().normalMapStrength > 0 && GetMaterial2().uvset_normalMap >= 0)
{
- float2 uv = GetMaterial2().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial2().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw;
sam.rgb = texture_blend2_normalmap.Sample(sampler_objectshader, uv).rgb;
sam.rgb = sam.rgb * 2 - 1;
surface2.N = lerp(baseN, mul(sam.rgb, TBN), GetMaterial2().normalMapStrength);
@@ -1396,7 +1409,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial2().uvset_emissiveMap >= 0 && any(surface2.emissiveColor))
{
- float2 uv = GetMaterial2().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial2().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw;
sam = texture_blend2_emissivemap.Sample(sampler_objectshader, uv);
sam.rgb = DEGAMMA(sam.rgb);
surface2.emissiveColor *= sam.rgb * sam.a;
@@ -1414,15 +1427,18 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (blend_weights.w > 0)
{
- float4 color2 = 1;
+ float4 color2 = GetMaterial3().baseColor;
+ float4 uvsets = input.uvsets;
+ uvsets.xy = mad(uvsets.xy, GetMaterial3().texMulAdd.xy, GetMaterial3().texMulAdd.zw);
#ifdef OBJECTSHADER_USE_UVSETS
[branch]
if (GetMaterial3().uvset_baseColorMap >= 0 && (GetFrame().options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0)
{
- float2 uv = GetMaterial3().uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw;
- color2 = texture_blend3_basecolormap.Sample(sampler_objectshader, uv);
- color2.rgb = DEGAMMA(color2.rgb);
+ float2 uv = GetMaterial3().uvset_baseColorMap == 0 ? uvsets.xy : uvsets.zw;
+ float4 basecolorMap = texture_blend3_basecolormap.Sample(sampler_objectshader, uv);
+ basecolorMap.rgb = DEGAMMA(basecolorMap.rgb);
+ color2 *= basecolorMap;
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1432,7 +1448,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial3().uvset_surfaceMap >= 0)
{
- float2 uv = GetMaterial3().uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial3().uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw;
surfaceMap2 = texture_blend3_surfacemap.Sample(sampler_objectshader, uv);
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1445,7 +1461,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial3().normalMapStrength > 0 && GetMaterial3().uvset_normalMap >= 0)
{
- float2 uv = GetMaterial3().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial3().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw;
sam.rgb = texture_blend3_normalmap.Sample(sampler_objectshader, uv).rgb;
sam.rgb = sam.rgb * 2 - 1;
surface2.N = lerp(baseN, mul(sam.rgb, TBN), GetMaterial3().normalMapStrength);
@@ -1458,7 +1474,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial3().uvset_emissiveMap >= 0 && any(surface2.emissiveColor))
{
- float2 uv = GetMaterial3().uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ float2 uv = GetMaterial3().uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw;
sam = texture_blend3_emissivemap.Sample(sampler_objectshader, uv);
sam.rgb = DEGAMMA(sam.rgb);
surface2.emissiveColor *= sam.rgb * sam.a;
@@ -1489,7 +1505,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().IsOcclusionEnabled_Secondary() && GetMaterial().uvset_occlusionMap >= 0)
{
- const float2 UV_occlusionMap = GetMaterial().uvset_occlusionMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_occlusionMap = GetMaterial().uvset_occlusionMap == 0 ? uvsets.xy : uvsets.zw;
surface.occlusion *= texture_occlusionmap.Sample(sampler_objectshader, UV_occlusionMap).r;
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1523,13 +1539,13 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().uvset_sheenColorMap >= 0)
{
- const float2 UV_sheenColorMap = GetMaterial().uvset_sheenColorMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_sheenColorMap = GetMaterial().uvset_sheenColorMap == 0 ? uvsets.xy : uvsets.zw;
surface.sheen.color *= DEGAMMA(texture_sheencolormap.Sample(sampler_objectshader, UV_sheenColorMap).rgb);
}
[branch]
if (GetMaterial().uvset_sheenRoughnessMap >= 0)
{
- const float2 uvset_sheenRoughnessMap = GetMaterial().uvset_sheenRoughnessMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 uvset_sheenRoughnessMap = GetMaterial().uvset_sheenRoughnessMap == 0 ? uvsets.xy : uvsets.zw;
surface.sheen.roughness *= texture_sheenroughnessmap.Sample(sampler_objectshader, uvset_sheenRoughnessMap).a;
}
#endif // OBJECTSHADER_USE_UVSETS
@@ -1545,20 +1561,20 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().uvset_clearcoatMap >= 0)
{
- const float2 UV_clearcoatMap = GetMaterial().uvset_clearcoatMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_clearcoatMap = GetMaterial().uvset_clearcoatMap == 0 ? uvsets.xy : uvsets.zw;
surface.clearcoat.factor *= texture_clearcoatmap.Sample(sampler_objectshader, UV_clearcoatMap).r;
}
[branch]
if (GetMaterial().uvset_clearcoatRoughnessMap >= 0)
{
- const float2 uvset_clearcoatRoughnessMap = GetMaterial().uvset_clearcoatRoughnessMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 uvset_clearcoatRoughnessMap = GetMaterial().uvset_clearcoatRoughnessMap == 0 ? uvsets.xy : uvsets.zw;
surface.clearcoat.roughness *= texture_clearcoatroughnessmap.Sample(sampler_objectshader, uvset_clearcoatRoughnessMap).g;
}
#ifdef OBJECTSHADER_USE_TANGENT
[branch]
if (GetMaterial().uvset_clearcoatNormalMap >= 0)
{
- const float2 uvset_clearcoatNormalMap = GetMaterial().uvset_clearcoatNormalMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 uvset_clearcoatNormalMap = GetMaterial().uvset_clearcoatNormalMap == 0 ? uvsets.xy : uvsets.zw;
float3 clearcoatNormalMap = float3(texture_clearcoatnormalmap.Sample(sampler_objectshader, uvset_clearcoatNormalMap).rg, 1);
clearcoatNormalMap = clearcoatNormalMap * 2 - 1;
surface.clearcoat.N = mul(clearcoatNormalMap, TBN);
@@ -1598,7 +1614,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().uvset_normalMap >= 0)
{
- const float2 UV_normalMap = GetMaterial().uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_normalMap = GetMaterial().uvset_normalMap == 0 ? uvsets.xy : uvsets.zw;
bumpColor0 = 2 * texture_normalmap.Sample(sampler_objectshader, UV_normalMap - GetMaterial().texMulAdd.ww).rg - 1;
bumpColor1 = 2 * texture_normalmap.Sample(sampler_objectshader, UV_normalMap + GetMaterial().texMulAdd.zw).rg - 1;
}
@@ -1634,7 +1650,7 @@ float4 main(PixelInput input, in bool is_frontface : SV_IsFrontFace) : SV_Target
[branch]
if (GetMaterial().uvset_transmissionMap >= 0)
{
- const float2 UV_transmissionMap = GetMaterial().uvset_transmissionMap == 0 ? input.uvsets.xy : input.uvsets.zw;
+ const float2 UV_transmissionMap = GetMaterial().uvset_transmissionMap == 0 ? uvsets.xy : uvsets.zw;
float transmissionMap = texture_transmissionmap.Sample(sampler_objectshader, UV_transmissionMap).r;
surface.transmission *= transmissionMap;
}
diff --git a/WickedEngine/wiArchive.cpp b/WickedEngine/wiArchive.cpp
index 685c44043..e218b382e 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 = 75;
+ static constexpr uint64_t __archiveVersion = 76;
// 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/wiECS.h b/WickedEngine/wiECS.h
index eec045da1..772bf4d32 100644
--- a/WickedEngine/wiECS.h
+++ b/WickedEngine/wiECS.h
@@ -46,7 +46,7 @@ namespace wi::ecs
uint64_t mem;
archive >> mem;
- if (seri.allow_remap)
+ if (mem != INVALID_ENTITY && seri.allow_remap)
{
auto it = seri.remap.find(mem);
if (it == seri.remap.end())
diff --git a/WickedEngine/wiHairParticle.cpp b/WickedEngine/wiHairParticle.cpp
index ca42d6c9e..9cbf9870a 100644
--- a/WickedEngine/wiHairParticle.cpp
+++ b/WickedEngine/wiHairParticle.cpp
@@ -31,6 +31,174 @@ namespace wi
static PipelineState PSO[RENDERPASS_COUNT];
static PipelineState PSO_wire;
+ void HairParticleSystem::CreateRenderData(const wi::scene::MeshComponent& mesh)
+ {
+ GraphicsDevice* device = wi::graphics::GetDevice();
+
+ _flags &= ~REBUILD_BUFFERS;
+ regenerate_frame = true;
+ render_data_updated = false;
+
+ GPUBufferDesc bd;
+ bd.usage = Usage::DEFAULT;
+ bd.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
+ bd.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
+
+ const uint32_t particleCount = strandCount * segmentCount;
+ if (particleCount > 0)
+ {
+ bd.stride = sizeof(PatchSimulationData);
+ bd.size = bd.stride * particleCount;
+ device->CreateBuffer(&bd, nullptr, &simulationBuffer);
+ device->SetName(&simulationBuffer, "HairParticleSystem::simulationBuffer");
+
+ bd.misc_flags = ResourceMiscFlag::BUFFER_RAW;
+ if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
+ {
+ bd.misc_flags |= ResourceMiscFlag::RAY_TRACING;
+ }
+ bd.stride = sizeof(MeshComponent::Vertex_POS);
+ bd.size = bd.stride * 4 * particleCount;
+ device->CreateBuffer(&bd, nullptr, &vertexBuffer_POS[0]);
+ device->SetName(&vertexBuffer_POS[0], "HairParticleSystem::vertexBuffer_POS[0]");
+ device->CreateBuffer(&bd, nullptr, &vertexBuffer_POS[1]);
+ device->SetName(&vertexBuffer_POS[1], "HairParticleSystem::vertexBuffer_POS[1]");
+
+ bd.misc_flags = ResourceMiscFlag::BUFFER_RAW;
+ bd.stride = sizeof(MeshComponent::Vertex_UVS);
+ bd.size = bd.stride * 4 * particleCount;
+ device->CreateBuffer(&bd, nullptr, &vertexBuffer_UVS);
+ device->SetName(&vertexBuffer_UVS, "HairParticleSystem::vertexBuffer_UVS");
+
+ bd.bind_flags = BindFlag::SHADER_RESOURCE;
+ bd.misc_flags = ResourceMiscFlag::NONE;
+ if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
+ {
+ bd.misc_flags |= ResourceMiscFlag::RAY_TRACING;
+ }
+ bd.format = Format::R32_UINT;
+ bd.stride = sizeof(uint);
+ bd.size = bd.stride * 6 * particleCount;
+ wi::vector primitiveData(6 * particleCount);
+ for (uint particleID = 0; particleID < particleCount; ++particleID)
+ {
+ uint v0 = particleID * 4;
+ uint i0 = particleID * 6;
+ primitiveData[i0 + 0] = v0 + 0;
+ primitiveData[i0 + 1] = v0 + 1;
+ primitiveData[i0 + 2] = v0 + 2;
+ primitiveData[i0 + 3] = v0 + 2;
+ primitiveData[i0 + 4] = v0 + 1;
+ primitiveData[i0 + 5] = v0 + 3;
+ }
+ device->CreateBuffer(&bd, primitiveData.data(), &primitiveBuffer);
+ device->SetName(&primitiveBuffer, "HairParticleSystem::primitiveBuffer");
+
+ bd.bind_flags = BindFlag::INDEX_BUFFER | BindFlag::UNORDERED_ACCESS;
+ bd.misc_flags = ResourceMiscFlag::NONE;
+ bd.format = Format::R32_UINT;
+ bd.stride = sizeof(uint);
+ bd.size = bd.stride * 6 * particleCount;
+ device->CreateBuffer(&bd, nullptr, &culledIndexBuffer);
+ device->SetName(&culledIndexBuffer, "HairParticleSystem::culledIndexBuffer");
+ }
+
+ bd.usage = Usage::DEFAULT;
+ bd.size = sizeof(HairParticleCB);
+ bd.bind_flags = BindFlag::CONSTANT_BUFFER;
+ bd.misc_flags = ResourceMiscFlag::NONE;
+ device->CreateBuffer(&bd, nullptr, &constantBuffer);
+ device->SetName(&constantBuffer, "HairParticleSystem::constantBuffer");
+
+ if (vertex_lengths.size() != mesh.vertex_positions.size())
+ {
+ vertex_lengths.resize(mesh.vertex_positions.size());
+ std::fill(vertex_lengths.begin(), vertex_lengths.end(), 1.0f);
+ }
+
+ indices.clear();
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ mesh.GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
+ {
+ const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex];
+ for (size_t i = 0; i < subset.indexCount; i += 3)
+ {
+ const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0];
+ const uint32_t i1 = mesh.indices[subset.indexOffset + i + 1];
+ const uint32_t i2 = mesh.indices[subset.indexOffset + i + 2];
+ if (vertex_lengths[i0] > 0 || vertex_lengths[i1] > 0 || vertex_lengths[i2] > 0)
+ {
+ indices.push_back(i0);
+ indices.push_back(i1);
+ indices.push_back(i2);
+ }
+ }
+ }
+
+ if (!vertex_lengths.empty())
+ {
+ wi::vector ulengths;
+ ulengths.reserve(vertex_lengths.size());
+ for (auto& x : vertex_lengths)
+ {
+ ulengths.push_back(uint8_t(wi::math::Clamp(x, 0, 1) * 255.0f));
+ }
+
+ bd.misc_flags = ResourceMiscFlag::NONE;
+ bd.bind_flags = BindFlag::SHADER_RESOURCE;
+ bd.format = Format::R8_UNORM;
+ bd.stride = sizeof(uint8_t);
+ bd.size = bd.stride * (uint32_t)ulengths.size();
+ device->CreateBuffer(&bd, ulengths.data(), &vertexBuffer_length);
+ device->SetName(&vertexBuffer_length, "HairParticleSystem::vertexBuffer_length");
+ }
+ if (!indices.empty())
+ {
+ bd.misc_flags = ResourceMiscFlag::NONE;
+ bd.bind_flags = BindFlag::SHADER_RESOURCE;
+ bd.format = Format::R32_UINT;
+ bd.stride = sizeof(uint32_t);
+ bd.size = bd.stride * (uint32_t)indices.size();
+ device->CreateBuffer(&bd, indices.data(), &indexBuffer);
+ device->SetName(&indexBuffer, "HairParticleSystem::indexBuffer");
+ }
+
+ if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING) && primitiveBuffer.IsValid())
+ {
+ RaytracingAccelerationStructureDesc desc;
+ desc.type = RaytracingAccelerationStructureDesc::Type::BOTTOMLEVEL;
+ desc.flags |= RaytracingAccelerationStructureDesc::FLAG_ALLOW_UPDATE;
+ desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_BUILD;
+
+ desc.bottom_level.geometries.emplace_back();
+ auto& geometry = desc.bottom_level.geometries.back();
+ geometry.type = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::Type::TRIANGLES;
+ geometry.triangles.vertex_buffer = vertexBuffer_POS[0];
+ geometry.triangles.index_buffer = primitiveBuffer;
+ geometry.triangles.index_format = IndexBufferFormat::UINT32;
+ geometry.triangles.index_count = (uint32_t)(primitiveBuffer.desc.size / primitiveBuffer.desc.stride);
+ geometry.triangles.index_offset = 0;
+ geometry.triangles.vertex_count = (uint32_t)(vertexBuffer_POS[0].desc.size / vertexBuffer_POS[0].desc.stride);
+ geometry.triangles.vertex_format = Format::R32G32B32_FLOAT;
+ geometry.triangles.vertex_stride = sizeof(MeshComponent::Vertex_POS);
+
+ bool success = device->CreateRaytracingAccelerationStructure(&desc, &BLAS);
+ assert(success);
+ device->SetName(&BLAS, "HairParticleSystem::BLAS");
+ }
+
+ if (!indirectBuffer.IsValid())
+ {
+ GPUBufferDesc desc;
+ desc.size = sizeof(uint) + sizeof(IndirectDrawArgsIndexedInstanced); // counter + draw args
+ desc.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::INDIRECT_ARGS;
+ desc.bind_flags = BindFlag::UNORDERED_ACCESS;
+ device->CreateBuffer(&desc, nullptr, &indirectBuffer);
+ }
+ }
+
void HairParticleSystem::UpdateCPU(const TransformComponent& transform, const MeshComponent& mesh, float dt)
{
world = transform.world;
@@ -49,166 +217,9 @@ namespace wi
aabb = AABB(_min, _max);
aabb = aabb.transform(world);
- GraphicsDevice* device = wi::graphics::GetDevice();
-
if (_flags & REBUILD_BUFFERS || !constantBuffer.IsValid() || (strandCount * segmentCount) != simulationBuffer.GetDesc().size / sizeof(PatchSimulationData))
{
- _flags &= ~REBUILD_BUFFERS;
- regenerate_frame = true;
-
- GPUBufferDesc bd;
- bd.usage = Usage::DEFAULT;
- bd.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
- bd.misc_flags = ResourceMiscFlag::BUFFER_STRUCTURED;
-
- const uint32_t particleCount = strandCount * segmentCount;
- if (particleCount > 0)
- {
- bd.stride = sizeof(PatchSimulationData);
- bd.size = bd.stride * particleCount;
- device->CreateBuffer(&bd, nullptr, &simulationBuffer);
- device->SetName(&simulationBuffer, "HairParticleSystem::simulationBuffer");
-
- bd.misc_flags = ResourceMiscFlag::BUFFER_RAW;
- if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
- {
- bd.misc_flags |= ResourceMiscFlag::RAY_TRACING;
- }
- bd.stride = sizeof(MeshComponent::Vertex_POS);
- bd.size = bd.stride * 4 * particleCount;
- device->CreateBuffer(&bd, nullptr, &vertexBuffer_POS[0]);
- device->SetName(&vertexBuffer_POS[0], "HairParticleSystem::vertexBuffer_POS[0]");
- device->CreateBuffer(&bd, nullptr, &vertexBuffer_POS[1]);
- device->SetName(&vertexBuffer_POS[1], "HairParticleSystem::vertexBuffer_POS[1]");
-
- bd.misc_flags = ResourceMiscFlag::BUFFER_RAW;
- bd.stride = sizeof(MeshComponent::Vertex_UVS);
- bd.size = bd.stride * 4 * particleCount;
- device->CreateBuffer(&bd, nullptr, &vertexBuffer_UVS);
- device->SetName(&vertexBuffer_UVS, "HairParticleSystem::vertexBuffer_UVS");
-
- bd.bind_flags = BindFlag::SHADER_RESOURCE;
- bd.misc_flags = ResourceMiscFlag::NONE;
- if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
- {
- bd.misc_flags |= ResourceMiscFlag::RAY_TRACING;
- }
- bd.format = Format::R32_UINT;
- bd.stride = sizeof(uint);
- bd.size = bd.stride * 6 * particleCount;
- wi::vector primitiveData(6 * particleCount);
- for (uint particleID = 0; particleID < particleCount; ++particleID)
- {
- uint v0 = particleID * 4;
- uint i0 = particleID * 6;
- primitiveData[i0 + 0] = v0 + 0;
- primitiveData[i0 + 1] = v0 + 1;
- primitiveData[i0 + 2] = v0 + 2;
- primitiveData[i0 + 3] = v0 + 2;
- primitiveData[i0 + 4] = v0 + 1;
- primitiveData[i0 + 5] = v0 + 3;
- }
- device->CreateBuffer(&bd, primitiveData.data(), &primitiveBuffer);
- device->SetName(&primitiveBuffer, "HairParticleSystem::primitiveBuffer");
-
- bd.bind_flags = BindFlag::INDEX_BUFFER | BindFlag::UNORDERED_ACCESS;
- bd.misc_flags = ResourceMiscFlag::NONE;
- bd.format = Format::R32_UINT;
- bd.stride = sizeof(uint);
- bd.size = bd.stride * 6 * particleCount;
- device->CreateBuffer(&bd, nullptr, &culledIndexBuffer);
- device->SetName(&culledIndexBuffer, "HairParticleSystem::culledIndexBuffer");
- }
-
- bd.usage = Usage::DEFAULT;
- bd.size = sizeof(HairParticleCB);
- bd.bind_flags = BindFlag::CONSTANT_BUFFER;
- bd.misc_flags = ResourceMiscFlag::NONE;
- device->CreateBuffer(&bd, nullptr, &constantBuffer);
- device->SetName(&constantBuffer, "HairParticleSystem::constantBuffer");
-
- if (vertex_lengths.size() != mesh.vertex_positions.size())
- {
- vertex_lengths.resize(mesh.vertex_positions.size());
- std::fill(vertex_lengths.begin(), vertex_lengths.end(), 1.0f);
- }
-
- indices.clear();
- for (size_t j = 0; j < mesh.indices.size(); j += 3)
- {
- const uint32_t triangle[] = {
- mesh.indices[j + 0],
- mesh.indices[j + 1],
- mesh.indices[j + 2],
- };
- if (vertex_lengths[triangle[0]] > 0 || vertex_lengths[triangle[1]] > 0 || vertex_lengths[triangle[2]] > 0)
- {
- indices.push_back(triangle[0]);
- indices.push_back(triangle[1]);
- indices.push_back(triangle[2]);
- }
- }
-
- if (!vertex_lengths.empty())
- {
- wi::vector ulengths;
- ulengths.reserve(vertex_lengths.size());
- for (auto& x : vertex_lengths)
- {
- ulengths.push_back(uint8_t(wi::math::Clamp(x, 0, 1) * 255.0f));
- }
-
- bd.misc_flags = ResourceMiscFlag::NONE;
- bd.bind_flags = BindFlag::SHADER_RESOURCE;
- bd.format = Format::R8_UNORM;
- bd.stride = sizeof(uint8_t);
- bd.size = bd.stride * (uint32_t)ulengths.size();
- device->CreateBuffer(&bd, ulengths.data(), &vertexBuffer_length);
- device->SetName(&vertexBuffer_length, "HairParticleSystem::vertexBuffer_length");
- }
- if (!indices.empty())
- {
- bd.misc_flags = ResourceMiscFlag::NONE;
- bd.bind_flags = BindFlag::SHADER_RESOURCE;
- bd.format = Format::R32_UINT;
- bd.stride = sizeof(uint32_t);
- bd.size = bd.stride * (uint32_t)indices.size();
- device->CreateBuffer(&bd, indices.data(), &indexBuffer);
- device->SetName(&indexBuffer, "HairParticleSystem::indexBuffer");
- }
-
- if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING) && primitiveBuffer.IsValid())
- {
- RaytracingAccelerationStructureDesc desc;
- desc.type = RaytracingAccelerationStructureDesc::Type::BOTTOMLEVEL;
- desc.flags |= RaytracingAccelerationStructureDesc::FLAG_ALLOW_UPDATE;
- desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_BUILD;
-
- desc.bottom_level.geometries.emplace_back();
- auto& geometry = desc.bottom_level.geometries.back();
- geometry.type = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::Type::TRIANGLES;
- geometry.triangles.vertex_buffer = vertexBuffer_POS[0];
- geometry.triangles.index_buffer = primitiveBuffer;
- geometry.triangles.index_format = IndexBufferFormat::UINT32;
- geometry.triangles.index_count = (uint32_t)(primitiveBuffer.desc.size / primitiveBuffer.desc.stride);
- geometry.triangles.index_offset = 0;
- geometry.triangles.vertex_count = (uint32_t)(vertexBuffer_POS[0].desc.size / vertexBuffer_POS[0].desc.stride);
- geometry.triangles.vertex_format = Format::R32G32B32_FLOAT;
- geometry.triangles.vertex_stride = sizeof(MeshComponent::Vertex_POS);
-
- bool success = device->CreateRaytracingAccelerationStructure(&desc, &BLAS);
- assert(success);
- device->SetName(&BLAS, "HairParticleSystem::BLAS");
- }
- }
-
- if (!indirectBuffer.IsValid())
- {
- GPUBufferDesc desc;
- desc.size = sizeof(uint) + sizeof(IndirectDrawArgsIndexedInstanced); // counter + draw args
- desc.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::INDIRECT_ARGS;
- desc.bind_flags = BindFlag::UNORDERED_ACCESS;
- device->CreateBuffer(&desc, nullptr, &indirectBuffer);
+ CreateRenderData(mesh);
}
std::swap(vertexBuffer_POS[0], vertexBuffer_POS[1]);
@@ -217,7 +228,7 @@ namespace wi
{
BLAS.desc.bottom_level.geometries.back().triangles.vertex_buffer = vertexBuffer_POS[0];
}
-
+ render_data_updated = false;
}
void HairParticleSystem::UpdateGPU(uint32_t instanceIndex, const MeshComponent& mesh, const MaterialComponent& material, CommandList cmd) const
{
@@ -245,7 +256,7 @@ namespace wi
hcb.xHairParticleCount = hcb.xHairStrandCount * hcb.xHairSegmentCount;
hcb.xHairRandomSeed = randomSeed;
hcb.xHairViewDistance = viewDistance;
- hcb.xHairBaseMeshIndexCount = (indices.empty() ? (uint)mesh.indices.size() : (uint)indices.size());
+ hcb.xHairBaseMeshIndexCount = (uint)indices.size();
hcb.xHairBaseMeshVertexPositionStride = sizeof(MeshComponent::Vertex_POS);
// segmentCount will be loop in the shader, not a threadgroup so we don't need it here:
hcb.xHairNumDispatchGroups = (hcb.xHairParticleCount + THREADCOUNT_SIMULATEHAIR - 1) / THREADCOUNT_SIMULATEHAIR;
@@ -265,6 +276,22 @@ namespace wi
device->Barrier(barriers, arraysize(barriers), cmd);
}
+ if (regenerate_frame)
+ {
+ device->ClearUAV(&simulationBuffer, 0, cmd);
+ device->ClearUAV(&vertexBuffer_POS[0], 0, cmd);
+ device->ClearUAV(&vertexBuffer_POS[1], 0, cmd);
+ device->ClearUAV(&vertexBuffer_UVS, 0, cmd);
+ device->ClearUAV(&culledIndexBuffer, 0, cmd);
+ device->ClearUAV(&indirectBuffer, 0, cmd);
+ {
+ GPUBarrier barriers[] = {
+ GPUBarrier::Memory(),
+ };
+ device->Barrier(barriers, arraysize(barriers), cmd);
+ }
+ }
+
// Simulate:
{
device->BindComputeShader(&cs_simulate, cmd);
@@ -331,6 +358,7 @@ namespace wi
device->EventEnd(cmd);
regenerate_frame = false;
+ render_data_updated = true;
}
void HairParticleSystem::Draw(const MaterialComponent& material, wi::enums::RENDERPASS renderPass, CommandList cmd) const
@@ -352,7 +380,6 @@ namespace wi
return;
}
device->BindPipelineState(&PSO_wire, cmd);
- device->BindResource(wi::texturehelper::getWhite(), 0, cmd);
}
else
{
diff --git a/WickedEngine/wiHairParticle.h b/WickedEngine/wiHairParticle.h
index 82dca0cf1..ef689a3b4 100644
--- a/WickedEngine/wiHairParticle.h
+++ b/WickedEngine/wiHairParticle.h
@@ -31,6 +31,8 @@ namespace wi
wi::graphics::RaytracingAccelerationStructure BLAS;
+ void CreateRenderData(const wi::scene::MeshComponent& mesh);
+
void UpdateCPU(
const wi::scene::TransformComponent& transform,
const wi::scene::MeshComponent& mesh,
@@ -80,6 +82,7 @@ namespace wi
wi::vector indices; // it is dependent on vertex_lengths and contains triangles with non-zero lengths
uint32_t layerMask = ~0u;
mutable bool regenerate_frame = true;
+ mutable bool render_data_updated = false;
void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri);
diff --git a/WickedEngine/wiMath.h b/WickedEngine/wiMath.h
index 33aace5ce..a871a3e47 100644
--- a/WickedEngine/wiMath.h
+++ b/WickedEngine/wiMath.h
@@ -3,6 +3,7 @@
#include
#include
+#include
#if __has_include("DirectXMath.h")
// In this case, DirectXMath is coming from Windows SDK.
@@ -76,12 +77,24 @@ namespace wi::math
XMVECTOR vector2 = XMLoadFloat3(&v2);
return Distance(vector1, vector2);
}
+ inline float DistanceSquared(const XMFLOAT2& v1, const XMFLOAT2& v2)
+ {
+ XMVECTOR vector1 = XMLoadFloat2(&v1);
+ XMVECTOR vector2 = XMLoadFloat2(&v2);
+ return XMVectorGetX(XMVector2LengthSq(vector2 - vector1));
+ }
inline float DistanceSquared(const XMFLOAT3& v1, const XMFLOAT3& v2)
{
XMVECTOR vector1 = XMLoadFloat3(&v1);
XMVECTOR vector2 = XMLoadFloat3(&v2);
return DistanceSquared(vector1, vector2);
}
+ inline float DistanceEstimated(const XMFLOAT2& v1, const XMFLOAT2& v2)
+ {
+ XMVECTOR vector1 = XMLoadFloat2(&v1);
+ XMVECTOR vector2 = XMLoadFloat2(&v2);
+ return XMVectorGetX(XMVector2LengthEst(vector2 - vector1));
+ }
inline float DistanceEstimated(const XMFLOAT3& v1, const XMFLOAT3& v2)
{
XMVECTOR vector1 = XMLoadFloat3(&v1);
@@ -267,10 +280,20 @@ namespace wi::math
// Ray-Triangle Intersection", Journal of Graphics Tools, vol. 2, no. 1,
// pp 21-28, 1997.
//
- // Modified for WickedEngine to return barycentrics
+ // Modified for WickedEngine to return barycentrics and support TMin, TMax
//-----------------------------------------------------------------------------
_Use_decl_annotations_
- inline bool XM_CALLCONV RayTriangleIntersects(FXMVECTOR Origin, FXMVECTOR Direction, FXMVECTOR V0, GXMVECTOR V1, HXMVECTOR V2, float& Dist, XMFLOAT2& bary)
+ inline bool XM_CALLCONV RayTriangleIntersects(
+ FXMVECTOR Origin,
+ FXMVECTOR Direction,
+ FXMVECTOR V0,
+ GXMVECTOR V1,
+ HXMVECTOR V2,
+ float& Dist,
+ XMFLOAT2& bary,
+ float TMin = 0,
+ float TMax = std::numeric_limits::max()
+ )
{
const XMVECTOR g_RayEpsilon = XMVectorSet(1e-20f, 1e-20f, 1e-20f, 1e-20f);
const XMVECTOR g_RayNegEpsilon = XMVectorSet(-1e-20f, -1e-20f, -1e-20f, -1e-20f);
@@ -366,6 +389,9 @@ namespace wi::math
// Store the x-component to *pDist
XMStoreFloat(&Dist, t);
+ if (Dist > TMax || Dist < TMin)
+ return false;
+
return true;
}
};
diff --git a/WickedEngine/wiNoise.h b/WickedEngine/wiNoise.h
new file mode 100644
index 000000000..a22a0584d
--- /dev/null
+++ b/WickedEngine/wiNoise.h
@@ -0,0 +1,155 @@
+#pragma once
+#include "CommonInclude.h"
+#include "wiMath.h"
+
+#include
+
+namespace wi::noise
+{
+ // Based on: https://github.com/Reputeless/PerlinNoise
+ struct Perlin
+ {
+ uint8_t state[256];
+
+ void init(uint32_t seed)
+ {
+ std::mt19937 perlin_rand(seed);
+ std::uniform_int_distribution perlin_distr(0, 255);
+ for (int i = 0; i < arraysize(state); ++i)
+ {
+ state[i] = perlin_distr(perlin_rand);
+ }
+ }
+ constexpr float fade(float t) const
+ {
+ return t * t * t * (t * (t * 6 - 15) + 10);
+ }
+ constexpr float lerp(float a, float b, float t) const
+ {
+ return (a + (b - a) * t);
+ }
+ constexpr float grad(uint8_t hash, float x, float y, float z) const
+ {
+ const uint8_t h = hash & 15;
+ const float u = h < 8 ? x : y;
+ const float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+ return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+ }
+ // returns noise in range [-1, 1]
+ inline float compute(float x, float y, float z) const
+ {
+ const float _x = std::floor(x);
+ const float _y = std::floor(y);
+ const float _z = std::floor(z);
+
+ const int ix = int(_x) & 255;
+ const int iy = int(_y) & 255;
+ const int iz = int(_z) & 255;
+
+ const float fx = (x - _x);
+ const float fy = (y - _y);
+ const float fz = (z - _z);
+
+ const float u = fade(fx);
+ const float v = fade(fy);
+ const float w = fade(fz);
+
+ const uint8_t A = (state[ix & 255] + iy) & 255;
+ const uint8_t B = (state[(ix + 1) & 255] + iy) & 255;
+
+ const uint8_t AA = (state[A] + iz) & 255;
+ const uint8_t AB = (state[(A + 1) & 255] + iz) & 255;
+
+ const uint8_t BA = (state[B] + iz) & 255;
+ const uint8_t BB = (state[(B + 1) & 255] + iz) & 255;
+
+ const float p0 = grad(state[AA], fx, fy, fz);
+ const float p1 = grad(state[BA], fx - 1, fy, fz);
+ const float p2 = grad(state[AB], fx, fy - 1, fz);
+ const float p3 = grad(state[BB], fx - 1, fy - 1, fz);
+ const float p4 = grad(state[(AA + 1) & 255], fx, fy, fz - 1);
+ const float p5 = grad(state[(BA + 1) & 255], fx - 1, fy, fz - 1);
+ const float p6 = grad(state[(AB + 1) & 255], fx, fy - 1, fz - 1);
+ const float p7 = grad(state[(BB + 1) & 255], fx - 1, fy - 1, fz - 1);
+
+ const float q0 = lerp(p0, p1, u);
+ const float q1 = lerp(p2, p3, u);
+ const float q2 = lerp(p4, p5, u);
+ const float q3 = lerp(p6, p7, u);
+
+ const float r0 = lerp(q0, q1, v);
+ const float r1 = lerp(q2, q3, v);
+
+ return lerp(r0, r1, w);
+ }
+ // returns noise in range [-1, 1]
+ constexpr float compute(float x, float y, float z, int octaves, float persistence = 0.5f) const
+ {
+ float result = 0;
+ float amplitude = 1;
+ for (int i = 0; i < octaves; ++i)
+ {
+ result += (compute(x, y, z) * amplitude);
+ x *= 2;
+ y *= 2;
+ z *= 2;
+ amplitude *= persistence;
+ }
+ return result;
+ }
+ };
+
+ // Based on: https://www.shadertoy.com/view/MslGD8
+ namespace voronoi
+ {
+ inline XMVECTOR fract(XMVECTOR p)
+ {
+ return p - XMVectorFloor(p);
+ }
+ inline XMVECTOR hash(XMVECTOR p)
+ {
+ p = XMVectorSet(
+ XMVectorGetX(XMVector2Dot(p, XMVectorSet(127.1f, 311.7f, 0, 0))),
+ XMVectorGetX(XMVector2Dot(p, XMVectorSet(269.5f, 183.3f, 0, 0))),
+ 0,
+ 0
+ );
+ return fract(XMVectorSin(p) * 18.5453f);
+ }
+ struct Result
+ {
+ float distance;
+ float cell_id;
+ };
+ inline Result compute(float x, float y, float seed)
+ {
+ Result result = {};
+
+ XMVECTOR p = XMVectorSet(x, y, 0, 0);
+ XMVECTOR n = XMVectorFloor(p);
+ XMVECTOR f = fract(p);
+
+ XMVECTOR m = XMVectorSet(8, 0, 0, 0);
+ for (int j = -1; j <= 1; j++)
+ {
+ for (int i = -1; i <= 1; i++)
+ {
+ XMVECTOR g = XMVectorSet(float(i), float(j), 0, 0);
+ XMVECTOR o = hash(n + g);
+ //XMVECTOR r = g - f + o;
+ XMVECTOR r = g - f + (XMVectorReplicate(0.5f) + 0.5f * XMVectorSin(seed * o));
+ float d = XMVectorGetX(XMVector2Dot(r, r));
+ if (d < XMVectorGetX(m))
+ {
+ m = XMVectorSet(d, XMVectorGetX(o), XMVectorGetY(o), 0);
+ }
+ }
+ }
+
+ result.distance = XMVectorGetX(XMVectorSqrt(m));
+ result.cell_id = XMVectorGetY(m) + XMVectorGetZ(m);
+
+ return result;
+ }
+ };
+}
diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h
index 40f75d89d..005152968 100644
--- a/WickedEngine/wiOcean.h
+++ b/WickedEngine/wiOcean.h
@@ -49,6 +49,18 @@ namespace wi
bool IsValid() const { return displacementMap.IsValid(); }
+ // 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;
+ }
+
protected:
wi::graphics::Texture displacementMap; // (RGBA32F)
wi::graphics::Texture gradientMap; // (RGBA16F)
diff --git a/WickedEngine/wiPrimitive.cpp b/WickedEngine/wiPrimitive.cpp
index c4ad80818..09a7f15f2 100644
--- a/WickedEngine/wiPrimitive.cpp
+++ b/WickedEngine/wiPrimitive.cpp
@@ -136,18 +136,24 @@ namespace wi::primitive
float tmin = std::min(tx1, tx2);
float tmax = std::max(tx1, tx2);
+ if (ray.TMax < tmin || ray.TMin > tmax)
+ return false;
float ty1 = (MIN.y - ray.origin.y) * ray.direction_inverse.y;
float ty2 = (MAX.y - ray.origin.y) * ray.direction_inverse.y;
tmin = std::max(tmin, std::min(ty1, ty2));
tmax = std::min(tmax, std::max(ty1, ty2));
+ if (ray.TMax < tmin || ray.TMin > tmax)
+ return false;
float tz1 = (MIN.z - ray.origin.z) * ray.direction_inverse.z;
float tz2 = (MAX.z - ray.origin.z) * ray.direction_inverse.z;
tmin = std::max(tmin, std::min(tz1, tz2));
tmax = std::min(tmax, std::max(tz1, tz2));
+ if (ray.TMax < tmin || ray.TMin > tmax)
+ return false;
return tmax >= tmin;
}
diff --git a/WickedEngine/wiPrimitive.h b/WickedEngine/wiPrimitive.h
index f14f5b6c0..87856e1b7 100644
--- a/WickedEngine/wiPrimitive.h
+++ b/WickedEngine/wiPrimitive.h
@@ -103,13 +103,19 @@ namespace wi::primitive
};
struct Ray
{
- XMFLOAT3 origin, direction, direction_inverse;
+ XMFLOAT3 origin;
+ float TMin = 0;
+ XMFLOAT3 direction;
+ float TMax = std::numeric_limits::max();
+ XMFLOAT3 direction_inverse;
- Ray(const XMFLOAT3& newOrigin = XMFLOAT3(0, 0, 0), const XMFLOAT3& newDirection = XMFLOAT3(0, 0, 1)) : Ray(XMLoadFloat3(&newOrigin), XMLoadFloat3(&newDirection)) {}
- Ray(const XMVECTOR& newOrigin, const XMVECTOR& newDirection) {
+ Ray(const XMFLOAT3& newOrigin = XMFLOAT3(0, 0, 0), const XMFLOAT3& newDirection = XMFLOAT3(0, 0, 1), float newTMin = 0, float newTMax = std::numeric_limits::max()) : Ray(XMLoadFloat3(&newOrigin), XMLoadFloat3(&newDirection), TMin, TMax) {}
+ Ray(const XMVECTOR& newOrigin, const XMVECTOR& newDirection, float newTMin = 0, float newTMax = std::numeric_limits::max()) {
XMStoreFloat3(&origin, newOrigin);
XMStoreFloat3(&direction, newDirection);
XMStoreFloat3(&direction_inverse, XMVectorDivide(XMVectorReplicate(1.0f), newDirection));
+ TMin = newTMin;
+ TMax = newTMax;
}
bool intersects(const AABB& b) const;
bool intersects(const Sphere& b) const;
diff --git a/WickedEngine/wiPrimitive_BindLua.cpp b/WickedEngine/wiPrimitive_BindLua.cpp
index 015e1e69f..359de7822 100644
--- a/WickedEngine/wiPrimitive_BindLua.cpp
+++ b/WickedEngine/wiPrimitive_BindLua.cpp
@@ -46,6 +46,14 @@ namespace wi::lua::primitive
if (o && d)
{
ray = Ray(XMLoadFloat4(o), XMLoadFloat4(d));
+ if (argc > 2)
+ {
+ ray.TMin = wi::lua::SGetFloat(L, 3);
+ }
+ if (argc > 3)
+ {
+ ray.TMax = wi::lua::SGetFloat(L, 4);
+ }
}
else
{
diff --git a/WickedEngine/wiRenderPath3D.cpp b/WickedEngine/wiRenderPath3D.cpp
index 6706d978a..31763b84a 100644
--- a/WickedEngine/wiRenderPath3D.cpp
+++ b/WickedEngine/wiRenderPath3D.cpp
@@ -519,6 +519,7 @@ void RenderPath3D::Update(float dt)
}
scene->Update(dt * wi::renderer::GetGameSpeed());
+ scene->UpdateLODsForCamera(*camera);
}
// Frustum culling for main camera:
diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp
index 2d59a2e03..303661558 100644
--- a/WickedEngine/wiRenderer.cpp
+++ b/WickedEngine/wiRenderer.cpp
@@ -1854,9 +1854,9 @@ void SetUpStates()
rs.fill_mode = FillMode::SOLID;
rs.cull_mode = CullMode::NONE;
rs.front_counter_clockwise = true;
- rs.depth_bias = 2;
+ rs.depth_bias = 0;
rs.depth_bias_clamp = 0;
- rs.slope_scaled_depth_bias = 2;
+ rs.slope_scaled_depth_bias = 0;
rs.depth_clip_enable = false;
rs.multisample_enable = false;
rs.antialiased_line_enable = false;
@@ -2402,6 +2402,7 @@ void RenderMeshes(
uint8_t userStencilRefOverride = 0;
bool forceAlphatestForDithering = false;
AABB aabb;
+ uint32_t lod = 0;
} instancedBatch = {};
@@ -2426,7 +2427,10 @@ void RenderMeshes(
device->BindIndexBuffer(&mesh.generalBuffer, mesh.GetIndexFormat(), mesh.ib.offset, cmd);
- for (size_t subsetIndex = 0; subsetIndex < mesh.subsets.size(); ++subsetIndex)
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ mesh.GetLODSubsetRange(instancedBatch.lod, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
{
const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex];
if (subset.indexCount == 0)
@@ -2539,7 +2543,10 @@ void RenderMeshes(
const uint8_t userStencilRefOverride = instance.userStencilRef;
// When we encounter a new mesh inside the global instance array, we begin a new RenderBatch:
- if (meshIndex != instancedBatch.meshIndex || userStencilRefOverride != instancedBatch.userStencilRefOverride)
+ if (meshIndex != instancedBatch.meshIndex ||
+ userStencilRefOverride != instancedBatch.userStencilRefOverride ||
+ instance.lod != instancedBatch.lod
+ )
{
batch_flush();
@@ -2550,6 +2557,7 @@ void RenderMeshes(
instancedBatch.userStencilRefOverride = userStencilRefOverride;
instancedBatch.forceAlphatestForDithering = 0;
instancedBatch.aabb = AABB();
+ instancedBatch.lod = instance.lod;
}
float dither = instance.GetTransparency();
@@ -2941,6 +2949,10 @@ void UpdateVisibility(Visibility& vis)
{
continue;
}
+ const float dist = wi::math::Distance(vis.camera->Eye, hair.aabb.getCenter());
+ const float radius = hair.aabb.getRadius();
+ if (dist - radius > hair.viewDistance)
+ continue;
if (hair.meshID == INVALID_ENTITY || !vis.frustum.CheckBoxFast(hair.aabb))
{
continue;
@@ -2964,12 +2976,25 @@ void UpdateVisibility(Visibility& vis)
vis.visibleObjects.resize((size_t)vis.object_counter.load());
vis.visibleDecals.resize((size_t)vis.decal_counter.load());
- if ((vis.flags & Visibility::ALLOW_REQUEST_REFLECTION) && vis.scene->weather.IsOceanEnabled())
+ if (vis.scene->weather.IsOceanEnabled())
{
- // Ocean will override any current reflectors
- vis.planar_reflection_visible = true;
- XMVECTOR _refPlane = XMPlaneFromPointNormal(XMVectorSet(0, vis.scene->weather.oceanParameters.waterHeight, 0, 0), XMVectorSet(0, 1, 0, 0));
- XMStoreFloat4(&vis.reflectionPlane, _refPlane);
+ bool occluded = false;
+ if (vis.flags & Visibility::ALLOW_OCCLUSION_CULLING)
+ {
+ vis.scene->ocean.occlusionQueries[vis.scene->queryheap_idx] = vis.scene->queryAllocator.fetch_add(1); // allocate new occlusion query from heap
+ if (vis.scene->ocean.IsOccluded())
+ {
+ occluded = true;
+ }
+ }
+
+ if ((vis.flags & Visibility::ALLOW_REQUEST_REFLECTION) && !occluded)
+ {
+ // Ocean will override any current reflectors
+ vis.planar_reflection_visible = true;
+ XMVECTOR _refPlane = XMPlaneFromPointNormal(XMVectorSet(0, vis.scene->weather.oceanParameters.waterHeight, 0, 0), XMVectorSet(0, 1, 0, 0));
+ XMStoreFloat4(&vis.reflectionPlane, _refPlane);
+ }
}
wi::profiler::EndRange(range); // Frustum Culling
@@ -3853,9 +3878,12 @@ void UpdateRenderDataAsync(
// Compute water simulation:
if (vis.scene->weather.IsOceanEnabled())
{
- auto range = wi::profiler::BeginRangeGPU("Ocean - Simulate", cmd);
- vis.scene->ocean.UpdateDisplacementMap(vis.scene->weather.oceanParameters, cmd);
- wi::profiler::EndRange(range);
+ if (!GetOcclusionCullingEnabled() || !vis.scene->ocean.IsOccluded())
+ {
+ auto range = wi::profiler::BeginRangeGPU("Ocean - Simulate", cmd);
+ vis.scene->ocean.UpdateDisplacementMap(vis.scene->weather.oceanParameters, cmd);
+ wi::profiler::EndRange(range);
+ }
}
device->EventEnd(cmd);
@@ -3908,7 +3936,7 @@ void UpdateRaytracingAccelerationStructures(const Scene& scene, CommandList cmd)
{
const wi::HairParticleSystem& hair = scene.hairs[i];
- if (hair.meshID != INVALID_ENTITY && hair.BLAS.IsValid())
+ if (hair.meshID != INVALID_ENTITY && hair.BLAS.IsValid() && hair.render_data_updated)
{
device->BuildRaytracingAccelerationStructure(&hair.BLAS, cmd, nullptr);
}
@@ -4006,11 +4034,11 @@ void OcclusionCulling_Render(const CameraComponent& camera, const Visibility& vi
XMMATRIX VP = camera.GetViewProjection();
const GPUQueryHeap& queryHeap = vis.scene->queryHeap;
+ int query_write = vis.scene->queryheap_idx;
if (!vis.visibleObjects.empty())
{
device->EventBegin("Occlusion Culling Objects", cmd);
- int query_write = vis.scene->queryheap_idx;
for (uint32_t instanceIndex : vis.visibleObjects)
{
@@ -4057,6 +4085,29 @@ void OcclusionCulling_Render(const CameraComponent& camera, const Visibility& vi
device->EventEnd(cmd);
}
+ if (vis.scene->weather.IsOceanEnabled())
+ {
+ int queryIndex = vis.scene->ocean.occlusionQueries[query_write];
+ if (queryIndex >= 0)
+ {
+ device->EventBegin("Occlusion Culling Ocean", cmd);
+
+ AABB aabb;
+ aabb.createFromHalfWidth(
+ XMFLOAT3(vis.camera->Eye.x, vis.scene->weather.oceanParameters.waterHeight, vis.camera->Eye.z),
+ XMFLOAT3(vis.camera->zFarP, 1, vis.camera->zFarP)
+ );
+ const XMMATRIX transform = aabb.getAsBoxMatrix() * VP;
+ device->PushConstants(&transform, sizeof(transform), cmd);
+
+ device->QueryBegin(&queryHeap, queryIndex, cmd);
+ device->Draw(14, 0, cmd);
+ device->QueryEnd(&queryHeap, queryIndex, cmd);
+
+ device->EventEnd(cmd);
+ }
+ }
+
wi::profiler::EndRange(range); // Occlusion Culling Render
}
void OcclusionCulling_Resolve(const Visibility& vis, CommandList cmd)
@@ -4871,7 +4922,10 @@ void DrawScene(
if (transparent && vis.scene->weather.IsOceanEnabled())
{
- vis.scene->ocean.Render(*vis.camera, vis.scene->weather.oceanParameters, cmd);
+ if (!occlusion || !vis.scene->ocean.IsOccluded())
+ {
+ vis.scene->ocean.Render(*vis.camera, vis.scene->weather.oceanParameters, cmd);
+ }
}
if (IsWireRender() && !transparent)
@@ -4927,7 +4981,7 @@ void DrawScene(
if (hairparticle)
{
- if (!transparent)
+ if (IsWireRender() || !transparent)
{
for (uint32_t hairIndex : vis.visibleHairs)
{
@@ -6452,7 +6506,10 @@ void RefreshImpostors(const Scene& scene, CommandList cmd)
viewport.width = (float)scene.impostorTextureDim;
device->BindViewports(1, &viewport, cmd);
- for (size_t subsetIndex = 0; subsetIndex < mesh.subsets.size(); ++subsetIndex)
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ mesh.GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
{
const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex];
if (subset.indexCount == 0)
diff --git a/WickedEngine/wiResourceManager.cpp b/WickedEngine/wiResourceManager.cpp
index 06f91369c..73ca18c89 100644
--- a/WickedEngine/wiResourceManager.cpp
+++ b/WickedEngine/wiResourceManager.cpp
@@ -416,6 +416,7 @@ namespace wi
case tinyddsloader::DDSFile::DXGIFormat::R10G10B10A2_UNorm: desc.format = Format::R10G10B10A2_UNORM; break;
case tinyddsloader::DDSFile::DXGIFormat::R10G10B10A2_UInt: desc.format = Format::R10G10B10A2_UINT; break;
case tinyddsloader::DDSFile::DXGIFormat::R11G11B10_Float: desc.format = Format::R11G11B10_FLOAT; break;
+ case tinyddsloader::DDSFile::DXGIFormat::B8G8R8X8_UNorm: desc.format = Format::B8G8R8A8_UNORM; break;
case tinyddsloader::DDSFile::DXGIFormat::B8G8R8A8_UNorm: desc.format = Format::B8G8R8A8_UNORM; break;
case tinyddsloader::DDSFile::DXGIFormat::B8G8R8A8_UNorm_SRGB: desc.format = Format::B8G8R8A8_UNORM_SRGB; break;
case tinyddsloader::DDSFile::DXGIFormat::R8G8B8A8_UNorm: desc.format = Format::R8G8B8A8_UNORM; break;
diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp
index 843c88ef3..affdbcb9b 100644
--- a/WickedEngine/wiScene.cpp
+++ b/WickedEngine/wiScene.cpp
@@ -404,82 +404,93 @@ namespace wi::scene
// Generate tangents if not found:
vertex_tangents.resize(vertex_positions.size());
- for (size_t i = 0; i < indices.size(); i += 3)
+ 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 uint32_t i0 = indices[i + 0];
- const uint32_t i1 = indices[i + 1];
- const uint32_t i2 = indices[i + 2];
+ 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 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 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 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 nor0 = XMLoadFloat3(&n0);
+ const XMVECTOR nor1 = XMLoadFloat3(&n1);
+ const XMVECTOR nor2 = XMLoadFloat3(&n2);
- const XMVECTOR facenormal = XMVector3Normalize(nor0 + nor1 + nor2);
+ 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 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 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);
+ 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;
+ 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);
+ 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[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[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_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 subsetCounter = 0;
- for (auto& subset : subsets)
{
- for (uint32_t i = 0; i < subset.indexCount; ++i)
+ 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)
{
- uint32_t index = indices[subset.indexOffset + i];
- vertex_subsets[index] = subsetCounter;
+ 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;
+ }
}
- subsetCounter++;
}
const size_t uv_count = std::max(vertex_uvset_0.size(), vertex_uvset_1.size());
@@ -698,8 +709,12 @@ namespace wi::scene
desc.flags |= RaytracingAccelerationStructureDesc::FLAG_PREFER_FAST_TRACE;
}
- for (auto& subset : subsets)
+ 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];
desc.bottom_level.geometries.emplace_back();
auto& geometry = desc.bottom_level.geometries.back();
geometry.type = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::Type::TRIANGLES;
@@ -1523,7 +1538,7 @@ namespace wi::scene
// Occlusion culling read:
if(wi::renderer::GetOcclusionCullingEnabled() && !wi::renderer::GetFreezeCullingCameraEnabled())
{
- uint32_t minQueryCount = uint32_t(objects.GetCount() + lights.GetCount());
+ uint32_t minQueryCount = uint32_t(objects.GetCount() + lights.GetCount() + 1); // +1 : ocean
if (queryHeap.desc.query_count < minQueryCount)
{
GPUQueryHeapDesc desc;
@@ -3075,7 +3090,8 @@ namespace wi::scene
if (material != nullptr)
{
subset.materialIndex = (uint32_t)materials.GetIndex(subset.materialID);
- if (mesh.BLAS.IsValid())
+ const uint32_t lod_index = mesh.subsets_per_lod > 0 ? subsetIndex / mesh.subsets_per_lod : 0;
+ if (lod_index == 0 && mesh.BLAS.IsValid())
{
auto& geometry = mesh.BLAS.desc.bottom_level.geometries[subsetIndex];
uint32_t flags = geometry.flags;
@@ -3278,7 +3294,7 @@ namespace wi::scene
uint32_t transform_index = (uint32_t)transforms.GetIndex(entity);
const TransformComponent& transform = transforms[transform_index];
- if (object.mesh_index >= 0)
+ if (object.mesh_index != ~0u)
{
const MeshComponent& mesh = meshes[object.mesh_index];
@@ -3302,8 +3318,12 @@ namespace wi::scene
}
}
- for (auto& subset : mesh.subsets)
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ mesh.GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
{
+ const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex];
const MaterialComponent* material = materials.GetComponent(subset.materialID);
if (material != nullptr)
@@ -3926,6 +3946,26 @@ namespace wi::scene
{
OceanRegenerate();
}
+
+ // Ocean occlusion status:
+ if (!wi::renderer::GetFreezeCullingCameraEnabled() && weather.IsOceanEnabled())
+ {
+ ocean.occlusionHistory <<= 1u; // advance history by 1 frame
+ int query_id = ocean.occlusionQueries[queryheap_idx];
+ if (queryResultBuffer[queryheap_idx].mapped_data != nullptr && query_id >= 0)
+ {
+ uint64_t visible = ((uint64_t*)queryResultBuffer[queryheap_idx].mapped_data)[query_id];
+ if (visible)
+ {
+ ocean.occlusionHistory |= 1; // visible
+ }
+ }
+ else
+ {
+ ocean.occlusionHistory |= 1; // visible
+ }
+ }
+ ocean.occlusionQueries[queryheap_idx] = -1; // invalidate query
}
}
void Scene::RunSoundUpdateSystem(wi::jobsystem::context& ctx)
@@ -3966,6 +4006,37 @@ namespace wi::scene
}
}
+ void Scene::UpdateLODsForCamera(const CameraComponent& camera)
+ {
+ wi::jobsystem::context ctx;
+ wi::jobsystem::Dispatch(ctx, (uint32_t)objects.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) {
+ ObjectComponent& object = objects[args.jobIndex];
+ if (object.meshID == INVALID_ENTITY)
+ return;
+ const AABB& aabb = aabb_objects[args.jobIndex];
+ const float distsq = wi::math::DistanceSquared(camera.Eye, aabb.getCenter());
+ const float radius = aabb.getRadius();
+ const float radiussq = radius * radius;
+ if (distsq < radiussq)
+ {
+ object.lod = 0;
+ }
+ else
+ {
+ const MeshComponent* mesh = meshes.GetComponent(object.meshID);
+ if (mesh != nullptr && mesh->subsets_per_lod > 0)
+ {
+ const float dist = std::sqrt(distsq);
+ const float dist_to_sphere = dist - radius;
+ object.lod = uint32_t(dist_to_sphere * object.lod_distance_multiplier);
+ object.lod = std::min(object.lod, mesh->GetLODCount() - 1);
+ }
+ }
+
+ });
+ wi::jobsystem::Wait(ctx);
+ }
+
void Scene::PutWaterRipple(const std::string& image, const XMFLOAT3& pos)
{
wi::Sprite img(image);
@@ -4133,9 +4204,12 @@ namespace wi::scene
const ArmatureComponent* armature = mesh.IsSkinned() ? scene.armatures.GetComponent(mesh.armatureID) : nullptr;
- int subsetCounter = 0;
- for (auto& subset : mesh.subsets)
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ mesh.GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
{
+ const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex];
for (size_t i = 0; i < subset.indexCount; i += 3)
{
const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0];
@@ -4179,7 +4253,7 @@ namespace wi::scene
float distance;
XMFLOAT2 bary;
- if (wi::math::RayTriangleIntersects(rayOrigin_local, rayDirection_local, p0, p1, p2, distance, bary))
+ if (wi::math::RayTriangleIntersects(rayOrigin_local, rayDirection_local, p0, p1, p2, distance, bary, ray.TMin, ray.TMax))
{
const XMVECTOR pos = XMVector3Transform(XMVectorAdd(rayOrigin_local, rayDirection_local*distance), objectMat);
distance = wi::math::Distance(pos, rayOrigin);
@@ -4192,7 +4266,7 @@ namespace wi::scene
XMStoreFloat3(&result.position, pos);
XMStoreFloat3(&result.normal, nor);
result.distance = distance;
- result.subsetIndex = subsetCounter;
+ result.subsetIndex = (int)subsetIndex;
result.vertexID0 = (int)i0;
result.vertexID1 = (int)i1;
result.vertexID2 = (int)i2;
@@ -4200,7 +4274,6 @@ namespace wi::scene
}
}
}
- subsetCounter++;
}
}
@@ -4261,9 +4334,12 @@ namespace wi::scene
const ArmatureComponent* armature = mesh.IsSkinned() ? scene.armatures.GetComponent(mesh.armatureID) : nullptr;
- int subsetCounter = 0;
- for (auto& subset : mesh.subsets)
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ mesh.GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
{
+ const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex];
for (size_t i = 0; i < subset.indexCount; i += 3)
{
const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0];
@@ -4411,7 +4487,6 @@ namespace wi::scene
return result;
}
}
- subsetCounter++;
}
}
@@ -4467,9 +4542,12 @@ namespace wi::scene
const ArmatureComponent* armature = mesh.IsSkinned() ? scene.armatures.GetComponent(mesh.armatureID) : nullptr;
- int subsetCounter = 0;
- for (auto& subset : mesh.subsets)
+ uint32_t first_subset = 0;
+ uint32_t last_subset = 0;
+ mesh.GetLODSubsetRange(0, first_subset, last_subset);
+ for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
{
+ const MeshComponent::MeshSubset& subset = mesh.subsets[subsetIndex];
for (size_t i = 0; i < subset.indexCount; i += 3)
{
const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0];
@@ -4688,7 +4766,6 @@ namespace wi::scene
return result;
}
}
- subsetCounter++;
}
}
diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h
index 647cd5f39..05b46c938 100644
--- a/WickedEngine/wiScene.h
+++ b/WickedEngine/wiScene.h
@@ -365,6 +365,8 @@ namespace wi::scene
};
wi::vector 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
@@ -426,6 +428,18 @@ namespace wi::scene
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();
@@ -641,6 +655,7 @@ namespace wi::scene
wi::vector lightmapTextureData;
uint8_t userStencilRef = 0;
+ float lod_distance_multiplier = 1;
// Non-serialized attributes:
@@ -653,6 +668,8 @@ namespace wi::scene
float impostorFadeThresholdRadius;
float impostorSwapDistance;
+ uint32_t lod = 0;
+
// these will only be valid for a single frame:
uint32_t mesh_index = ~0u;
XMFLOAT4X4 worldMatrix = wi::math::IDENTITY_MATRIX;
@@ -888,7 +905,7 @@ namespace wi::scene
float width = 0.0f;
float height = 0.0f;
float zNearP = 0.1f;
- float zFarP = 800.0f;
+ float zFarP = 5000.0f;
float fov = XM_PI / 3.0f;
float focal_length = 1;
float aperture_size = 0;
@@ -1494,6 +1511,8 @@ namespace wi::scene
void RunParticleUpdateSystem(wi::jobsystem::context& ctx);
void RunWeatherUpdateSystem(wi::jobsystem::context& ctx);
void RunSoundUpdateSystem(wi::jobsystem::context& ctx);
+
+ void UpdateLODsForCamera(const CameraComponent& camera);
};
// Returns skinned vertex position in armature local space
@@ -1545,7 +1564,7 @@ namespace wi::scene
XMFLOAT2 bary = XMFLOAT2(0, 0);
XMFLOAT4X4 orientation = wi::math::IDENTITY_MATRIX;
- bool operator==(const PickResult& other)
+ constexpr bool operator==(const PickResult& other) const
{
return entity == other.entity;
}
diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp
index d254dc36f..e9c2c1eb1 100644
--- a/WickedEngine/wiScene_Serializers.cpp
+++ b/WickedEngine/wiScene_Serializers.cpp
@@ -403,6 +403,11 @@ namespace wi::scene
}
}
+ if (archive.GetVersion() >= 76)
+ {
+ archive >> subsets_per_lod;
+ }
+
wi::jobsystem::Execute(seri.ctx, [&](wi::jobsystem::JobArgs args) {
CreateRenderData();
});
@@ -463,6 +468,11 @@ namespace wi::scene
}
}
+ if (archive.GetVersion() >= 76)
+ {
+ archive << subsets_per_lod;
+ }
+
}
}
void ImpostorComponent::Serialize(wi::Archive& archive, EntitySerializer& seri)
@@ -516,6 +526,10 @@ namespace wi::scene
{
archive >> emissiveColor;
}
+ if (archive.GetVersion() >= 76)
+ {
+ archive >> lod_distance_multiplier;
+ }
}
else
{
@@ -539,6 +553,10 @@ namespace wi::scene
{
archive << emissiveColor;
}
+ if (archive.GetVersion() >= 76)
+ {
+ archive << lod_distance_multiplier;
+ }
}
}
void RigidBodyPhysicsComponent::Serialize(wi::Archive& archive, EntitySerializer& seri)
diff --git a/WickedEngine/wiUnorderedMap.h b/WickedEngine/wiUnorderedMap.h
index 8c5a6dc04..8083fac77 100644
--- a/WickedEngine/wiUnorderedMap.h
+++ b/WickedEngine/wiUnorderedMap.h
@@ -14,11 +14,11 @@
namespace wi
{
- template
+ template, typename E = std::equal_to, typename A = std::allocator > >
#if WI_UNORDERED_MAP_TYPE == 1
- using unordered_map = ska::flat_hash_map;
+ using unordered_map = ska::flat_hash_map;
#else
- using unordered_map = std::unordered_map;
+ using unordered_map = std::unordered_map;
#endif // WI_UNORDERED_MAP_TYPE
}
diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp
index 6a24c16c2..1dcd30416 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 = 60;
// minor bug fixes, alterations, refactors, updates
- const int revision = 49;
+ const int revision = 50;
const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);
diff --git a/appveyor.yml b/appveyor.yml
index 1e7e09eee..5e7a09671 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -23,11 +23,11 @@ after_build:
#Editor:
- cmd: move %APPVEYOR_BUILD_FOLDER%\BUILD\%PLATFORM%\Release\Editor_Windows\Editor_Windows.exe %APPVEYOR_BUILD_FOLDER%\Editor
- cmd: xcopy C:\projects\wickedengine\WickedEngine\*.dll %APPVEYOR_BUILD_FOLDER%\Editor
- - cmd: 7z a WickedEngineEditor.zip Content\ features.txt other_licenses.txt *.md Editor\*.exe Editor\images\ Editor\sound\ Editor\*.ini Editor\*.ico Editor\*.lua
+ - cmd: 7z a WickedEngineEditor.zip Content\ features.txt other_licenses.txt *.md Editor\*.exe Editor\images\ Editor\terrain\ Editor\sound\ Editor\*.ini Editor\*.ico Editor\*.lua
#Tests:
- cmd: move %APPVEYOR_BUILD_FOLDER%\BUILD\%PLATFORM%\Release\Tests\Tests.exe %APPVEYOR_BUILD_FOLDER%\Tests
- cmd: xcopy C:\projects\wickedengine\WickedEngine\*.dll %APPVEYOR_BUILD_FOLDER%\Tests
- - cmd: 7z a WickedEngineTests.zip Content\ features.txt other_licenses.txt *.md Tests\*.exe Tests\images\ Tests\sound\ Tests\*.ini Tests\*.ico Tests\*.lua Tests\*.ttf
+ - cmd: 7z a WickedEngineTests.zip Content\ features.txt other_licenses.txt *.md Tests\*.exe Tests\images\ Tests\sound\ Tests\*.ini Tests\*.txt Tests\*.ico Tests\*.lua Tests\*.ttf
artifacts:
- path: WickedEngineEditor.zip
diff --git a/features.txt b/features.txt
index 4267549cd..ec574ef20 100644
--- a/features.txt
+++ b/features.txt
@@ -83,6 +83,7 @@ Stochastic alphatest transparency
Surfel GI
HDR display output
Dynamic Diffuse Global Illumination (DDGI)
+Procedural terrain generator
GLTF 2.0 extensions supported:
KHR_materials_unlit