Files
WickedEngine/Editor/TerrainWindow.cpp
T
2023-06-11 13:20:30 +02:00

1100 lines
37 KiB
C++

#include "stdafx.h"
#include "TerrainWindow.h"
#include "Utility/stb_image.h"
using namespace wi::ecs;
using namespace wi::scene;
ModifierWindow::ModifierWindow(const std::string& name)
{
wi::gui::Window::Create(name, wi::gui::Window::WindowControls::CLOSE_AND_COLLAPSE);
SetSize(XMFLOAT2(200, 100));
blendCombo.Create("Blend: ");
blendCombo.SetSize(XMFLOAT2(100, 20));
blendCombo.AddItem("Normal", (uint64_t)wi::terrain::Modifier::BlendMode::Normal);
blendCombo.AddItem("Additive", (uint64_t)wi::terrain::Modifier::BlendMode::Additive);
blendCombo.AddItem("Multiply", (uint64_t)wi::terrain::Modifier::BlendMode::Multiply);
blendCombo.OnSelect([=](wi::gui::EventArgs args) {
modifier->blend = (wi::terrain::Modifier::BlendMode)args.userdata;
generation_callback();
});
AddWidget(&blendCombo);
weightSlider.Create(0, 1, 0.5f, 10000, "Weight: ");
weightSlider.SetSize(XMFLOAT2(100, 20));
weightSlider.SetTooltip("Blending amount");
weightSlider.OnSlide([=](wi::gui::EventArgs args) {
modifier->weight = args.fValue;
generation_callback();
});
AddWidget(&weightSlider);
frequencySlider.Create(0.0001f, 0.01f, 0.0008f, 10000, "Frequency: ");
frequencySlider.SetSize(XMFLOAT2(100, 20));
frequencySlider.SetTooltip("Frequency for the tiling");
frequencySlider.OnSlide([=](wi::gui::EventArgs args) {
modifier->frequency = args.fValue;
generation_callback();
});
AddWidget(&frequencySlider);
}
void ModifierWindow::Bind(wi::terrain::Modifier* ptr)
{
modifier = ptr;
modifier->blend = (wi::terrain::Modifier::BlendMode)blendCombo.GetItemUserData(blendCombo.GetSelected());
modifier->weight = weightSlider.GetValue();
modifier->frequency = frequencySlider.GetValue();
}
void ModifierWindow::From(wi::terrain::Modifier* ptr)
{
modifier = ptr;
blendCombo.SetSelectedByUserdataWithoutCallback((uint64_t)ptr->blend);
weightSlider.SetValue(ptr->weight);
frequencySlider.SetValue(ptr->frequency);
}
PerlinModifierWindow::PerlinModifierWindow() : ModifierWindow("Perlin Noise")
{
octavesSlider.Create(1, 8, 6, 7, "Octaves: ");
octavesSlider.SetTooltip("Octave count for the perlin noise");
octavesSlider.SetSize(XMFLOAT2(100, 20));
octavesSlider.OnSlide([=](wi::gui::EventArgs args) {
((wi::terrain::PerlinModifier*)modifier)->octaves = args.iValue;
generation_callback();
});
AddWidget(&octavesSlider);
SetSize(XMFLOAT2(200, 140));
}
void PerlinModifierWindow::ResizeLayout()
{
ModifierWindow::ResizeLayout();
const float padding = 4;
const float width = GetWidgetAreaSize().x;
float y = padding;
auto add = [&](wi::gui::Widget& widget) {
const float margin_left = 100;
const float margin_right = 50;
widget.SetPos(XMFLOAT2(margin_left, y));
widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
y += widget.GetSize().y;
y += padding;
};
add(blendCombo);
add(weightSlider);
add(frequencySlider);
add(octavesSlider);
}
void PerlinModifierWindow::Bind(wi::terrain::PerlinModifier* ptr)
{
ModifierWindow::Bind(ptr);
ptr->octaves = (int)octavesSlider.GetValue();
}
void PerlinModifierWindow::From(wi::terrain::PerlinModifier* ptr)
{
ModifierWindow::From(ptr);
octavesSlider.SetValue((float)ptr->octaves);
}
VoronoiModifierWindow::VoronoiModifierWindow() : ModifierWindow("Voronoi Noise")
{
fadeSlider.Create(0, 100, 2.59f, 10000, "Fade: ");
fadeSlider.SetTooltip("Fade out voronoi regions by distance from cell's center");
fadeSlider.SetSize(XMFLOAT2(100, 20));
fadeSlider.OnSlide([=](wi::gui::EventArgs args) {
((wi::terrain::VoronoiModifier*)modifier)->fade = args.fValue;
generation_callback();
});
AddWidget(&fadeSlider);
shapeSlider.Create(0, 1, 0.7f, 10000, "Shape: ");
shapeSlider.SetTooltip("How much the voronoi shape will be kept");
shapeSlider.SetSize(XMFLOAT2(100, 20));
shapeSlider.OnSlide([=](wi::gui::EventArgs args) {
((wi::terrain::VoronoiModifier*)modifier)->shape = args.fValue;
generation_callback();
});
AddWidget(&shapeSlider);
falloffSlider.Create(0, 8, 6, 10000, "Falloff: ");
falloffSlider.SetTooltip("Controls the falloff of the voronoi distance fade effect");
falloffSlider.SetSize(XMFLOAT2(100, 20));
falloffSlider.OnSlide([=](wi::gui::EventArgs args) {
((wi::terrain::VoronoiModifier*)modifier)->falloff = args.fValue;
generation_callback();
});
AddWidget(&falloffSlider);
perturbationSlider.Create(0, 1, 0.1f, 10000, "Perturbation: ");
perturbationSlider.SetTooltip("Controls the random look of voronoi region edges");
perturbationSlider.SetSize(XMFLOAT2(100, 20));
perturbationSlider.OnSlide([=](wi::gui::EventArgs args) {
((wi::terrain::VoronoiModifier*)modifier)->perturbation = args.fValue;
generation_callback();
});
AddWidget(&perturbationSlider);
SetSize(XMFLOAT2(200, 200));
}
void VoronoiModifierWindow::ResizeLayout()
{
ModifierWindow::ResizeLayout();
const float padding = 4;
const float width = GetWidgetAreaSize().x;
float y = padding;
auto add = [&](wi::gui::Widget& widget) {
const float margin_left = 100;
const float margin_right = 50;
widget.SetPos(XMFLOAT2(margin_left, y));
widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
y += widget.GetSize().y;
y += padding;
};
add(blendCombo);
add(weightSlider);
add(frequencySlider);
add(fadeSlider);
add(shapeSlider);
add(falloffSlider);
add(perturbationSlider);
}
void VoronoiModifierWindow::Bind(wi::terrain::VoronoiModifier* ptr)
{
ModifierWindow::Bind(ptr);
ptr->fade = fadeSlider.GetValue();
ptr->shape = shapeSlider.GetValue();
ptr->falloff = falloffSlider.GetValue();
ptr->perturbation = perturbationSlider.GetValue();
}
void VoronoiModifierWindow::From(wi::terrain::VoronoiModifier* ptr)
{
ModifierWindow::From(ptr);
fadeSlider.SetValue(ptr->fade);
shapeSlider.SetValue(ptr->shape);
falloffSlider.SetValue(ptr->falloff);
perturbationSlider.SetValue(ptr->perturbation);
}
HeightmapModifierWindow::HeightmapModifierWindow() : ModifierWindow("Heightmap")
{
weightSlider.SetValue(1);
frequencySlider.SetValue(1);
scaleSlider.Create(0, 1, 0.1f, 1000, "Scale: ");
scaleSlider.SetSize(XMFLOAT2(100, 20));
scaleSlider.OnSlide([=](wi::gui::EventArgs args) {
((wi::terrain::HeightmapModifier*)modifier)->scale = args.fValue;
generation_callback();
});
AddWidget(&scaleSlider);
loadButton.Create("Load Heightmap...");
loadButton.SetTooltip("Load a heightmap texture, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be placed in the world center.");
loadButton.OnClick([=](wi::gui::EventArgs args) {
wi::helper::FileDialogParams params;
params.type = wi::helper::FileDialogParams::OPEN;
params.description = "Texture";
params.extensions = wi::resourcemanager::GetSupportedImageExtensions();
wi::helper::FileDialog(params, [=](std::string fileName) {
wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) {
auto* heightmap_modifier = (wi::terrain::HeightmapModifier*)modifier;
heightmap_modifier->data.clear();
heightmap_modifier->width = 0;
heightmap_modifier->height = 0;
int bpp = 0;
stbi_uc* rgba = stbi_load(fileName.c_str(), &heightmap_modifier->width, &heightmap_modifier->height, &bpp, 1);
if (rgba != nullptr)
{
heightmap_modifier->data.resize(heightmap_modifier->width * heightmap_modifier->height);
for (int i = 0; i < heightmap_modifier->width * heightmap_modifier->height; ++i)
{
heightmap_modifier->data[i] = rgba[i];
}
stbi_image_free(rgba);
generation_callback(); // callback after heightmap load confirmation
}
});
});
});
AddWidget(&loadButton);
SetSize(XMFLOAT2(200, 180));
}
void HeightmapModifierWindow::ResizeLayout()
{
ModifierWindow::ResizeLayout();
const float padding = 4;
const float width = GetWidgetAreaSize().x;
float y = padding;
auto add = [&](wi::gui::Widget& widget) {
const float margin_left = 100;
const float margin_right = 50;
widget.SetPos(XMFLOAT2(margin_left, y));
widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
y += widget.GetSize().y;
y += padding;
};
add(blendCombo);
add(weightSlider);
add(frequencySlider);
add(scaleSlider);
add(loadButton);
}
void HeightmapModifierWindow::Bind(wi::terrain::HeightmapModifier* ptr)
{
ModifierWindow::Bind(ptr);
ptr->scale = scaleSlider.GetValue();
}
void HeightmapModifierWindow::From(wi::terrain::HeightmapModifier* ptr)
{
ModifierWindow::From(ptr);
scaleSlider.SetValue(ptr->scale);
}
void TerrainWindow::Create(EditorComponent* _editor)
{
editor = _editor;
RemoveWidgets();
ClearTransform();
wi::gui::Window::Create(ICON_TERRAIN " Terrain", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE);
SetSize(XMFLOAT2(420, 940));
closeButton.SetTooltip("Delete Terrain.");
OnClose([=](wi::gui::EventArgs args) {
wi::Archive& archive = editor->AdvanceHistory();
archive << EditorComponent::HISTORYOP_COMPONENT_DATA;
editor->RecordEntity(archive, entity);
editor->GetCurrentScene().terrains.Remove(entity);
editor->RecordEntity(archive, entity);
editor->optionsWnd.RefreshEntityTree();
});
float x = 140;
float y = 0;
float step = 25;
float hei = 20;
float wid = 120;
auto generate_callback = [&]() {
terrain->SetCenterToCamEnabled(centerToCamCheckBox.GetCheck());
terrain->SetRemovalEnabled(removalCheckBox.GetCheck());
terrain->SetGrassEnabled(grassCheckBox.GetCheck());
terrain->lod_multiplier = lodSlider.GetValue();
terrain->generation = (int)generationSlider.GetValue();
terrain->prop_generation = (int)propGenerationSlider.GetValue();
terrain->physics_generation = (int)physicsGenerationSlider.GetValue();
terrain->prop_density = propDensitySlider.GetValue();
terrain->grass_density = grassDensitySlider.GetValue();
terrain->grass_properties.length = grassLengthSlider.GetValue();
terrain->grass_properties.viewDistance = grassDistanceSlider.GetValue();
terrain->chunk_scale = scaleSlider.GetValue();
terrain->seed = (uint32_t)seedSlider.GetValue();
terrain->bottomLevel = bottomLevelSlider.GetValue();
terrain->topLevel = topLevelSlider.GetValue();
terrain->region1 = region1Slider.GetValue();
terrain->region2 = region2Slider.GetValue();
terrain->region3 = region3Slider.GetValue();
terrain->SetGenerationStarted(false);
};
centerToCamCheckBox.Create("Center to Cam: ");
centerToCamCheckBox.SetTooltip("Automatically generate chunks around camera. This sets the center chunk to camera position.");
centerToCamCheckBox.SetSize(XMFLOAT2(hei, hei));
centerToCamCheckBox.SetPos(XMFLOAT2(x, y));
centerToCamCheckBox.SetCheck(true);
centerToCamCheckBox.OnClick([=](wi::gui::EventArgs args) {
terrain->SetCenterToCamEnabled(args.bValue);
});
AddWidget(&centerToCamCheckBox);
removalCheckBox.Create("Removal: ");
removalCheckBox.SetTooltip("Automatically remove chunks that are farther than generation distance around center chunk.");
removalCheckBox.SetSize(XMFLOAT2(hei, hei));
removalCheckBox.SetPos(XMFLOAT2(x + 100, y));
removalCheckBox.SetCheck(true);
removalCheckBox.OnClick([=](wi::gui::EventArgs args) {
terrain->SetRemovalEnabled(args.bValue);
});
AddWidget(&removalCheckBox);
grassCheckBox.Create("Grass: ");
grassCheckBox.SetTooltip("Specify whether grass generation is enabled.");
grassCheckBox.SetSize(XMFLOAT2(hei, hei));
grassCheckBox.SetPos(XMFLOAT2(x, y += step));
grassCheckBox.SetCheck(true);
grassCheckBox.OnClick([=](wi::gui::EventArgs args) {
terrain->SetGrassEnabled(args.bValue);
});
AddWidget(&grassCheckBox);
physicsCheckBox.Create("Physics: ");
physicsCheckBox.SetTooltip("Specify whether physics is enabled for newly generated terrain chunks.");
physicsCheckBox.SetSize(XMFLOAT2(hei, hei));
physicsCheckBox.SetPos(XMFLOAT2(x, y += step));
physicsCheckBox.SetCheck(true);
physicsCheckBox.OnClick([=](wi::gui::EventArgs args) {
terrain->SetPhysicsEnabled(args.bValue);
generate_callback();
});
AddWidget(&physicsCheckBox);
tessellationCheckBox.Create("Tessellation: ");
tessellationCheckBox.SetTooltip("Specify whether tessellation is enabled for terrain surface.\nTessellation requires GPU hardware support\nTessellation doesn't work with raytracing effects or Visibility Compute Shading rendering mode.");
tessellationCheckBox.SetSize(XMFLOAT2(hei, hei));
tessellationCheckBox.SetPos(XMFLOAT2(x, y += step));
tessellationCheckBox.SetCheck(true);
tessellationCheckBox.OnClick([=](wi::gui::EventArgs args) {
terrain->SetTessellationEnabled(args.bValue);
});
AddWidget(&tessellationCheckBox);
lodSlider.Create(0.0001f, 0.01f, 0.005f, 10000, "Mesh LOD Distance: ");
lodSlider.SetTooltip("Set the LOD (Level Of Detail) distance multiplier.\nLow values increase LOD detail in distance");
lodSlider.SetSize(XMFLOAT2(wid, hei));
lodSlider.SetPos(XMFLOAT2(x, y += step));
lodSlider.OnSlide([this](wi::gui::EventArgs args) {
for (auto& it : terrain->chunks)
{
const wi::terrain::ChunkData& chunk_data = it.second;
if (chunk_data.entity != INVALID_ENTITY)
{
ObjectComponent* object = terrain->scene->objects.GetComponent(chunk_data.entity);
if (object != nullptr)
{
object->lod_distance_multiplier = args.fValue;
}
}
}
terrain->lod_multiplier = args.fValue;
});
AddWidget(&lodSlider);
generationSlider.Create(0, 16, 12, 16, "Generation Distance: ");
generationSlider.SetTooltip("How far out chunks will be generated (value is in number of chunks)");
generationSlider.SetSize(XMFLOAT2(wid, hei));
generationSlider.SetPos(XMFLOAT2(x, y += step));
generationSlider.OnSlide([this](wi::gui::EventArgs args) {
terrain->generation = args.iValue;
});
AddWidget(&generationSlider);
propGenerationSlider.Create(0, 16, 10, 16, "Prop Distance: ");
propGenerationSlider.SetTooltip("How far out props will be generated (value is in number of chunks)");
propGenerationSlider.SetSize(XMFLOAT2(wid, hei));
propGenerationSlider.SetPos(XMFLOAT2(x, y += step));
propGenerationSlider.OnSlide([this](wi::gui::EventArgs args) {
terrain->prop_generation = args.iValue;
});
AddWidget(&propGenerationSlider);
physicsGenerationSlider.Create(0, 16, 3, 16, "Physics Distance: ");
physicsGenerationSlider.SetTooltip("How far out physics meshes will be generated (value is in number of chunks)");
physicsGenerationSlider.SetSize(XMFLOAT2(wid, hei));
physicsGenerationSlider.SetPos(XMFLOAT2(x, y += step));
physicsGenerationSlider.OnSlide([this](wi::gui::EventArgs args) {
terrain->physics_generation = args.iValue;
});
AddWidget(&physicsGenerationSlider);
propDensitySlider.Create(0, 10, 1, 1000, "Prop Density: ");
propDensitySlider.SetTooltip("Modifies overall prop density.");
propDensitySlider.SetSize(XMFLOAT2(wid, hei));
propDensitySlider.SetPos(XMFLOAT2(x, y += step));
propDensitySlider.OnSlide([this](wi::gui::EventArgs args) {
terrain->prop_density = args.fValue;
});
AddWidget(&propDensitySlider);
grassDensitySlider.Create(0, 4, 2, 1000, "Grass Density: ");
grassDensitySlider.SetTooltip("Modifies overall grass density.");
grassDensitySlider.SetSize(XMFLOAT2(wid, hei));
grassDensitySlider.SetPos(XMFLOAT2(x, y += step));
grassDensitySlider.OnSlide([this](wi::gui::EventArgs args) {
terrain->grass_density = args.fValue;
});
AddWidget(&grassDensitySlider);
grassLengthSlider.Create(0, 8, 2, 1000, "Grass Length: ");
grassLengthSlider.SetTooltip("Modifies overall grass length.");
grassLengthSlider.SetSize(XMFLOAT2(wid, hei));
grassLengthSlider.SetPos(XMFLOAT2(x, y += step));
grassLengthSlider.OnSlide([this](wi::gui::EventArgs args) {
terrain->grass_properties.length = args.fValue;
});
AddWidget(&grassLengthSlider);
grassDistanceSlider.Create(10, 100, 60, 1000, "Grass Distance: ");
grassDistanceSlider.SetTooltip("Modifies grass view distance.");
grassDistanceSlider.SetSize(XMFLOAT2(wid, hei));
grassDistanceSlider.SetPos(XMFLOAT2(x, y += step));
grassDistanceSlider.OnSlide([this](wi::gui::EventArgs args) {
terrain->grass_properties.viewDistance = args.fValue;
});
AddWidget(&grassDistanceSlider);
presetCombo.Create("Preset: ");
presetCombo.SetTooltip("Select a terrain preset");
presetCombo.SetSize(XMFLOAT2(wid, hei));
presetCombo.SetPos(XMFLOAT2(x, y += step));
presetCombo.AddItem("Hills", PRESET_HILLS);
presetCombo.AddItem("Islands", PRESET_ISLANDS);
presetCombo.AddItem("Mountains", PRESET_MOUNTAINS);
presetCombo.AddItem("Arctic", PRESET_ARCTIC);
presetCombo.OnSelect([=](wi::gui::EventArgs args) {
terrain->Generation_Cancel();
for (auto& modifier : modifiers)
{
RemoveWidget(modifier.get());
}
modifiers.clear();
modifiers_to_remove.clear();
terrain->modifiers.clear();
terrain->modifiers_to_remove.clear();
PerlinModifierWindow* perlin = new PerlinModifierWindow;
VoronoiModifierWindow* voronoi = new VoronoiModifierWindow;
perlin->blendCombo.SetSelectedByUserdataWithoutCallback((uint64_t)wi::terrain::Modifier::BlendMode::Additive);
voronoi->blendCombo.SetSelectedByUserdataWithoutCallback((uint64_t)wi::terrain::Modifier::BlendMode::Multiply);
switch (args.userdata)
{
default:
case PRESET_HILLS:
terrain->weather.SetOceanEnabled(false);
seedSlider.SetValue(5415);
bottomLevelSlider.SetValue(-60);
topLevelSlider.SetValue(380);
perlin->weightSlider.SetValue(0.5f);
perlin->frequencySlider.SetValue(0.0008f);
perlin->octavesSlider.SetValue(6);
voronoi->weightSlider.SetValue(0.5f);
voronoi->frequencySlider.SetValue(0.001f);
voronoi->fadeSlider.SetValue(2.59f);
voronoi->shapeSlider.SetValue(0.7f);
voronoi->falloffSlider.SetValue(6);
voronoi->perturbationSlider.SetValue(0.1f);
region1Slider.SetValue(1);
region2Slider.SetValue(2);
region3Slider.SetValue(8);
break;
case PRESET_ISLANDS:
terrain->weather.SetOceanEnabled(true);
seedSlider.SetValue(8526);
bottomLevelSlider.SetValue(-79);
topLevelSlider.SetValue(520);
perlin->weightSlider.SetValue(0.5f);
perlin->frequencySlider.SetValue(0.000991f);
perlin->octavesSlider.SetValue(6);
voronoi->weightSlider.SetValue(0.5f);
voronoi->frequencySlider.SetValue(0.000317f);
voronoi->fadeSlider.SetValue(8.2f);
voronoi->shapeSlider.SetValue(0.126f);
voronoi->falloffSlider.SetValue(1.392f);
voronoi->perturbationSlider.SetValue(0.126f);
region1Slider.SetValue(8);
region2Slider.SetValue(0.7f);
region3Slider.SetValue(8);
break;
case PRESET_MOUNTAINS:
terrain->weather.SetOceanEnabled(false);
seedSlider.SetValue(5213);
bottomLevelSlider.SetValue(0);
topLevelSlider.SetValue(2960);
perlin->weightSlider.SetValue(0.5f);
perlin->frequencySlider.SetValue(0.00279f);
perlin->octavesSlider.SetValue(8);
voronoi->weightSlider.SetValue(0.5f);
voronoi->frequencySlider.SetValue(0.000496f);
voronoi->fadeSlider.SetValue(5.2f);
voronoi->shapeSlider.SetValue(0.412f);
voronoi->falloffSlider.SetValue(1.456f);
voronoi->perturbationSlider.SetValue(0.092f);
region1Slider.SetValue(1);
region2Slider.SetValue(1);
region3Slider.SetValue(0.8f);
break;
case PRESET_ARCTIC:
terrain->weather.SetOceanEnabled(false);
seedSlider.SetValue(11597);
bottomLevelSlider.SetValue(-50);
topLevelSlider.SetValue(40);
perlin->weightSlider.SetValue(1);
perlin->frequencySlider.SetValue(0.002f);
perlin->octavesSlider.SetValue(4);
voronoi->weightSlider.SetValue(1);
voronoi->frequencySlider.SetValue(0.004f);
voronoi->fadeSlider.SetValue(1.8f);
voronoi->shapeSlider.SetValue(0.518f);
voronoi->falloffSlider.SetValue(0.2f);
voronoi->perturbationSlider.SetValue(0.298f);
region1Slider.SetValue(8);
region2Slider.SetValue(8);
region3Slider.SetValue(0);
break;
}
std::shared_ptr<wi::terrain::PerlinModifier> terrain_perlin = std::make_shared<wi::terrain::PerlinModifier>();
terrain->modifiers.emplace_back() = terrain_perlin;
std::shared_ptr<wi::terrain::VoronoiModifier> terrain_voronoi = std::make_shared<wi::terrain::VoronoiModifier>();
terrain->modifiers.emplace_back() = terrain_voronoi;
perlin->Bind(terrain_perlin.get());
voronoi->Bind(terrain_voronoi.get());
AddModifier(perlin);
AddModifier(voronoi);
generate_callback();
editor->optionsWnd.RefreshEntityTree();
});
AddWidget(&presetCombo);
addModifierCombo.Create("Add Modifier: ");
addModifierCombo.selected_font.anim.typewriter.looped = true;
addModifierCombo.selected_font.anim.typewriter.time = 2;
addModifierCombo.selected_font.anim.typewriter.character_start = 1;
addModifierCombo.SetTooltip("Add a new modifier that will affect terrain generation.");
addModifierCombo.SetSize(XMFLOAT2(wid, hei));
addModifierCombo.SetPos(XMFLOAT2(x, y += step));
addModifierCombo.SetInvalidSelectionText("...");
addModifierCombo.AddItem("Perlin Noise");
addModifierCombo.AddItem("Voronoi Noise");
addModifierCombo.AddItem("Heightmap Image");
addModifierCombo.OnSelect([=](wi::gui::EventArgs args) {
addModifierCombo.SetSelectedWithoutCallback(-1);
terrain->Generation_Cancel();
switch (args.iValue)
{
default:
break;
case 0:
{
PerlinModifierWindow* ptr = new PerlinModifierWindow;
std::shared_ptr<wi::terrain::PerlinModifier> modifier = std::make_shared<wi::terrain::PerlinModifier>();
terrain->modifiers.push_back(modifier);
ptr->Bind(modifier.get());
AddModifier(ptr);
}
break;
case 1:
{
VoronoiModifierWindow* ptr = new VoronoiModifierWindow;
std::shared_ptr<wi::terrain::VoronoiModifier> modifier = std::make_shared<wi::terrain::VoronoiModifier>();
terrain->modifiers.push_back(modifier);
ptr->Bind(modifier.get());
AddModifier(ptr);
}
break;
case 2:
{
HeightmapModifierWindow* ptr = new HeightmapModifierWindow;
std::shared_ptr<wi::terrain::HeightmapModifier> modifier = std::make_shared<wi::terrain::HeightmapModifier>();
terrain->modifiers.push_back(modifier);
ptr->Bind(modifier.get());
AddModifier(ptr);
}
break;
}
generate_callback();
});
AddWidget(&addModifierCombo);
scaleSlider.Create(1, 10, 1, 9, "Chunk Scale: ");
scaleSlider.SetTooltip("Size of one chunk in horizontal directions.\nLarger chunk scale will cover larger distance, but will have less detail per unit.");
scaleSlider.SetSize(XMFLOAT2(wid, hei));
scaleSlider.SetPos(XMFLOAT2(x, y += step));
scaleSlider.OnSlide([=](wi::gui::EventArgs args) {
terrain->chunk_scale = args.fValue;
generate_callback();
});
AddWidget(&scaleSlider);
seedSlider.Create(1, 12345, 3926, 12344, "Seed: ");
seedSlider.SetTooltip("Seed for terrain randomness");
seedSlider.SetSize(XMFLOAT2(wid, hei));
seedSlider.SetPos(XMFLOAT2(x, y += step));
seedSlider.OnSlide([=](wi::gui::EventArgs args) {
terrain->seed = (uint32_t)args.iValue;
generate_callback();
});
AddWidget(&seedSlider);
bottomLevelSlider.Create(-100, 0, -60, 10000, "Bottom Level: ");
bottomLevelSlider.SetTooltip("Terrain mesh grid lowest level");
bottomLevelSlider.SetSize(XMFLOAT2(wid, hei));
bottomLevelSlider.SetPos(XMFLOAT2(x, y += step));
bottomLevelSlider.OnSlide([=](wi::gui::EventArgs args) {
terrain->bottomLevel = args.fValue;
generate_callback();
});
AddWidget(&bottomLevelSlider);
topLevelSlider.Create(0, 5000, 380, 10000, "Top Level: ");
topLevelSlider.SetTooltip("Terrain mesh grid topmost level");
topLevelSlider.SetSize(XMFLOAT2(wid, hei));
topLevelSlider.SetPos(XMFLOAT2(x, y += step));
topLevelSlider.OnSlide([=](wi::gui::EventArgs args) {
terrain->topLevel = args.fValue;
generate_callback();
});
AddWidget(&topLevelSlider);
saveHeightmapButton.Create("Save Heightmap...");
saveHeightmapButton.SetTooltip("Save a heightmap texture from the currently generated terrain, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be normalized into 8bit PNG format which can result in precision loss!");
saveHeightmapButton.SetSize(XMFLOAT2(wid, hei));
saveHeightmapButton.SetPos(XMFLOAT2(x, y += step));
AddWidget(&saveHeightmapButton);
region1Slider.Create(0, 8, 1, 10000, "Slope Region: ");
region1Slider.SetTooltip("The region's falloff power");
region1Slider.SetSize(XMFLOAT2(wid, hei));
region1Slider.SetPos(XMFLOAT2(x, y += step));
region1Slider.OnSlide([=](wi::gui::EventArgs args) {
terrain->region1 = args.fValue;
generate_callback();
});
AddWidget(&region1Slider);
region2Slider.Create(0, 8, 2, 10000, "Low Altitude Region: ");
region2Slider.SetTooltip("The region's falloff power");
region2Slider.SetSize(XMFLOAT2(wid, hei));
region2Slider.SetPos(XMFLOAT2(x, y += step));
region2Slider.OnSlide([=](wi::gui::EventArgs args) {
terrain->region2 = args.fValue;
generate_callback();
});
AddWidget(&region2Slider);
region3Slider.Create(0, 8, 8, 10000, "High Altitude Region: ");
region3Slider.SetTooltip("The region's falloff power");
region3Slider.SetSize(XMFLOAT2(wid, hei));
region3Slider.SetPos(XMFLOAT2(x, y += step));
region3Slider.OnSlide([=](wi::gui::EventArgs args) {
terrain->region3 = args.fValue;
generate_callback();
});
AddWidget(&region3Slider);
saveHeightmapButton.OnClick([=](wi::gui::EventArgs args) {
wi::helper::FileDialogParams params;
params.type = wi::helper::FileDialogParams::SAVE;
params.description = "PNG";
params.extensions = { "PNG" };
wi::helper::FileDialog(params, [=](std::string fileName) {
wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) {
wi::primitive::AABB aabb;
for (auto& chunk : terrain->chunks)
{
const wi::primitive::AABB& object_aabb = terrain->scene->aabb_objects[terrain->scene->objects.GetIndex(chunk.second.entity)];
aabb = wi::primitive::AABB::Merge(aabb, object_aabb);
}
wi::vector<uint8_t> data;
int width = int(aabb.getHalfWidth().x * 2 + 1);
int height = int(aabb.getHalfWidth().z * 2 + 1);
data.resize(width * height);
std::fill(data.begin(), data.end(), 0u);
for (auto& chunk : terrain->chunks)
{
const ObjectComponent* object = terrain->scene->objects.GetComponent(chunk.second.entity);
if (object != nullptr)
{
const MeshComponent* mesh = terrain->scene->meshes.GetComponent(object->meshID);
if (mesh != nullptr)
{
size_t objectIndex = terrain->scene->objects.GetIndex(chunk.second.entity);
const XMMATRIX W = XMLoadFloat4x4(&terrain->scene->matrix_objects[objectIndex]);
for (auto& x : mesh->vertex_positions)
{
XMVECTOR P = XMLoadFloat3(&x);
P = XMVector3Transform(P, W);
XMFLOAT3 p;
XMStoreFloat3(&p, P);
p.x -= aabb._min.x;
p.z -= aabb._min.z;
int coord = int(p.x) + int(p.z) * width;
data[coord] = uint8_t(wi::math::InverseLerp(aabb._min.y, aabb._max.y, p.y) * 255u);
}
}
}
}
wi::graphics::TextureDesc desc;
desc.width = uint32_t(width);
desc.height = uint32_t(height);
desc.format = wi::graphics::Format::R8_UNORM;
bool success = wi::helper::saveTextureToFile(data, desc, wi::helper::ReplaceExtension(fileName, "PNG"));
assert(success);
});
});
});
SetCollapsed(true);
}
void TerrainWindow::SetEntity(Entity entity)
{
wi::scene::Scene& scene = editor->GetCurrentScene();
terrain = scene.terrains.GetComponent(entity);
if (terrain == nullptr)
{
entity = INVALID_ENTITY;
terrain = &terrain_preset;
}
if (this->entity == entity)
return;
this->entity = entity;
for (auto& x : modifiers)
{
modifiers_to_remove.push_back(x.get());
}
centerToCamCheckBox.SetCheck(terrain->IsCenterToCamEnabled());
removalCheckBox.SetCheck(terrain->IsRemovalEnabled());
grassCheckBox.SetCheck(terrain->IsGrassEnabled());
physicsCheckBox.SetCheck(terrain->IsPhysicsEnabled());
tessellationCheckBox.SetCheck(terrain->IsTessellationEnabled());
lodSlider.SetValue(terrain->lod_multiplier);
generationSlider.SetValue((float)terrain->generation);
propGenerationSlider.SetValue((float)terrain->prop_generation);
physicsGenerationSlider.SetValue((float)terrain->physics_generation);
propDensitySlider.SetValue(terrain->prop_density);
grassDensitySlider.SetValue(terrain->grass_density);
grassLengthSlider.SetValue(terrain->grass_properties.length);
grassDistanceSlider.SetValue(terrain->grass_properties.viewDistance);
scaleSlider.SetValue(terrain->chunk_scale);
seedSlider.SetValue((float)terrain->seed);
bottomLevelSlider.SetValue(terrain->bottomLevel);
topLevelSlider.SetValue(terrain->topLevel);
region1Slider.SetValue(terrain->region1);
region2Slider.SetValue(terrain->region2);
region3Slider.SetValue(terrain->region3);
for (auto& x : terrain->modifiers)
{
switch (x->type)
{
default:
case wi::terrain::Modifier::Type::Perlin:
{
PerlinModifierWindow* modifier = new PerlinModifierWindow;
modifier->From((wi::terrain::PerlinModifier*)x.get());
AddModifier(modifier);
}
break;
case wi::terrain::Modifier::Type::Voronoi:
{
VoronoiModifierWindow* modifier = new VoronoiModifierWindow;
modifier->From((wi::terrain::VoronoiModifier*)x.get());
AddModifier(modifier);
}
break;
case wi::terrain::Modifier::Type::Heightmap:
{
HeightmapModifierWindow* modifier = new HeightmapModifierWindow;
modifier->From((wi::terrain::HeightmapModifier*)x.get());
AddModifier(modifier);
}
break;
}
}
}
void TerrainWindow::AddModifier(ModifierWindow* modifier_window)
{
modifier_window->generation_callback = [=]() {
terrain->Generation_Restart();
};
modifiers.emplace_back().reset(modifier_window);
AddWidget(modifier_window);
modifier_window->OnClose([=](wi::gui::EventArgs args) {
// Can't delete modifier in itself, so add to a deferred deletion queue:
terrain->modifiers_to_remove.push_back(modifier_window->modifier);
modifiers_to_remove.push_back(modifier_window);
});
editor->optionsWnd.generalWnd.themeCombo.SetSelected(editor->optionsWnd.generalWnd.themeCombo.GetSelected()); // theme refresh
}
void TerrainWindow::SetupAssets()
{
if (!terrain_preset.props.empty())
return;
// Customize terrain generator before it's initialized:
terrain_preset.material_Base.SetRoughness(1);
terrain_preset.material_Base.SetReflectance(0.005f);
terrain_preset.material_Slope.SetRoughness(0.1f);
terrain_preset.material_LowAltitude.SetRoughness(1);
terrain_preset.material_HighAltitude.SetRoughness(1);
terrain_preset.material_Base.textures[MaterialComponent::BASECOLORMAP].name = wi::helper::GetCurrentPath() + "/terrain/base.jpg";
terrain_preset.material_Base.textures[MaterialComponent::NORMALMAP].name = wi::helper::GetCurrentPath() + "/terrain/base_nor.jpg";
terrain_preset.material_Slope.textures[MaterialComponent::BASECOLORMAP].name = wi::helper::GetCurrentPath() + "/terrain/slope.jpg";
terrain_preset.material_Slope.textures[MaterialComponent::NORMALMAP].name = wi::helper::GetCurrentPath() + "/terrain/slope_nor.jpg";
terrain_preset.material_LowAltitude.textures[MaterialComponent::BASECOLORMAP].name = wi::helper::GetCurrentPath() + "/terrain/low_altitude.jpg";
terrain_preset.material_LowAltitude.textures[MaterialComponent::NORMALMAP].name = wi::helper::GetCurrentPath() + "/terrain/low_altitude_nor.jpg";
terrain_preset.material_HighAltitude.textures[MaterialComponent::BASECOLORMAP].name = wi::helper::GetCurrentPath() + "/terrain/high_altitude.jpg";
terrain_preset.material_HighAltitude.textures[MaterialComponent::NORMALMAP].name = wi::helper::GetCurrentPath() + "/terrain/high_altitude_nor.jpg";
terrain_preset.material_Base.CreateRenderData();
terrain_preset.material_Slope.CreateRenderData();
terrain_preset.material_LowAltitude.CreateRenderData();
terrain_preset.material_HighAltitude.CreateRenderData();
std::string terrain_path = wi::helper::GetCurrentPath() + "/terrain/";
wi::config::File config;
config.Open(std::string(terrain_path + "props.ini").c_str());
std::unordered_map<std::string, Scene> prop_scenes;
for (const auto& it : config)
{
const std::string& section_name = it.first;
const wi::config::Section& section = it.second;
Entity entity = INVALID_ENTITY;
Scene* scene = &editor->GetCurrentScene();
if (section.Has("file"))
{
std::string text = section.GetText("file");
if (prop_scenes.count(text) == 0)
{
wi::scene::LoadModel(prop_scenes[text], terrain_path + text);
}
if (prop_scenes.count(text) != 0)
{
scene = &prop_scenes[text];
}
}
if (section.Has("entity"))
{
std::string text = section.GetText("entity");
entity = scene->Entity_FindByName(text);
}
if (entity == INVALID_ENTITY)
{
continue;
}
wi::terrain::Prop& prop = terrain_preset.props.emplace_back();
if (section.Has("min_count_per_chunk"))
{
prop.min_count_per_chunk = section.GetInt("min_count_per_chunk");
}
if (section.Has("max_count_per_chunk"))
{
prop.max_count_per_chunk = section.GetInt("max_count_per_chunk");
}
if (section.Has("region"))
{
prop.region = section.GetInt("region");
}
if (section.Has("region_power"))
{
prop.region_power = section.GetFloat("region_power");
}
if (section.Has("noise_frequency"))
{
prop.noise_frequency = section.GetFloat("noise_frequency");
}
if (section.Has("noise_power"))
{
prop.noise_power = section.GetFloat("noise_power");
}
if (section.Has("threshold"))
{
prop.threshold = section.GetFloat("threshold");
}
if (section.Has("min_size"))
{
prop.min_size = section.GetFloat("min_size");
}
if (section.Has("max_size"))
{
prop.max_size = section.GetFloat("max_size");
}
if (section.Has("min_y_offset"))
{
prop.min_y_offset = section.GetFloat("min_y_offset");
}
if (section.Has("max_y_offset"))
{
prop.max_y_offset = section.GetFloat("max_y_offset");
}
wi::Archive archive;
EntitySerializer seri;
scene->Entity_Serialize(
archive,
seri,
entity,
wi::scene::Scene::EntitySerializeFlags::RECURSIVE | wi::scene::Scene::EntitySerializeFlags::KEEP_INTERNAL_ENTITY_REFERENCES
);
archive.WriteData(prop.data);
scene->Entity_Remove(entity); // The entities will be placed by terrain generator, we don't need the default object that the scene has anymore
}
for (auto& it : prop_scenes)
{
editor->GetCurrentScene().Merge(it.second);
}
// Grass config:
terrain_preset.material_GrassParticle.alphaRef = 0.75f;
terrain_preset.grass_properties.length = 2;
terrain_preset.grass_properties.frameCount = 2;
terrain_preset.grass_properties.framesX = 1;
terrain_preset.grass_properties.framesY = 2;
terrain_preset.grass_properties.frameStart = 0;
wi::config::File grass_config;
grass_config.Open(std::string(terrain_path + "grass.ini").c_str());
if (grass_config.Has("texture"))
{
terrain_preset.material_GrassParticle.textures[MaterialComponent::BASECOLORMAP].name = terrain_path + grass_config.GetText("texture");
terrain_preset.material_GrassParticle.CreateRenderData();
}
if (grass_config.Has("alphaRef"))
{
terrain_preset.material_GrassParticle.alphaRef = grass_config.GetFloat("alphaRef");
}
if (grass_config.Has("length"))
{
terrain_preset.grass_properties.length = grass_config.GetFloat("length");
}
if (grass_config.Has("frameCount"))
{
terrain_preset.grass_properties.frameCount = grass_config.GetInt("frameCount");
}
if (grass_config.Has("framesX"))
{
terrain_preset.grass_properties.framesX = grass_config.GetInt("framesX");
}
if (grass_config.Has("framesY"))
{
terrain_preset.grass_properties.framesY = grass_config.GetInt("framesY");
}
if (grass_config.Has("frameCount"))
{
terrain_preset.grass_properties.frameStart = grass_config.GetInt("frameStart");
}
terrain = &terrain_preset;
presetCombo.SetSelected(0);
}
void TerrainWindow::Update(const wi::Canvas& canvas, float dt)
{
// Check whether any modifiers were "closed", and we will really remove them here if so:
if (!modifiers_to_remove.empty())
{
for (auto& modifier : modifiers_to_remove)
{
for (auto it = modifiers.begin(); it != modifiers.end(); ++it)
{
if (it->get() == modifier)
{
modifiers.erase(it);
RemoveWidget(modifier);
break;
}
}
}
modifiers_to_remove.clear();
}
wi::gui::Window::Update(canvas, dt);
}
void TerrainWindow::ResizeLayout()
{
wi::gui::Window::ResizeLayout();
const float padding = 4;
const float width = GetWidgetAreaSize().x;
float y = padding;
auto add = [&](wi::gui::Widget& widget) {
const float margin_left = 150;
const float margin_right = 45;
widget.SetPos(XMFLOAT2(margin_left, y));
widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
y += widget.GetSize().y;
y += padding;
};
auto add_checkbox = [&](wi::gui::CheckBox& widget) {
const float margin_right = 45;
widget.SetPos(XMFLOAT2(width - margin_right - widget.GetSize().x, y));
y += widget.GetSize().y;
y += padding;
};
auto add_window = [&](wi::gui::Window& widget) {
const float margin_left = padding;
const float margin_right = padding;
widget.SetPos(XMFLOAT2(margin_left, y));
widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y));
y += widget.GetSize().y;
y += padding;
widget.SetEnabled(true);
};
add_checkbox(removalCheckBox);
centerToCamCheckBox.SetPos(XMFLOAT2(removalCheckBox.GetPos().x - 100, removalCheckBox.GetPos().y));
add_checkbox(grassCheckBox);
physicsCheckBox.SetPos(XMFLOAT2(grassCheckBox.GetPos().x - 100, grassCheckBox.GetPos().y));
add_checkbox(tessellationCheckBox);
add(lodSlider);
add(generationSlider);
add(propGenerationSlider);
add(physicsGenerationSlider);
add(propDensitySlider);
add(grassDensitySlider);
add(grassLengthSlider);
add(grassDistanceSlider);
add(presetCombo);
add(scaleSlider);
add(seedSlider);
add(bottomLevelSlider);
add(topLevelSlider);
add(region1Slider);
add(region2Slider);
add(region3Slider);
add(saveHeightmapButton);
add(addModifierCombo);
for (auto& modifier : modifiers)
{
add_window(*modifier);
}
}