#include "stdafx.h" #include "MeshWindow.h" #include "Editor.h" #include "Utility/stb_image.h" #include "meshoptimizer/meshoptimizer.h" #include using namespace wi::ecs; using namespace wi::scene; void MeshWindow::Create(EditorComponent* editor) { wi::gui::Window::Create("Mesh Window"); SetSize(XMFLOAT2(580, 580)); float x = 150; float y = 0; float hei = 18; float step = hei + 2; float infolabel_height = 190; meshInfoLabel.Create("Mesh Info"); meshInfoLabel.SetPos(XMFLOAT2(x - 50, y += step)); meshInfoLabel.SetSize(XMFLOAT2(450, infolabel_height)); meshInfoLabel.SetColor(wi::Color::Transparent()); AddWidget(&meshInfoLabel); // Left side: y = infolabel_height + 10; subsetComboBox.Create("Selected subset: "); subsetComboBox.SetSize(XMFLOAT2(40, hei)); subsetComboBox.SetPos(XMFLOAT2(x, y += step)); subsetComboBox.SetEnabled(false); subsetComboBox.OnSelect([=](wi::gui::EventArgs args) { Scene& scene = wi::scene::GetScene(); MeshComponent* mesh = scene.meshes.GetComponent(entity); if (mesh != nullptr) { subset = args.iValue; if (!editor->translator.selected.empty()) { editor->translator.selected.back().subsetIndex = subset; } } }); subsetComboBox.SetTooltip("Select a subset. A subset can also be selected by picking it in the 3D scene."); AddWidget(&subsetComboBox); 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([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::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([&](wi::gui::EventArgs args) { Scene& scene = wi::scene::GetScene(); SoftBodyPhysicsComponent* physicscomponent = scene.softbodies.GetComponent(entity); if (args.bValue) { if (physicscomponent == nullptr) { SoftBodyPhysicsComponent& softbody = scene.softbodies.Create(entity); softbody.friction = frictionSlider.GetValue(); softbody.restitution = restitutionSlider.GetValue(); softbody.mass = massSlider.GetValue(); } } else { if (physicscomponent != nullptr) { scene.softbodies.Remove(entity); } } }); AddWidget(&softbodyCheckBox); massSlider.Create(0, 10, 1, 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([&](wi::gui::EventArgs args) { SoftBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->mass = args.fValue; } }); AddWidget(&massSlider); frictionSlider.Create(0, 1, 0.5f, 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([&](wi::gui::EventArgs args) { SoftBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->friction = args.fValue; } }); AddWidget(&frictionSlider); restitutionSlider.Create(0, 1, 0, 100000, "Restitution: "); restitutionSlider.SetTooltip("Set the restitution amount for the physics engine."); restitutionSlider.SetSize(XMFLOAT2(100, hei)); restitutionSlider.SetPos(XMFLOAT2(x, y += step)); restitutionSlider.OnSlide([&](wi::gui::EventArgs args) { SoftBodyPhysicsComponent* physicscomponent = wi::scene::GetScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->restitution = args.fValue; } }); AddWidget(&restitutionSlider); 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(200, hei)); impostorCreateButton.SetPos(XMFLOAT2(x - 50, y += step)); impostorCreateButton.OnClick([&](wi::gui::EventArgs args) { Scene& scene = wi::scene::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([&](wi::gui::EventArgs args) { ImpostorComponent* impostor = wi::scene::GetScene().impostors.GetComponent(entity); if (impostor != nullptr) { impostor->swapInDistance = args.fValue; } }); AddWidget(&impostorDistanceSlider); tessellationFactorSlider.Create(0, 100, 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([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::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(200, hei)); flipCullingButton.SetPos(XMFLOAT2(x - 50, y += step)); flipCullingButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->FlipCulling(); SetEntity(entity, subset); } }); AddWidget(&flipCullingButton); flipNormalsButton.Create("Flip Normals"); flipNormalsButton.SetTooltip("Flip surface normals."); 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); if (mesh != nullptr) { mesh->FlipNormals(); SetEntity(entity, subset); } }); 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(200, hei)); computeNormalsSmoothButton.SetPos(XMFLOAT2(x - 50, y += step)); computeNormalsSmoothButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_SMOOTH); SetEntity(entity, subset); } }); 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(200, hei)); computeNormalsHardButton.SetPos(XMFLOAT2(x - 50, y += step)); computeNormalsHardButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr) { mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_HARD); SetEntity(entity, subset); } }); AddWidget(&computeNormalsHardButton); recenterButton.Create("Recenter"); recenterButton.SetTooltip("Recenter mesh to AABB center."); 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); if (mesh != nullptr) { mesh->Recenter(); SetEntity(entity, subset); } }); AddWidget(&recenterButton); recenterToBottomButton.Create("RecenterToBottom"); recenterToBottomButton.SetTooltip("Recenter mesh to AABB bottom."); 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); if (mesh != nullptr) { mesh->RecenterToBottom(); SetEntity(entity, subset); } }); 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(200, hei)); optimizeButton.SetPos(XMFLOAT2(x - 50, y += step)); optimizeButton.OnClick([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr) { // https://github.com/zeux/meshoptimizer#vertex-cache-optimization size_t index_count = mesh->indices.size(); size_t vertex_count = mesh->vertex_positions.size(); wi::vector indices(index_count); meshopt_optimizeVertexCache(indices.data(), mesh->indices.data(), index_count, vertex_count); mesh->indices = indices; mesh->CreateRenderData(); SetEntity(entity, subset); } }); AddWidget(&optimizeButton); // Right side: x = 150; y = infolabel_height + 10; subsetMaterialComboBox.Create("Subset Material: "); subsetMaterialComboBox.SetSize(XMFLOAT2(200, hei)); subsetMaterialComboBox.SetPos(XMFLOAT2(x + 180, y += step)); subsetMaterialComboBox.SetEnabled(false); subsetMaterialComboBox.OnSelect([&](wi::gui::EventArgs args) { Scene& scene = wi::scene::GetScene(); MeshComponent* mesh = scene.meshes.GetComponent(entity); if (mesh != nullptr && subset >= 0 && subset < mesh->subsets.size()) { MeshComponent::MeshSubset& meshsubset = mesh->subsets[subset]; if (args.iValue == 0) { meshsubset.materialID = INVALID_ENTITY; } else { MeshComponent::MeshSubset& meshsubset = mesh->subsets[subset]; meshsubset.materialID = scene.materials.GetEntity(args.iValue - 1); } } }); subsetMaterialComboBox.SetTooltip("Set the base material of the selected MeshSubset"); AddWidget(&subsetMaterialComboBox); morphTargetCombo.Create("Morph Target:"); morphTargetCombo.SetSize(XMFLOAT2(100, hei)); morphTargetCombo.SetPos(XMFLOAT2(x + 280, y += step)); morphTargetCombo.OnSelect([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr && args.iValue < (int)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([&](wi::gui::EventArgs args) { MeshComponent* mesh = wi::scene::GetScene().meshes.GetComponent(entity); if (mesh != nullptr && morphTargetCombo.GetSelected() < (int)mesh->targets.size()) { mesh->targets[morphTargetCombo.GetSelected()].weight = args.fValue; mesh->dirty_morph = true; } }); 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); SetEntity(INVALID_ENTITY, -1); } void MeshWindow::SetEntity(Entity entity, int subset) { subset = std::max(0, subset); this->entity = entity; this->subset = subset; Scene& scene = wi::scene::GetScene(); const MeshComponent* mesh = scene.meshes.GetComponent(entity); if (mesh != nullptr) { const NameComponent& name = *scene.names.GetComponent(entity); std::string ss; 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()) + " (" + 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; "; if (mesh->vb_uvs.IsValid()) ss += "uvsets; "; if (mesh->vb_atl.IsValid()) ss += "atlas; "; if (mesh->vb_col.IsValid()) ss += "color; "; if (mesh->so_pre.IsValid()) ss += "previous_position; "; if (mesh->vb_bon.IsValid()) ss += "bone; "; if (mesh->vb_tan.IsValid()) ss += "tangent; "; if (mesh->so_pos_nor_wind.IsValid()) ss += "streamout_position; "; if (mesh->so_tan.IsValid()) ss += "streamout_tangents; "; meshInfoLabel.SetText(ss); subsetComboBox.ClearItems(); for (size_t i = 0; i < mesh->subsets.size(); ++i) { subsetComboBox.AddItem(std::to_string(i)); } if (subset >= 0) { subsetComboBox.SetSelectedWithoutCallback(subset); } subsetMaterialComboBox.ClearItems(); subsetMaterialComboBox.AddItem("NO MATERIAL"); for (size_t i = 0; i < scene.materials.GetCount(); ++i) { Entity entity = scene.materials.GetEntity(i); const NameComponent& name = *scene.names.GetComponent(entity); subsetMaterialComboBox.AddItem(name.name); if (subset >= 0 && subset < mesh->subsets.size() && mesh->subsets[subset].materialID == entity) { subsetMaterialComboBox.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 = wi::scene::GetScene().softbodies.GetComponent(entity); if (physicscomponent != nullptr) { softbodyCheckBox.SetCheck(true); massSlider.SetValue(physicscomponent->mass); frictionSlider.SetValue(physicscomponent->friction); restitutionSlider.SetValue(physicscomponent->restitution); } 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); if (mesh->targets.empty()) { morphTargetCombo.SetEnabled(false); morphTargetSlider.SetEnabled(false); } else { morphTargetCombo.SetEnabled(true); morphTargetSlider.SetEnabled(true); } } else { meshInfoLabel.SetText("Select a mesh..."); SetEnabled(false); } mergeButton.SetEnabled(true); }