Files
WickedEngine/Editor/MeshWindow.cpp
T
2020-04-11 19:39:16 +01:00

627 lines
21 KiB
C++

#include "stdafx.h"
#include "MeshWindow.h"
#include "Editor.h"
#include "Utility/stb_image.h"
#include <sstream>
using namespace std;
using namespace wiECS;
using namespace wiScene;
MeshWindow::MeshWindow(EditorComponent* editor) : GUI(&editor->GetGUI())
{
assert(GUI && "Invalid GUI!");
meshWindow = new wiWindow(GUI, "Mesh Window");
meshWindow->SetSize(XMFLOAT2(580, 640));
GUI->AddWidget(meshWindow);
float x = 150;
float y = 0;
float step = 30;
float hei = 25;
meshInfoLabel = new wiLabel("Mesh Info");
meshInfoLabel->SetPos(XMFLOAT2(x - 50, y += step));
meshInfoLabel->SetSize(XMFLOAT2(450, 180));
meshWindow->AddWidget(meshInfoLabel);
y += 190;
doubleSidedCheckBox = new wiCheckBox("Double Sided: ");
doubleSidedCheckBox->SetTooltip("If enabled, the inside of the mesh will be visible.");
doubleSidedCheckBox->SetPos(XMFLOAT2(x, y += step));
doubleSidedCheckBox->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->SetDoubleSided(args.bValue);
}
});
meshWindow->AddWidget(doubleSidedCheckBox);
softbodyCheckBox = new wiCheckBox("Soft body: ");
softbodyCheckBox->SetTooltip("Enable soft body simulation. Tip: Use the Paint Tool to control vertex pinning.");
softbodyCheckBox->SetPos(XMFLOAT2(x, y += step));
softbodyCheckBox->OnClick([&](wiEventArgs args) {
Scene& scene = wiScene::GetScene();
SoftBodyPhysicsComponent* physicscomponent = scene.softbodies.GetComponent(entity);
if (args.bValue)
{
if (physicscomponent == nullptr)
{
SoftBodyPhysicsComponent& softbody = scene.softbodies.Create(entity);
softbody.friction = frictionSlider->GetValue();
softbody.mass = massSlider->GetValue();
}
}
else
{
if (physicscomponent != nullptr)
{
scene.softbodies.Remove(entity);
}
}
});
meshWindow->AddWidget(softbodyCheckBox);
massSlider = new wiSlider(0, 10, 0, 100000, "Mass: ");
massSlider->SetTooltip("Set the mass amount for the physics engine.");
massSlider->SetSize(XMFLOAT2(100, hei));
massSlider->SetPos(XMFLOAT2(x, y += step));
massSlider->OnSlide([&](wiEventArgs args) {
SoftBodyPhysicsComponent* physicscomponent = wiScene::GetScene().softbodies.GetComponent(entity);
if (physicscomponent != nullptr)
{
physicscomponent->mass = args.fValue;
}
});
meshWindow->AddWidget(massSlider);
frictionSlider = new wiSlider(0, 2, 0, 100000, "Friction: ");
frictionSlider->SetTooltip("Set the friction amount for the physics engine.");
frictionSlider->SetSize(XMFLOAT2(100, hei));
frictionSlider->SetPos(XMFLOAT2(x, y += step));
frictionSlider->OnSlide([&](wiEventArgs args) {
SoftBodyPhysicsComponent* physicscomponent = wiScene::GetScene().softbodies.GetComponent(entity);
if (physicscomponent != nullptr)
{
physicscomponent->friction = args.fValue;
}
});
meshWindow->AddWidget(frictionSlider);
impostorCreateButton = new wiButton("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->SetPos(XMFLOAT2(x - 50, y += step));
impostorCreateButton->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
Scene& scene = wiScene::GetScene();
scene.impostors.Create(entity).swapInDistance = impostorDistanceSlider->GetValue();
}
});
meshWindow->AddWidget(impostorCreateButton);
impostorDistanceSlider = new wiSlider(0, 1000, 100, 10000, "Impostor Distance: ");
impostorDistanceSlider->SetTooltip("Assign the distance where the mesh geometry should be switched to the impostor image.");
impostorDistanceSlider->SetSize(XMFLOAT2(100, hei));
impostorDistanceSlider->SetPos(XMFLOAT2(x, y += step));
impostorDistanceSlider->OnSlide([&](wiEventArgs args) {
ImpostorComponent* impostor = wiScene::GetScene().impostors.GetComponent(entity);
if (impostor != nullptr)
{
impostor->swapInDistance = args.fValue;
}
});
meshWindow->AddWidget(impostorDistanceSlider);
tessellationFactorSlider = new wiSlider(0, 16, 0, 10000, "Tessellation Factor: ");
tessellationFactorSlider->SetTooltip("Set the dynamic tessellation amount. Tessellation should be enabled in the Renderer window and your GPU must support it!");
tessellationFactorSlider->SetSize(XMFLOAT2(100, hei));
tessellationFactorSlider->SetPos(XMFLOAT2(x, y += step));
tessellationFactorSlider->OnSlide([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->tessellationFactor = args.fValue;
}
});
meshWindow->AddWidget(tessellationFactorSlider);
flipCullingButton = new wiButton("Flip Culling");
flipCullingButton->SetTooltip("Flip faces to reverse triangle culling order.");
flipCullingButton->SetSize(XMFLOAT2(240, hei));
flipCullingButton->SetPos(XMFLOAT2(x - 50, y += step));
flipCullingButton->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->FlipCulling();
SetEntity(entity);
}
});
meshWindow->AddWidget(flipCullingButton);
flipNormalsButton = new wiButton("Flip Normals");
flipNormalsButton->SetTooltip("Flip surface normals.");
flipNormalsButton->SetSize(XMFLOAT2(240, hei));
flipNormalsButton->SetPos(XMFLOAT2(x - 50, y += step));
flipNormalsButton->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->FlipNormals();
SetEntity(entity);
}
});
meshWindow->AddWidget(flipNormalsButton);
computeNormalsSmoothButton = new wiButton("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->SetPos(XMFLOAT2(x - 50, y += step));
computeNormalsSmoothButton->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_SMOOTH);
SetEntity(entity);
}
});
meshWindow->AddWidget(computeNormalsSmoothButton);
computeNormalsHardButton = new wiButton("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->SetPos(XMFLOAT2(x - 50, y += step));
computeNormalsHardButton->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_HARD);
SetEntity(entity);
}
});
meshWindow->AddWidget(computeNormalsHardButton);
recenterButton = new wiButton("Recenter");
recenterButton->SetTooltip("Recenter mesh to AABB center.");
recenterButton->SetSize(XMFLOAT2(240, hei));
recenterButton->SetPos(XMFLOAT2(x - 50, y += step));
recenterButton->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->Recenter();
SetEntity(entity);
}
});
meshWindow->AddWidget(recenterButton);
recenterToBottomButton = new wiButton("RecenterToBottom");
recenterToBottomButton->SetTooltip("Recenter mesh to AABB bottom.");
recenterToBottomButton->SetSize(XMFLOAT2(240, hei));
recenterToBottomButton->SetPos(XMFLOAT2(x - 50, y += step));
recenterToBottomButton->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->RecenterToBottom();
SetEntity(entity);
}
});
meshWindow->AddWidget(recenterToBottomButton);
x = 150;
y = 190;
terrainCheckBox = new wiCheckBox("Terrain: ");
terrainCheckBox->SetTooltip("If enabled, the mesh will use multiple materials and blend between them based on vertex colors.");
terrainCheckBox->SetPos(XMFLOAT2(x, y += step));
terrainCheckBox->OnClick([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
mesh->SetTerrain(args.bValue);
if (args.bValue && mesh->vertex_colors.empty())
{
mesh->vertex_colors.resize(mesh->vertex_positions.size());
std::fill(mesh->vertex_colors.begin(), mesh->vertex_colors.end(), wiColor::Red().rgba); // fill red (meaning only blend base material)
mesh->CreateRenderData();
for (auto& subset : mesh->subsets)
{
MaterialComponent* material = wiScene::GetScene().materials.GetComponent(subset.materialID);
if (material != nullptr)
{
material->SetUseVertexColors(true);
}
}
}
SetEntity(entity); // refresh information label
}
});
meshWindow->AddWidget(terrainCheckBox);
terrainMat1Combo = new wiComboBox("Terrain Material 1: ");
terrainMat1Combo->SetSize(XMFLOAT2(200, 20));
terrainMat1Combo->SetPos(XMFLOAT2(x + 180, y));
terrainMat1Combo->SetEnabled(false);
terrainMat1Combo->OnSelect([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
if (args.iValue == 0)
{
mesh->terrain_material1 = INVALID_ENTITY;
}
else
{
Scene& scene = wiScene::GetScene();
mesh->terrain_material1 = scene.materials.GetEntity(args.iValue - 1);
}
}
});
terrainMat1Combo->SetTooltip("Choose a sub terrain blend material. (GREEN vertex color mask)");
meshWindow->AddWidget(terrainMat1Combo);
terrainMat2Combo = new wiComboBox("Terrain Material 2: ");
terrainMat2Combo->SetSize(XMFLOAT2(200, 20));
terrainMat2Combo->SetPos(XMFLOAT2(x + 180, y += step));
terrainMat2Combo->SetEnabled(false);
terrainMat2Combo->OnSelect([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
if (args.iValue == 0)
{
mesh->terrain_material2 = INVALID_ENTITY;
}
else
{
Scene& scene = wiScene::GetScene();
mesh->terrain_material2 = scene.materials.GetEntity(args.iValue - 1);
}
}
});
terrainMat2Combo->SetTooltip("Choose a sub terrain blend material. (BLUE vertex color mask)");
meshWindow->AddWidget(terrainMat2Combo);
terrainMat3Combo = new wiComboBox("Terrain Material 3: ");
terrainMat3Combo->SetSize(XMFLOAT2(200, 20));
terrainMat3Combo->SetPos(XMFLOAT2(x + 180, y += step));
terrainMat3Combo->SetEnabled(false);
terrainMat3Combo->OnSelect([&](wiEventArgs args) {
MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity);
if (mesh != nullptr)
{
if (args.iValue == 0)
{
mesh->terrain_material3 = INVALID_ENTITY;
}
else
{
Scene& scene = wiScene::GetScene();
mesh->terrain_material3 = scene.materials.GetEntity(args.iValue - 1);
}
}
});
terrainMat3Combo->SetTooltip("Choose a sub terrain blend material. (ALPHA vertex color mask)");
meshWindow->AddWidget(terrainMat3Combo);
terrainGenButton = new wiButton("Generate Terrain...");
terrainGenButton->SetTooltip("Generate terrain meshes.");
terrainGenButton->SetSize(XMFLOAT2(200, 20));
terrainGenButton->SetPos(XMFLOAT2(x + 180, y += step));
terrainGenButton->OnClick([=](wiEventArgs args) {
if (terrainGenWindow != nullptr)
{
GUI->RemoveWidget(terrainGenWindow);
delete terrainGenWindow;
terrainGenWindow = nullptr;
}
if (this->rgb != nullptr)
{
stbi_image_free(this->rgb);
this->rgb = nullptr;
}
terrainGenWindow = new wiWindow(GUI, "Terrain Gen");
terrainGenWindow->SetSize(XMFLOAT2(260, 130));
GUI->AddWidget(terrainGenWindow);
float xx = 20;
float yy = 0;
float stepstep = 25;
float heihei = 20;
Scene& scene = wiScene::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] = wiColor::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);
editor->ClearSelected();
wiScene::PickResult pick;
pick.entity = entity;
pick.subsetIndex = 0;
editor->AddSelected(pick);
SetEntity(object.meshID);
wiSlider* dimXSlider;
wiSlider* dimYSlider;
wiSlider* dimZSlider;
dimXSlider = new wiSlider(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));
terrainGenWindow->AddWidget(dimXSlider);
dimYSlider = new wiSlider(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));
terrainGenWindow->AddWidget(dimYSlider);
dimZSlider = new wiSlider(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));
terrainGenWindow->AddWidget(dimZSlider);
dimXSlider->OnSlide([=](wiEventArgs args) {
this->width = (int)dimXSlider->GetValue();
this->height = (int)dimZSlider->GetValue();
generate_mesh(this->width, this->height);
});
dimZSlider->OnSlide([=](wiEventArgs args) {
this->width = (int)dimXSlider->GetValue();
this->height = (int)dimZSlider->GetValue();
generate_mesh(this->width, this->height);
});
dimYSlider->OnSlide([=](wiEventArgs args) {
generate_mesh(this->width, this->height, this->rgb, this->channelCount, args.fValue);
});
wiButton* heightmapButton = new wiButton("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));
heightmapButton->OnClick([=](wiEventArgs args) {
wiHelper::FileDialogParams params;
wiHelper::FileDialogResult result;
params.type = wiHelper::FileDialogParams::OPEN;
params.description = "Texture";
params.extensions.push_back("dds");
params.extensions.push_back("png");
params.extensions.push_back("jpg");
params.extensions.push_back("tga");
wiHelper::FileDialog(params, result);
if (result.ok) {
string fileName = result.filenames.front();
if (this->rgb != nullptr)
{
stbi_image_free(this->rgb);
this->rgb = nullptr;
}
int bpp;
this->rgb = stbi_load(fileName.c_str(), &this->width, &this->height, &bpp, channelCount);
generate_mesh(width, height, rgb, channelCount, dimYSlider->GetValue());
}
});
terrainGenWindow->AddWidget(heightmapButton);
terrainGenWindow->Translate(XMFLOAT3(
terrainGenButton->translation.x + terrainGenButton->scale.x + 10,
terrainGenButton->translation.y,
0)
);
});
meshWindow->AddWidget(terrainGenButton);
meshWindow->Translate(XMFLOAT3((float)wiRenderer::GetDevice()->GetScreenWidth() - 1000, 80, 0));
meshWindow->SetVisible(false);
SetEntity(INVALID_ENTITY);
}
MeshWindow::~MeshWindow()
{
meshWindow->RemoveWidgets(true);
GUI->RemoveWidget(meshWindow);
delete meshWindow;
if (terrainGenWindow != nullptr)
{
GUI->RemoveWidget(terrainGenWindow);
delete terrainGenWindow;
terrainGenWindow = nullptr;
}
if (this->rgb != nullptr)
{
stbi_image_free(this->rgb);
this->rgb = nullptr;
}
}
void MeshWindow::SetEntity(Entity entity)
{
this->entity = entity;
Scene&scene = wiScene::GetScene();
const MeshComponent* mesh = scene.meshes.GetComponent(entity);
if (mesh == nullptr || !mesh->IsTerrain())
{
if (terrainGenWindow != nullptr)
{
GUI->RemoveWidget(terrainGenWindow);
delete terrainGenWindow;
terrainGenWindow = nullptr;
}
if (this->rgb != nullptr)
{
stbi_image_free(this->rgb);
this->rgb = nullptr;
}
}
if (mesh != nullptr)
{
const NameComponent& name = *scene.names.GetComponent(entity);
stringstream ss("");
ss << "Mesh name: " << name.name << endl;
ss << "Vertex count: " << mesh->vertex_positions.size() << endl;
ss << "Index count: " << mesh->indices.size() << endl;
ss << "Subset count: " << mesh->subsets.size() << endl;
ss << endl << "Vertex buffers: ";
if (mesh->vertexBuffer_POS.IsValid()) ss << "position; ";
if (mesh->vertexBuffer_UV0.IsValid()) ss << "uvset_0; ";
if (mesh->vertexBuffer_UV1.IsValid()) ss << "uvset_1; ";
if (mesh->vertexBuffer_ATL.IsValid()) ss << "atlas; ";
if (mesh->vertexBuffer_COL.IsValid()) ss << "color; ";
if (mesh->vertexBuffer_PRE.IsValid()) ss << "prevPos; ";
if (mesh->vertexBuffer_BON.IsValid()) ss << "bone; ";
if (mesh->streamoutBuffer_POS.IsValid()) ss << "streamout; ";
if (mesh->IsTerrain()) ss << endl << endl << "Terrain will use 4 blend materials and blend by vertex colors, the default one is always the subset material and uses RED vertex color channel mask, the other 3 are selectable below.";
meshInfoLabel->SetText(ss.str());
terrainCheckBox->SetCheck(mesh->IsTerrain());
terrainMat1Combo->ClearItems();
terrainMat1Combo->AddItem("OFF (Use subset)");
terrainMat2Combo->ClearItems();
terrainMat2Combo->AddItem("OFF (Use subset)");
terrainMat3Combo->ClearItems();
terrainMat3Combo->AddItem("OFF (Use subset)");
for (size_t i = 0; i < scene.materials.GetCount(); ++i)
{
Entity entity = scene.materials.GetEntity(i);
const NameComponent& name = *scene.names.GetComponent(entity);
terrainMat1Combo->AddItem(name.name);
terrainMat2Combo->AddItem(name.name);
terrainMat3Combo->AddItem(name.name);
if (mesh->terrain_material1 == entity)
{
terrainMat1Combo->SetSelected((int)i + 1);
}
if (mesh->terrain_material2 == entity)
{
terrainMat2Combo->SetSelected((int)i + 1);
}
if (mesh->terrain_material3 == entity)
{
terrainMat3Combo->SetSelected((int)i + 1);
}
}
doubleSidedCheckBox->SetCheck(mesh->IsDoubleSided());
const ImpostorComponent* impostor = scene.impostors.GetComponent(entity);
if (impostor != nullptr)
{
impostorDistanceSlider->SetValue(impostor->swapInDistance);
}
tessellationFactorSlider->SetValue(mesh->GetTessellationFactor());
softbodyCheckBox->SetCheck(false);
SoftBodyPhysicsComponent* physicscomponent = wiScene::GetScene().softbodies.GetComponent(entity);
if (physicscomponent != nullptr)
{
softbodyCheckBox->SetCheck(true);
massSlider->SetValue(physicscomponent->mass);
frictionSlider->SetValue(physicscomponent->friction);
}
meshWindow->SetEnabled(true);
}
else
{
meshInfoLabel->SetText("Select a mesh...");
meshWindow->SetEnabled(false);
}
terrainGenButton->SetEnabled(true);
}