diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index f1f93c3b0..41dddb6e3 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -20,9 +20,10 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) float x = 200; float y = 0; + float step = 35; meshInfoLabel = new wiLabel("Mesh Info"); - meshInfoLabel->SetPos(XMFLOAT2(x, y += 30)); + meshInfoLabel->SetPos(XMFLOAT2(x, y += step)); meshInfoLabel->SetSize(XMFLOAT2(400, 150)); meshWindow->AddWidget(meshInfoLabel); @@ -30,7 +31,7 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) doubleSidedCheckBox = new wiCheckBox("Double Sided: "); doubleSidedCheckBox->SetTooltip("If enabled, the inside of the mesh will be visible."); - doubleSidedCheckBox->SetPos(XMFLOAT2(x, y += 30)); + doubleSidedCheckBox->SetPos(XMFLOAT2(x, y += step)); doubleSidedCheckBox->OnClick([&](wiEventArgs args) { if (mesh != nullptr) { @@ -42,7 +43,7 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) massSlider = new wiSlider(0, 5000, 0, 100000, "Mass: "); massSlider->SetTooltip("Set the mass amount for the physics engine."); massSlider->SetSize(XMFLOAT2(100, 30)); - massSlider->SetPos(XMFLOAT2(x, y += 30)); + massSlider->SetPos(XMFLOAT2(x, y += step)); massSlider->OnSlide([&](wiEventArgs args) { if (mesh != nullptr) { @@ -54,7 +55,7 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) frictionSlider = new wiSlider(0, 5000, 0, 100000, "Friction: "); frictionSlider->SetTooltip("Set the friction amount for the physics engine."); frictionSlider->SetSize(XMFLOAT2(100, 30)); - frictionSlider->SetPos(XMFLOAT2(x, y += 30)); + frictionSlider->SetPos(XMFLOAT2(x, y += step)); frictionSlider->OnSlide([&](wiEventArgs args) { if (mesh != nullptr) { @@ -66,7 +67,7 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) 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, 30)); - impostorCreateButton->SetPos(XMFLOAT2(x - 50, y += 30)); + impostorCreateButton->SetPos(XMFLOAT2(x - 50, y += step)); impostorCreateButton->OnClick([&](wiEventArgs args) { if (mesh != nullptr) { @@ -78,7 +79,7 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) 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, 30)); - impostorDistanceSlider->SetPos(XMFLOAT2(x, y += 30)); + impostorDistanceSlider->SetPos(XMFLOAT2(x, y += step)); impostorDistanceSlider->OnSlide([&](wiEventArgs args) { if (mesh != nullptr) { @@ -90,7 +91,7 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) 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, 30)); - tessellationFactorSlider->SetPos(XMFLOAT2(x, y += 30)); + tessellationFactorSlider->SetPos(XMFLOAT2(x, y += step)); tessellationFactorSlider->OnSlide([&](wiEventArgs args) { if (mesh != nullptr) { @@ -102,11 +103,12 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) computeNormalsSmoothButton = new wiButton("Compute Normals [SMOOTH]"); computeNormalsSmoothButton->SetTooltip("Compute surface normals of the mesh. Resulting normals will be unique per vertex."); computeNormalsSmoothButton->SetSize(XMFLOAT2(240, 30)); - computeNormalsSmoothButton->SetPos(XMFLOAT2(x - 50, y += 30)); + computeNormalsSmoothButton->SetPos(XMFLOAT2(x - 50, y += step)); computeNormalsSmoothButton->OnClick([&](wiEventArgs args) { if (mesh != nullptr) { mesh->ComputeNormals(true); + SetMesh(mesh); } }); meshWindow->AddWidget(computeNormalsSmoothButton); @@ -114,11 +116,12 @@ MeshWindow::MeshWindow(wiGUI* gui) : GUI(gui) computeNormalsHardButton = new wiButton("Compute Normals [HARD]"); computeNormalsHardButton->SetTooltip("Compute surface normals of the mesh. Resulting normals will be unique per face."); computeNormalsHardButton->SetSize(XMFLOAT2(240, 30)); - computeNormalsHardButton->SetPos(XMFLOAT2(x - 50, y += 30)); + computeNormalsHardButton->SetPos(XMFLOAT2(x - 50, y += step)); computeNormalsHardButton->OnClick([&](wiEventArgs args) { if (mesh != nullptr) { mesh->ComputeNormals(false); + SetMesh(mesh); } }); meshWindow->AddWidget(computeNormalsHardButton); @@ -142,9 +145,6 @@ MeshWindow::~MeshWindow() void MeshWindow::SetMesh(Mesh* mesh) { - if (this->mesh == mesh) - return; - this->mesh = mesh; if (mesh != nullptr) { diff --git a/WickedEngine/wiLoader.cpp b/WickedEngine/wiLoader.cpp index fad3989a6..a45ffe2c7 100644 --- a/WickedEngine/wiLoader.cpp +++ b/WickedEngine/wiLoader.cpp @@ -1864,24 +1864,6 @@ void Mesh::CreateBuffers() wiRenderer::GetDevice()->CreateBuffer(&bd, &InitData, &vertexBuffer_TEX); - //PHYSICALMAPPING - if (!physicsverts.empty() && physicalmapGP.empty()) - { - for (unsigned int i = 0; i < vertices_POS.size(); ++i) { - for (unsigned int j = 0; j < physicsverts.size(); ++j) { - if (fabs(vertices_POS[i].pos.x - physicsverts[j].x) < FLT_EPSILON - && fabs(vertices_POS[i].pos.y - physicsverts[j].y) < FLT_EPSILON - && fabs(vertices_POS[i].pos.z - physicsverts[j].z) < FLT_EPSILON - ) - { - physicalmapGP.push_back(j); - break; - } - } - } - } - - // Remap index buffer to be continuous across subsets and create gpu buffer data: uint32_t counter = 0; uint8_t stride; @@ -2132,6 +2114,7 @@ void Mesh::CreateVertexArrays() return; } + // We can call this function anytime to recreate data, so clean up first: vertices_POS.clear(); vertices_TEX.clear(); vertices_BON.clear(); @@ -2139,7 +2122,7 @@ void Mesh::CreateVertexArrays() // De-interleave vertex arrays: vertices_POS.resize(vertices_FULL.size()); vertices_TEX.resize(vertices_FULL.size()); - // do not resize vertices_BON just yet!! + // do not resize vertices_BON just yet, not every mesh will need bone vertex data! for (size_t i = 0; i < vertices_FULL.size(); ++i) { // Normalize normals: @@ -2161,6 +2144,7 @@ void Mesh::CreateVertexArrays() if (vertices_BON.empty()) { + // Allocate full bone vertex data when we find a correct bone weight. vertices_BON.resize(vertices_FULL.size()); } vertices_BON[i] = Vertex_BON(vertices_FULL[i]); @@ -2173,7 +2157,7 @@ void Mesh::CreateVertexArrays() // Save original vertices. This will be input for CPU skinning / soft bodies vertices_Transformed_POS = vertices_POS; - vertices_Transformed_PRE = vertices_POS; // pre <- pos!! + vertices_Transformed_PRE = vertices_POS; // pre <- pos!! (previous positions will have the current positions initially) // Map subset indices: for (auto& subset : subsets) @@ -2197,6 +2181,8 @@ void Mesh::CreateVertexArrays() } } + + // Goal positions, normals are controlling blending between animation and physics states for soft body rendering: goalPositions.clear(); goalNormals.clear(); if (goalVG >= 0) @@ -2205,49 +2191,159 @@ void Mesh::CreateVertexArrays() goalNormals.resize(vertexGroups[goalVG].vertices.size()); } + + // Mapping render vertices to physics vertex representation: + // the physics vertices contain unique position, not duplicated by texcoord or normals + // this way we can map several renderable vertices to one physics vertex + // but the mapping function will actually be indexed by renderable vertex index for efficient retrieval. + if (!physicsverts.empty() && physicalmapGP.empty()) + { + for (size_t i = 0; i < vertices_POS.size(); ++i) + { + for (size_t j = 0; j < physicsverts.size(); ++j) + { + if (fabs(vertices_POS[i].pos.x - physicsverts[j].x) < FLT_EPSILON + && fabs(vertices_POS[i].pos.y - physicsverts[j].y) < FLT_EPSILON + && fabs(vertices_POS[i].pos.z - physicsverts[j].z) < FLT_EPSILON + ) + { + physicalmapGP.push_back(static_cast(j)); + break; + } + } + } + } + arraysComplete = true; } void Mesh::ComputeNormals(bool smooth) { // Start recalculating normals: - vector newIndexBuffer; - vector newVertexBuffer; - for (size_t face = 0; face < indices.size() / 3; face++) + if (smooth) { - uint32_t i0 = indices[face * 3 + 0]; - uint32_t i1 = indices[face * 3 + 1]; - uint32_t i2 = indices[face * 3 + 2]; + // Compute smooth surface normals: - Vertex_FULL& v0 = vertices_FULL[i0]; - Vertex_FULL& v1 = vertices_FULL[i1]; - Vertex_FULL& v2 = vertices_FULL[i2]; - - XMVECTOR U = XMLoadFloat4(&v2.pos) - XMLoadFloat4(&v0.pos); - XMVECTOR V = XMLoadFloat4(&v1.pos) - XMLoadFloat4(&v0.pos); - - XMVECTOR N = XMVector3Cross(U, V); - N = XMVector3Normalize(N); - - XMFLOAT4 normal; - XMStoreFloat4(&normal, N); - - if (smooth) + // 1.) Zero normals, they will be averaged later + for (size_t i = 0; i < vertices_FULL.size() - 1; i++) { - v0.nor.x += normal.x; - v0.nor.y += normal.y; - v0.nor.z += normal.z; - - v1.nor.x += normal.x; - v1.nor.y += normal.y; - v1.nor.z += normal.z; - - v2.nor.x += normal.x; - v2.nor.y += normal.y; - v2.nor.z += normal.z; + vertices_FULL[i].nor = XMFLOAT4(0, 0, 0, 0); } - else + + // 2.) Find identical vertices by POSITION, accumulate face normals + for (size_t i = 0; i < vertices_FULL.size() - 1; i++) { + Vertex_FULL& v_search = vertices_FULL[i]; + + for (size_t ind = 0; ind < indices.size() / 3; ++ind) + { + uint32_t i0 = indices[ind * 3 + 0]; + uint32_t i1 = indices[ind * 3 + 1]; + uint32_t i2 = indices[ind * 3 + 2]; + + Vertex_FULL& v0 = vertices_FULL[i0]; + Vertex_FULL& v1 = vertices_FULL[i1]; + Vertex_FULL& v2 = vertices_FULL[i2]; + + bool match_pos0 = + fabs(v_search.pos.x - v0.pos.x) < FLT_EPSILON && + fabs(v_search.pos.y - v0.pos.y) < FLT_EPSILON && + fabs(v_search.pos.z - v0.pos.z) < FLT_EPSILON; + + bool match_pos1 = + fabs(v_search.pos.x - v1.pos.x) < FLT_EPSILON && + fabs(v_search.pos.y - v1.pos.y) < FLT_EPSILON && + fabs(v_search.pos.z - v1.pos.z) < FLT_EPSILON; + + bool match_pos2 = + fabs(v_search.pos.x - v2.pos.x) < FLT_EPSILON && + fabs(v_search.pos.y - v2.pos.y) < FLT_EPSILON && + fabs(v_search.pos.z - v2.pos.z) < FLT_EPSILON; + + if (match_pos0 || match_pos1 || match_pos2) + { + XMVECTOR U = XMLoadFloat4(&v2.pos) - XMLoadFloat4(&v0.pos); + XMVECTOR V = XMLoadFloat4(&v1.pos) - XMLoadFloat4(&v0.pos); + + XMVECTOR N = XMVector3Cross(U, V); + N = XMVector3Normalize(N); + + XMFLOAT3 normal; + XMStoreFloat3(&normal, N); + + v_search.nor.x += normal.x; + v_search.nor.y += normal.y; + v_search.nor.z += normal.z; + } + + } + } + + // 3.) Find unique vertices by POSITION and TEXCOORD and MATERIAL and remove duplicates + for (size_t i = 0; i < vertices_FULL.size() - 1; i++) + { + const Vertex_FULL& v0 = vertices_FULL[i]; + + for (size_t j = i + 1; j < vertices_FULL.size(); j++) + { + const Vertex_FULL& v1 = vertices_FULL[j]; + + bool unique_pos = + fabs(v0.pos.x - v1.pos.x) < FLT_EPSILON && + fabs(v0.pos.y - v1.pos.y) < FLT_EPSILON && + fabs(v0.pos.z - v1.pos.z) < FLT_EPSILON; + + bool unique_tex = + fabs(v0.tex.x - v1.tex.x) < FLT_EPSILON && + fabs(v0.tex.y - v1.tex.y) < FLT_EPSILON && + (int)v0.tex.z == (int)v1.tex.z; + + if (unique_pos && unique_tex) + { + for (size_t ind = 0; ind < indices.size(); ++ind) + { + if (indices[ind] == j) + { + indices[ind] = static_cast(i); + } + else if (indices[ind] > j && indices[ind] > 0) + { + indices[ind]--; + } + } + + vertices_FULL.erase(vertices_FULL.begin() + j); + } + + } + } + } + else + { + // Compute hard surface normals: + + vector newIndexBuffer; + vector newVertexBuffer; + + for (size_t face = 0; face < indices.size() / 3; face++) + { + uint32_t i0 = indices[face * 3 + 0]; + uint32_t i1 = indices[face * 3 + 1]; + uint32_t i2 = indices[face * 3 + 2]; + + Vertex_FULL& v0 = vertices_FULL[i0]; + Vertex_FULL& v1 = vertices_FULL[i1]; + Vertex_FULL& v2 = vertices_FULL[i2]; + + XMVECTOR U = XMLoadFloat4(&v2.pos) - XMLoadFloat4(&v0.pos); + XMVECTOR V = XMLoadFloat4(&v1.pos) - XMLoadFloat4(&v0.pos); + + XMVECTOR N = XMVector3Cross(U, V); + N = XMVector3Normalize(N); + + XMFLOAT3 normal; + XMStoreFloat3(&normal, N); + v0.nor.x = normal.x; v0.nor.y = normal.y; v0.nor.z = normal.z; @@ -2268,10 +2364,8 @@ void Mesh::ComputeNormals(bool smooth) newIndexBuffer.push_back(static_cast(newIndexBuffer.size())); newIndexBuffer.push_back(static_cast(newIndexBuffer.size())); } - } - if (!smooth) - { + // For hard surface normals, we created a new mesh in the previous loop through faces, so swap data: vertices_FULL = newVertexBuffer; indices = newIndexBuffer; } diff --git a/WickedEngine/wiMath.cpp b/WickedEngine/wiMath.cpp index fab0f7265..cf45e8c9e 100644 --- a/WickedEngine/wiMath.cpp +++ b/WickedEngine/wiMath.cpp @@ -90,6 +90,26 @@ namespace wiMath return ++x; } + float TriangleArea(const XMVECTOR& A, const XMVECTOR& B, const XMVECTOR& C) + { + // Heron's formula: + XMVECTOR a = XMVector3Length(B - A); + XMVECTOR b = XMVector3Length(C - A); + XMVECTOR c = XMVector3Length(C - B); + XMVECTOR p = (a + b + c) * 0.5f; + XMVECTOR areaSq = p * (p - a) * (p - b) * (p - c); + float area; + XMStoreFloat(&area, areaSq); + area = sqrtf(area); + return area; + } + float TriangleArea(float a, float b, float c) + { + // Heron's formula: + float p = (a + b + c) * 0.5f; + return sqrtf(p * (p - a) * (p - b) * (p - c)); + } + float InverseLerp(float value1, float value2, float pos) { diff --git a/WickedEngine/wiMath.h b/WickedEngine/wiMath.h index 87ba45307..f99572c81 100644 --- a/WickedEngine/wiMath.h +++ b/WickedEngine/wiMath.h @@ -27,6 +27,11 @@ namespace wiMath UINT GetNextPowerOfTwo(UINT x); float SmoothStep(float value1, float value2, float amount); + // A, B, C: trangle vertices + float TriangleArea(const XMVECTOR& A, const XMVECTOR& B, const XMVECTOR& C); + // a, b, c: trangle side lengths + float TriangleArea(float a, float b, float c); + XMFLOAT3 getCubicHermiteSplinePos(const XMFLOAT3& startPos, const XMFLOAT3& endPos , const XMFLOAT3& startTangent, const XMFLOAT3& endTangent , float atInterval); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 1324def0f..dbe22beab 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wiVersion // minor features, major updates const int minor = 16; // minor bug fixes, alterations, refactors, updates - const int revision = 32; + const int revision = 33; long GetVersion()