#include "stdafx.h" #include "MeshWindow.h" #include "Editor.h" #include "Utility/stb_image.h" #include using namespace std; using namespace wiECS; using namespace wiScene; struct TerraGen : public wiWindow { wiSlider dimXSlider; wiSlider dimYSlider; wiSlider dimZSlider; wiButton heightmapButton; // heightmap texture: unsigned char* rgb = nullptr; const int channelCount = 4; int width = 0, height = 0; TerraGen() { wiWindow::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) { wiWindow::Create("Mesh Window"); SetSize(XMFLOAT2(580, 500)); float x = 150; float y = 0; float hei = 18; float step = hei + 2; meshInfoLabel.Create("Mesh Info"); meshInfoLabel.SetPos(XMFLOAT2(x - 50, y += step)); meshInfoLabel.SetSize(XMFLOAT2(450, 180)); AddWidget(&meshInfoLabel); y += 190; doubleSidedCheckBox.Create("Double Sided: "); doubleSidedCheckBox.SetTooltip("If enabled, the inside of the mesh will be visible."); doubleSidedCheckBox.SetSize(XMFLOAT2(hei, hei)); doubleSidedCheckBox.SetPos(XMFLOAT2(x, y += step)); doubleSidedCheckBox.OnClick([&](wiEventArgs args) { MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->SetDoubleSided(args.bValue); } }); AddWidget(&doubleSidedCheckBox); softbodyCheckBox.Create("Soft body: "); softbodyCheckBox.SetTooltip("Enable soft body simulation. Tip: Use the Paint Tool to control vertex pinning."); softbodyCheckBox.SetSize(XMFLOAT2(hei, hei)); 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); } } }); AddWidget(&softbodyCheckBox); massSlider.Create(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; } }); AddWidget(&massSlider); frictionSlider.Create(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; } }); AddWidget(&frictionSlider); 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.SetPos(XMFLOAT2(x - 50, y += step)); impostorCreateButton.OnClick([&](wiEventArgs args) { Scene& scene = wiScene::GetScene(); ImpostorComponent* impostor = scene.impostors.GetComponent(entity); if (impostor == nullptr) { impostorCreateButton.SetText("Delete Impostor"); scene.impostors.Create(entity).swapInDistance = impostorDistanceSlider.GetValue(); } else { impostorCreateButton.SetText("Create Impostor"); scene.impostors.Remove(entity); } }); AddWidget(&impostorCreateButton); impostorDistanceSlider.Create(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; } }); AddWidget(&impostorDistanceSlider); tessellationFactorSlider.Create(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; } }); AddWidget(&tessellationFactorSlider); flipCullingButton.Create("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); } }); AddWidget(&flipCullingButton); flipNormalsButton.Create("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); } }); AddWidget(&flipNormalsButton); 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.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); } }); AddWidget(&computeNormalsSmoothButton); 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.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); } }); AddWidget(&computeNormalsHardButton); recenterButton.Create("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); } }); AddWidget(&recenterButton); recenterToBottomButton.Create("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); } }); AddWidget(&recenterToBottomButton); x = 150; y = 190; terrainCheckBox.Create("Terrain: "); terrainCheckBox.SetTooltip("If enabled, the mesh will use multiple materials and blend between them based on vertex colors."); terrainCheckBox.SetSize(XMFLOAT2(hei, hei)); 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 } }); AddWidget(&terrainCheckBox); terrainMat1Combo.Create("Terrain Material 1: "); terrainMat1Combo.SetSize(XMFLOAT2(200, hei)); 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)"); AddWidget(&terrainMat1Combo); terrainMat2Combo.Create("Terrain Material 2: "); terrainMat2Combo.SetSize(XMFLOAT2(200, hei)); 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)"); AddWidget(&terrainMat2Combo); terrainMat3Combo.Create("Terrain Material 3: "); terrainMat3Combo.SetSize(XMFLOAT2(200, hei)); 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)"); 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([=](wiEventArgs 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 = 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); terragen.dimXSlider.OnSlide([=](wiEventArgs args) { terragen.width = (int)terragen.dimXSlider.GetValue(); terragen.height = (int)terragen.dimZSlider.GetValue(); generate_mesh(terragen.width, terragen.height); }); terragen.dimZSlider.OnSlide([=](wiEventArgs args) { terragen.width = (int)terragen.dimXSlider.GetValue(); terragen.height = (int)terragen.dimZSlider.GetValue(); generate_mesh(terragen.width, terragen.height); }); terragen.dimYSlider.OnSlide([=](wiEventArgs args) { generate_mesh(terragen.width, terragen.height, terragen.rgb, terragen.channelCount, args.fValue); }); terragen.heightmapButton.OnClick([=](wiEventArgs args) { wiHelper::FileDialogParams params; 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, [=](std::string fileName) { wiEvent::Subscribe_Once(SYSTEM_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)); morphTargetCombo.SetPos(XMFLOAT2(x + 280, y += step)); morphTargetCombo.OnSelect([&](wiEventArgs args) { MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr && args.iValue < mesh->targets.size()) { morphTargetSlider.SetValue(mesh->targets[args.iValue].weight); } }); morphTargetCombo.SetTooltip("Choose a morph target to edit weight."); AddWidget(&morphTargetCombo); morphTargetSlider.Create(0, 1, 0, 100000, "Weight: "); morphTargetSlider.SetTooltip("Set the weight for morph target"); morphTargetSlider.SetSize(XMFLOAT2(100, hei)); morphTargetSlider.SetPos(XMFLOAT2(x + 280, y += step)); morphTargetSlider.OnSlide([&](wiEventArgs args) { MeshComponent* mesh = wiScene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr && morphTargetCombo.GetSelected() < mesh->targets.size()) { mesh->targets[morphTargetCombo.GetSelected()].weight = args.fValue; mesh->SetDirtyMorph(); } }); AddWidget(&morphTargetSlider); Translate(XMFLOAT3((float)wiRenderer::GetDevice()->GetScreenWidth() - 1000, 80, 0)); SetVisible(false); SetEntity(INVALID_ENTITY); } void MeshWindow::SetEntity(Entity entity) { this->entity = entity; Scene&scene = wiScene::GetScene(); 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); 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); } uint8_t selected = morphTargetCombo.GetSelected(); morphTargetCombo.ClearItems(); for (size_t i = 0; i < mesh->targets.size(); i++) { morphTargetCombo.AddItem(std::to_string(i).c_str()); } if (selected < mesh->targets.size()) { morphTargetCombo.SetSelected(selected); } SetEnabled(true); } else { meshInfoLabel.SetText("Select a mesh..."); SetEnabled(false); } terrainGenButton.SetEnabled(true); }