diff --git a/dust3d.pro b/dust3d.pro index 076e8dbd..8315e669 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -254,6 +254,9 @@ HEADERS += src/material.h SOURCES += src/fbxfile.cpp HEADERS += src/fbxfile.h +SOURCES += src/anglesmooth.cpp +HEADERS += src/anglesmooth.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/src/anglesmooth.cpp b/src/anglesmooth.cpp new file mode 100644 index 00000000..a5559639 --- /dev/null +++ b/src/anglesmooth.cpp @@ -0,0 +1,56 @@ +#include +#include +#include "dust3dutil.h" +#include "anglesmooth.h" + +void angleSmooth(const std::vector &vertices, + const std::vector> &triangles, + const std::vector &triangleNormals, + float thresholdAngleDegrees, std::vector &triangleVertexNormals) +{ + std::vector>> triangleVertexNormalsMapByIndicies(vertices.size()); + std::vector angleAreaWeightedNormals; + for (size_t triangleIndex = 0; triangleIndex < triangles.size(); ++triangleIndex) { + const auto &sourceTriangle = triangles[triangleIndex]; + size_t indicies[] = {std::get<0>(sourceTriangle), + std::get<1>(sourceTriangle), + std::get<2>(sourceTriangle)}; + const auto &v1 = vertices[indicies[0]]; + const auto &v2 = vertices[indicies[1]]; + const auto &v3 = vertices[indicies[2]]; + float area = areaOfTriangle(v1, v2, v3); + float angles[] = {radianBetweenVectors(v2-v1, v3-v1), + radianBetweenVectors(v1-v2, v3-v2), + radianBetweenVectors(v1-v3, v2-v3)}; + for (int i = 0; i < 3; ++i) { + triangleVertexNormalsMapByIndicies[indicies[i]].push_back({triangleIndex, angleAreaWeightedNormals.size()}); + angleAreaWeightedNormals.push_back(triangleNormals[triangleIndex] * area * angles[i]); + } + } + triangleVertexNormals = angleAreaWeightedNormals; + std::map, float> degreesBetweenFacesMap; + for (size_t vertexIndex = 0; vertexIndex < vertices.size(); ++vertexIndex) { + const auto &triangleVertices = triangleVertexNormalsMapByIndicies[vertexIndex]; + for (const auto &triangleVertex: triangleVertices) { + for (const auto &otherTriangleVertex: triangleVertices) { + if (triangleVertex.first == otherTriangleVertex.first) + continue; + float degrees = 0; + auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first}); + if (findDegreesResult == degreesBetweenFacesMap.end()) { + degrees = angleBetweenVectors(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]); + degreesBetweenFacesMap.insert({{triangleVertex.first, otherTriangleVertex.first}, degrees}); + degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees}); + } else { + degrees = findDegreesResult->second; + } + if (degrees > thresholdAngleDegrees) { + continue; + } + triangleVertexNormals[triangleVertex.second] += angleAreaWeightedNormals[otherTriangleVertex.second]; + } + } + } + for (auto &item: triangleVertexNormals) + item.normalize(); +} diff --git a/src/anglesmooth.h b/src/anglesmooth.h new file mode 100644 index 00000000..6dceaaea --- /dev/null +++ b/src/anglesmooth.h @@ -0,0 +1,11 @@ +#ifndef ANGLE_SMOOTH_H +#define ANGLE_SMOOTH_H +#include +#include + +void angleSmooth(const std::vector &vertices, + const std::vector> &triangles, + const std::vector &triangleNormals, + float thresholdAngleDegrees, std::vector &triangleVertexNormals); + +#endif diff --git a/src/dust3dutil.cpp b/src/dust3dutil.cpp index f745b1dc..9806dbf8 100644 --- a/src/dust3dutil.cpp +++ b/src/dust3dutil.cpp @@ -74,3 +74,19 @@ QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q return QQuaternion::slerp(q0, q1, t); } +float radianBetweenVectors(const QVector3D &first, const QVector3D &second) +{ + return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); +}; + +float angleBetweenVectors(const QVector3D &first, const QVector3D &second) +{ + return radianBetweenVectors(first, second) * 180.0 / M_PI; +} + +float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c) +{ + auto ab = b - a; + auto ac = c - a; + return 0.5 * QVector3D::crossProduct(ab, ac).length(); +}; diff --git a/src/dust3dutil.h b/src/dust3dutil.h index e4f069de..6c2a1436 100644 --- a/src/dust3dutil.h +++ b/src/dust3dutil.h @@ -19,5 +19,8 @@ float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D plan QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal); QString unifiedWindowTitle(const QString &text); QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t); +float radianBetweenVectors(const QVector3D &first, const QVector3D &second); +float angleBetweenVectors(const QVector3D &first, const QVector3D &second); +float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c); #endif diff --git a/src/fbxfile.cpp b/src/fbxfile.cpp index a0abbc87..233f4dad 100644 --- a/src/fbxfile.cpp +++ b/src/fbxfile.cpp @@ -1476,11 +1476,12 @@ FbxFileWriter::FbxFileWriter(MeshResultContext &resultContext, layerElementNormal.addProperty((int32_t)0); layerElementNormal.addPropertyNode("Version", (int32_t)101); layerElementNormal.addPropertyNode("Name", ""); - layerElementNormal.addPropertyNode("MappingInformationType", "ByVertice"); + layerElementNormal.addPropertyNode("MappingInformationType", "ByPolygonVertex"); layerElementNormal.addPropertyNode("ReferenceInformationType", "Direct"); std::vector normals; - for (decltype(resultContext.vertices.size()) i = 0; i < resultContext.vertices.size(); ++i) { - const auto &n = resultContext.interpolatedVertexNormals()[i]; + const auto &triangleVertexNormals = resultContext.interpolatedTriangleVertexNormals(); + for (decltype(triangleVertexNormals.size()) i = 0; i < triangleVertexNormals.size(); ++i) { + const auto &n = triangleVertexNormals[i]; normals.push_back((double)n.x()); normals.push_back((double)n.y()); normals.push_back((double)n.z()); diff --git a/src/meshloader.cpp b/src/meshloader.cpp index 163d7bc0..47dbca59 100644 --- a/src/meshloader.cpp +++ b/src/meshloader.cpp @@ -7,6 +7,7 @@ #include "theme.h" #include "positionmap.h" #include "ds3file.h" +#include "anglesmooth.h" #define MAX_VERTICES_PER_FACE 100 @@ -104,21 +105,15 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo GLfloat *triangleNormals = new GLfloat[triangleCount * 3]; int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, triangleCount * 3); - auto angleBetweenVectors = [](const QVector3D &first, const QVector3D &second) { - return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); - }; - - auto areaOfTriangle = [](const QVector3D &a, const QVector3D &b, const QVector3D &c) { - auto ab = b - a; - auto ac = c - a; - return 0.5 * QVector3D::crossProduct(ab, ac).length(); - }; - float modelR = defaultColor.redF(); float modelG = defaultColor.greenF(); float modelB = defaultColor.blueF(); m_triangleVertexCount = triangleCount * 3; m_triangleVertices = new Vertex[m_triangleVertexCount * 3]; + const std::vector &inputVerticesForSmoothAngle = m_triangulatedVertices; + std::vector> inputTrianglesForSmoothAngle; + std::vector inputNormalsForSmoothAngle; + float thresholdAngleDegrees = 60; for (int i = 0; i < triangleCount; i++) { int firstIndex = i * 3; float useColorR = modelR; @@ -140,21 +135,14 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo float area = 1.0; float angles[3] = {1.0, 1.0, 1.0}; if (smoothNormal) { - for (int j = 0; j < 3; j++) { - assert(firstIndex + j < loadedTriangleVertexIndexItemCount); - int posIndex = triangleIndices[firstIndex + j] * 3; - assert(posIndex < loadedTriangleVertexPositionItemCount); - positions[j] = QVector3D(triangleVertexPositions[posIndex + 0], - triangleVertexPositions[posIndex + 1], - triangleVertexPositions[posIndex + 2]); - } - const auto &v1 = positions[0]; - const auto &v2 = positions[1]; - const auto &v3 = positions[2]; - area = areaOfTriangle(v1, v2, v3); - angles[0] = angleBetweenVectors(v2-v1, v3-v1); - angles[1] = angleBetweenVectors(v1-v2, v3-v2); - angles[2] = angleBetweenVectors(v1-v3, v2-v3); + inputTrianglesForSmoothAngle.push_back({ + triangleIndices[firstIndex + 0], + triangleIndices[firstIndex + 1], + triangleIndices[firstIndex + 2] + }); + inputNormalsForSmoothAngle.push_back(QVector3D(triangleNormals[firstIndex + 0], + triangleNormals[firstIndex + 1], + triangleNormals[firstIndex + 2])); } for (int j = 0; j < 3; j++) { assert(firstIndex + j < loadedTriangleVertexIndexItemCount); @@ -169,34 +157,26 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo v->normX = triangleNormals[firstIndex + 0]; v->normY = triangleNormals[firstIndex + 1]; v->normZ = triangleNormals[firstIndex + 2]; - if (smoothNormal) { - auto factor = area * angles[j]; - triangleVertexSmoothNormals[posIndex + 0] += v->normX * factor; - triangleVertexSmoothNormals[posIndex + 1] += v->normY * factor; - triangleVertexSmoothNormals[posIndex + 2] += v->normZ * factor; - } v->colorR = useColorR; v->colorG = useColorG; v->colorB = useColorB; v->metalness = useMetalness; v->roughness = useRoughness; } - - m_triangulatedFaces.push_back(triangulatedFace); } if (smoothNormal) { + std::vector resultNormals; + angleSmooth(inputVerticesForSmoothAngle, inputTrianglesForSmoothAngle, inputNormalsForSmoothAngle, + thresholdAngleDegrees, resultNormals); + size_t normalIndex = 0; for (int i = 0; i < triangleCount; i++) { int firstIndex = i * 3; for (int j = 0; j < 3; j++) { assert(firstIndex + j < loadedTriangleVertexIndexItemCount); - int posIndex = triangleIndices[firstIndex + j] * 3; Vertex *v = &m_triangleVertices[firstIndex + j]; - QVector3D normal(triangleVertexSmoothNormals[posIndex + 0], - triangleVertexSmoothNormals[posIndex + 1], - triangleVertexSmoothNormals[posIndex + 2]); - normal.normalize(); + const auto &normal = resultNormals[normalIndex++]; v->normX = normal.x(); v->normY = normal.y(); v->normZ = normal.z(); diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index 20dbfa9b..f4922270 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -9,6 +9,7 @@ #include "theme.h" #include "meshresultcontext.h" #include "positionmap.h" +#include "anglesmooth.h" struct HalfColorEdge { @@ -32,7 +33,7 @@ MeshResultContext::MeshResultContext() : m_bmeshNodeMapResolved(false), m_resultPartsResolved(false), m_resultTriangleUvsResolved(false), - m_vertexNormalsInterpolated(false), + m_triangleVertexNormalsInterpolated(false), m_triangleTangentsResolved(false) { } @@ -318,6 +319,7 @@ void MeshResultContext::calculateResultParts(std::map &parts) { std::map, int> oldVertexToNewMap; for (auto x = 0u; x < triangles.size(); x++) { + size_t normalIndex = x * 3; const auto &triangle = triangles[x]; const auto &sourceNode = triangleSourceNodes()[x]; auto it = parts.find(sourceNode.first); @@ -330,13 +332,14 @@ void MeshResultContext::calculateResultParts(std::map &parts) ResultTriangle newTriangle; newTriangle.normal = triangle.normal; for (auto i = 0u; i < 3; i++) { + const auto &normal = interpolatedTriangleVertexNormals()[normalIndex++]; auto key = std::make_pair(sourceNode.first, triangle.indicies[i]); const auto &it = oldVertexToNewMap.find(key); bool isNewVertex = it == oldVertexToNewMap.end(); bool isSeamVertex = m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i]); if (isNewVertex || isSeamVertex) { int newIndex = resultPart.vertices.size(); - resultPart.interpolatedVertexNormals.push_back(interpolatedVertexNormals()[triangle.indicies[i]]); + resultPart.interpolatedVertexNormals.push_back(normal); resultPart.verticesOldIndicies.push_back(triangle.indicies[i]); resultPart.vertices.push_back(vertices[triangle.indicies[i]]); ResultVertexUv vertexUv; @@ -417,44 +420,28 @@ void MeshResultContext::calculateResultTriangleUvs(std::vector } } -void MeshResultContext::interpolateVertexNormals(std::vector &resultNormals) +void MeshResultContext::interpolateTriangleVertexNormals(std::vector &resultNormals) { - resultNormals.resize(vertices.size()); - - auto angleBetweenVectors = [](const QVector3D &first, const QVector3D &second) { - return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); - }; - - auto areaOfTriangle = [](const QVector3D &a, const QVector3D &b, const QVector3D &c) { - auto ab = b - a; - auto ac = c - a; - return 0.5 * QVector3D::crossProduct(ab, ac).length(); - }; - - for (size_t triangleIndex = 0; triangleIndex < triangles.size(); triangleIndex++) { - const auto &sourceTriangle = triangles[triangleIndex]; - const auto &v1 = vertices[sourceTriangle.indicies[0]].position; - const auto &v2 = vertices[sourceTriangle.indicies[1]].position; - const auto &v3 = vertices[sourceTriangle.indicies[2]].position; - float area = areaOfTriangle(v1, v2, v3); - float angles[] = {angleBetweenVectors(v2-v1, v3-v1), - angleBetweenVectors(v1-v2, v3-v2), - angleBetweenVectors(v1-v3, v2-v3)}; - for (int i = 0; i < 3; i++) - resultNormals[sourceTriangle.indicies[i]] += sourceTriangle.normal * area * angles[i]; + std::vector inputVerticies; + std::vector> inputTriangles; + std::vector inputNormals; + float thresholdAngleDegrees = 60; + for (const auto &vertex: vertices) + inputVerticies.push_back(vertex.position); + for (const auto &triangle: triangles) { + inputTriangles.push_back({triangle.indicies[0], triangle.indicies[1], triangle.indicies[2]}); + inputNormals.push_back(triangle.normal); } - - for (auto &item: resultNormals) - item.normalize(); + angleSmooth(inputVerticies, inputTriangles, inputNormals, thresholdAngleDegrees, resultNormals); } -const std::vector &MeshResultContext::interpolatedVertexNormals() +const std::vector &MeshResultContext::interpolatedTriangleVertexNormals() { - if (!m_vertexNormalsInterpolated) { - m_vertexNormalsInterpolated = true; - interpolateVertexNormals(m_interpolatedVertexNormals); + if (!m_triangleVertexNormalsInterpolated) { + m_triangleVertexNormalsInterpolated = true; + interpolateTriangleVertexNormals(m_interpolatedTriangleVertexNormals); } - return m_interpolatedVertexNormals; + return m_interpolatedTriangleVertexNormals; } const std::vector &MeshResultContext::triangleTangents() diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h index 0cb9a5be..60fd9804 100644 --- a/src/meshresultcontext.h +++ b/src/meshresultcontext.h @@ -80,7 +80,7 @@ public: const std::map &parts(); const std::vector &triangleUvs(); const std::map> &vertexSourceMap(); - const std::vector &interpolatedVertexNormals(); + const std::vector &interpolatedTriangleVertexNormals(); const std::vector &triangleTangents(); private: bool m_triangleSourceResolved; @@ -89,7 +89,7 @@ private: bool m_bmeshNodeMapResolved; bool m_resultPartsResolved; bool m_resultTriangleUvsResolved; - bool m_vertexNormalsInterpolated; + bool m_triangleVertexNormalsInterpolated; bool m_triangleTangentsResolved; private: std::vector> m_triangleSourceNodes; @@ -101,7 +101,7 @@ private: std::set m_seamVertices; std::map> m_vertexSourceMap; std::map m_rearrangedVerticesToOldIndexMap; - std::vector m_interpolatedVertexNormals; + std::vector m_interpolatedTriangleVertexNormals; std::vector m_triangleTangents; private: void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes, std::map> &vertexSourceMap); @@ -111,7 +111,7 @@ private: void calculateBmeshNodeMap(std::map, BmeshNode *> &bmeshNodeMap); void calculateResultParts(std::map &parts); void calculateResultTriangleUvs(std::vector &uvs, std::set &seamVertices); - void interpolateVertexNormals(std::vector &resultNormals); + void interpolateTriangleVertexNormals(std::vector &resultNormals); void calculateTriangleTangents(std::vector &tangents); };