From 5b06f64fddd834974eb1bb6a8703e20bf861ec53 Mon Sep 17 00:00:00 2001 From: Jeremy HU Date: Sat, 8 Oct 2022 07:26:30 +1100 Subject: [PATCH] Implement rope mesh builder --- application/application.pro | 2 + application/sources/mesh_generator.cc | 3 +- application/sources/model_mesh.cc | 28 +++-- application/sources/model_mesh.h | 6 +- dust3d/base/math.h | 2 +- dust3d/base/vector3.cc | 2 +- dust3d/base/vector3.h | 2 +- dust3d/mesh/mesh_generator.cc | 69 ++++++++--- dust3d/mesh/mesh_generator.h | 2 + dust3d/mesh/rope_mesh.cc | 157 ++++++++++++++++++++++++++ dust3d/mesh/rope_mesh.h | 74 ++++++++++++ dust3d/mesh/stitch_mesh_builder.h | 2 + 12 files changed, 321 insertions(+), 28 deletions(-) create mode 100644 dust3d/mesh/rope_mesh.cc create mode 100644 dust3d/mesh/rope_mesh.h diff --git a/application/application.pro b/application/application.pro index 07e9e1fc..6bbb99ea 100644 --- a/application/application.pro +++ b/application/application.pro @@ -287,6 +287,8 @@ HEADERS += ../dust3d/mesh/resolve_triangle_source_node.h SOURCES += ../dust3d/mesh/resolve_triangle_source_node.cc HEADERS += ../dust3d/mesh/resolve_triangle_tangent.h SOURCES += ../dust3d/mesh/resolve_triangle_tangent.cc +HEADERS += ../dust3d/mesh/rope_mesh.h +SOURCES += ../dust3d/mesh/rope_mesh.cc HEADERS += ../dust3d/mesh/smooth_normal.h SOURCES += ../dust3d/mesh/smooth_normal.cc HEADERS += ../dust3d/mesh/stitch_mesh_builder.h diff --git a/application/sources/mesh_generator.cc b/application/sources/mesh_generator.cc index a73b95e9..cb47b88d 100644 --- a/application/sources/mesh_generator.cc +++ b/application/sources/mesh_generator.cc @@ -64,7 +64,8 @@ void MeshGenerator::process() previewTriangleVertexNormals, it->second.color, it->second.metalness, - it->second.roughness); + it->second.roughness, + it->second.vertexProperties.empty() ? nullptr : &it->second.vertexProperties); } if (nullptr != m_object) diff --git a/application/sources/model_mesh.cc b/application/sources/model_mesh.cc index fcfb64ee..545d586b 100644 --- a/application/sources/model_mesh.cc +++ b/application/sources/model_mesh.cc @@ -68,11 +68,13 @@ ModelMesh::ModelMesh(ModelOpenGLVertex *triangleVertices, int vertexNum) : { } -ModelMesh::ModelMesh(const std::vector &vertices, const std::vector> &triangles, +ModelMesh::ModelMesh(const std::vector &vertices, + const std::vector> &triangles, const std::vector> &triangleVertexNormals, const dust3d::Color &color, float metalness, - float roughness) + float roughness, + const std::vector> *vertexProperties) { m_triangleVertexCount = (int)triangles.size() * 3; m_triangleVertices = new ModelOpenGLVertex[m_triangleVertexCount]; @@ -83,10 +85,6 @@ ModelMesh::ModelMesh(const std::vector &vertices, const std::ve const dust3d::Vector3 *srcVert = &vertices[vertexIndex]; const dust3d::Vector3 *srcNormal = &(triangleVertexNormals)[i][j]; ModelOpenGLVertex *dest = &m_triangleVertices[destIndex]; - dest->colorR = color.r(); - dest->colorG = color.g(); - dest->colorB = color.b(); - dest->alpha = color.alpha(); dest->posX = srcVert->x(); dest->posY = srcVert->y(); dest->posZ = srcVert->z(); @@ -95,8 +93,22 @@ ModelMesh::ModelMesh(const std::vector &vertices, const std::ve dest->normX = srcNormal->x(); dest->normY = srcNormal->y(); dest->normZ = srcNormal->z(); - dest->metalness = metalness; - dest->roughness = roughness; + if (nullptr == vertexProperties) { + dest->colorR = color.r(); + dest->colorG = color.g(); + dest->colorB = color.b(); + dest->alpha = color.alpha(); + dest->metalness = metalness; + dest->roughness = roughness; + } else { + const auto &property = (*vertexProperties)[vertexIndex]; + dest->colorR = std::get<0>(property).r(); + dest->colorG = std::get<0>(property).g(); + dest->colorB = std::get<0>(property).b(); + dest->alpha = std::get<0>(property).alpha(); + dest->metalness = std::get<1>(property); + dest->roughness = std::get<2>(property); + } dest->tangentX = 0; dest->tangentY = 0; dest->tangentZ = 0; diff --git a/application/sources/model_mesh.h b/application/sources/model_mesh.h index 9f60d494..044b9c82 100644 --- a/application/sources/model_mesh.h +++ b/application/sources/model_mesh.h @@ -13,11 +13,13 @@ class ModelMesh { public: - ModelMesh(const std::vector &vertices, const std::vector> &triangles, + ModelMesh(const std::vector &vertices, + const std::vector> &triangles, const std::vector> &triangleVertexNormals, const dust3d::Color &color=dust3d::Color::createWhite(), float metalness=0.0, - float roughness=0.0); + float roughness=0.0, + const std::vector> *vertexProperties=nullptr); ModelMesh(dust3d::Object &object); ModelMesh(ModelOpenGLVertex *triangleVertices, int vertexNum); ModelMesh(const ModelMesh &mesh); diff --git a/dust3d/base/math.h b/dust3d/base/math.h index f7c62604..d95a7cf5 100644 --- a/dust3d/base/math.h +++ b/dust3d/base/math.h @@ -31,7 +31,7 @@ namespace dust3d namespace Math { -const double Pi = 3.14159265358979323846; +constexpr double Pi = 3.14159265358979323846; inline bool isZero(double number) { diff --git a/dust3d/base/vector3.cc b/dust3d/base/vector3.cc index 05b1f0be..62a1caa7 100644 --- a/dust3d/base/vector3.cc +++ b/dust3d/base/vector3.cc @@ -25,7 +25,7 @@ namespace dust3d { -Vector3 Vector3::rotated(const Vector3 &unitAxis, double angle) +Vector3 Vector3::rotated(const Vector3 &unitAxis, double angle) const { double c = std::cos(angle); double s = std::sin(angle); diff --git a/dust3d/base/vector3.h b/dust3d/base/vector3.h index 3a47a6f6..ae4971ab 100644 --- a/dust3d/base/vector3.h +++ b/dust3d/base/vector3.h @@ -334,7 +334,7 @@ public: return std::acos(dotProduct(first.normalized(), second.normalized())); } - Vector3 rotated(const Vector3 &unitAxis, double angle); + Vector3 rotated(const Vector3 &unitAxis, double angle) const; private: double m_data[3] = {0.0}; diff --git a/dust3d/mesh/mesh_generator.cc b/dust3d/mesh/mesh_generator.cc index ce102eb3..c354d815 100644 --- a/dust3d/mesh/mesh_generator.cc +++ b/dust3d/mesh/mesh_generator.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -411,11 +412,16 @@ void MeshGenerator::flattenLinks(const std::unordered_map &links } std::unique_ptr MeshGenerator::combineStitchingMesh(const std::vector &partIdStrings, + const std::vector &componentIdStrings, GeneratedComponent &componentCache) { std::vector splines; splines.reserve(partIdStrings.size()); - for (const auto &partIdString: partIdStrings) { + std::vector componentIds(componentIdStrings.size()); + for (size_t i = 0; i < componentIdStrings.size(); ++i) + componentIds[i] = componentIdStrings[i]; + for (size_t partIndex = 0; partIndex < partIdStrings.size(); ++partIndex) { + const auto &partIdString = partIdStrings[partIndex]; std::vector builderNodes; std::map builderNodeIdStringToIndexMap; for (const auto &nodeIdString: m_partNodeIds[partIdString]) { @@ -473,15 +479,14 @@ std::unique_ptr MeshGenerator::combineStitchingMesh(const st splines.emplace_back(StitchMeshBuilder::Spline { std::move(orderedBuilderNodes), isCircle, - isClosing + isClosing, + componentIds[partIndex] }); } auto stitchMeshBuilder = std::make_unique(std::move(splines)); stitchMeshBuilder->build(); - // stitchMeshBuilder->splines(); - collectSharedQuadEdges(stitchMeshBuilder->generatedVertices(), stitchMeshBuilder->generatedFaces(), &componentCache.sharedQuadEdges); @@ -491,6 +496,51 @@ std::unique_ptr MeshGenerator::combineStitchingMesh(const st if (mesh && mesh->isNull()) mesh.reset(); + // Generate preview for each stitching line + for (const auto &spline: stitchMeshBuilder->splines()) { + RopeMesh::BuildParameters buildParameters; + RopeMesh ropeMesh(buildParameters); + std::vector positions(spline.nodes.size()); + for (size_t i = 0; i < spline.nodes.size(); ++i) + positions[i] = spline.nodes[i].origin; + ropeMesh.addRope(positions, spline.isCircle); + + ComponentPreview stitchingLinePreview; + if (mesh) + mesh->fetch(stitchingLinePreview.vertices, stitchingLinePreview.triangles); + size_t startIndex = stitchingLinePreview.vertices.size(); + + stitchingLinePreview.color = Color(1.0, 1.0, 1.0, 0.2); + for (const auto &ropeVertex: ropeMesh.resultVertices()) { + stitchingLinePreview.vertices.emplace_back(ropeVertex); + } + stitchingLinePreview.vertexProperties.resize(stitchingLinePreview.vertices.size()); + auto modelProperty = std::tuple { + stitchingLinePreview.color, + stitchingLinePreview.metalness, + stitchingLinePreview.roughness + }; + auto lineProperty = std::tuple { + Color(1.0, 1.0, 1.0, 1.0), + stitchingLinePreview.metalness, + stitchingLinePreview.roughness + }; + for (size_t i = 0; i < startIndex; ++i) { + stitchingLinePreview.vertexProperties[i] = modelProperty; + } + for (size_t i = startIndex; i < stitchingLinePreview.vertexProperties.size(); ++i) { + stitchingLinePreview.vertexProperties[i] = lineProperty; + } + for (const auto &ropeTriangles: ropeMesh.resultTriangles()) { + stitchingLinePreview.triangles.emplace_back(std::vector { + startIndex + ropeTriangles[0], + startIndex + ropeTriangles[1], + startIndex + ropeTriangles[2] + }); + } + addComponentPreview(spline.sourceId, ComponentPreview(stitchingLinePreview)); + } + return mesh; } @@ -996,17 +1046,8 @@ std::unique_ptr MeshGenerator::combineComponentMesh(const st groupMeshes.emplace_back(std::make_tuple(std::move(childMesh), group.first, String::join(group.second, "|"))); } if (!stitchingParts.empty()) { - auto stitchingMesh = combineStitchingMesh(stitchingParts, componentCache); + auto stitchingMesh = combineStitchingMesh(stitchingParts, stitchingComponents, componentCache); if (stitchingMesh && !stitchingMesh->isNull()) { - - // Generate preview for each stitching line - ComponentPreview stitchingLinePreview; - if (stitchingMesh) - stitchingMesh->fetch(stitchingLinePreview.vertices, stitchingLinePreview.triangles); - stitchingLinePreview.color = Color(1.0, 1.0, 1.0, 0.2); - for (const auto &it: stitchingComponents) - addComponentPreview(it, ComponentPreview(stitchingLinePreview)); - groupMeshes.emplace_back(std::make_tuple(std::move(stitchingMesh), CombineMode::Normal, String::join(stitchingComponents, ":"))); } } diff --git a/dust3d/mesh/mesh_generator.h b/dust3d/mesh/mesh_generator.h index 617939ca..54d9ee1a 100644 --- a/dust3d/mesh/mesh_generator.h +++ b/dust3d/mesh/mesh_generator.h @@ -82,6 +82,7 @@ public: Color color = Color(1.0, 1.0, 1.0); float metalness = 0.0; float roughness = 1.0; + std::vector> vertexProperties; }; MeshGenerator(Snapshot *snapshot); @@ -144,6 +145,7 @@ private: GeneratedComponent &componentCache); std::unique_ptr combineMultipleMeshes(std::vector, CombineMode, std::string>> &&multipleMeshes, bool recombine=true); std::unique_ptr combineStitchingMesh(const std::vector &partIdStrings, + const std::vector &componentIdStrings, GeneratedComponent &componentCache); std::string componentColorName(const std::map *component); void collectUncombinedComponent(const std::string &componentIdString); diff --git a/dust3d/mesh/rope_mesh.cc b/dust3d/mesh/rope_mesh.cc new file mode 100644 index 00000000..7d2a20ab --- /dev/null +++ b/dust3d/mesh/rope_mesh.cc @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2016-2022 Jeremy HU . All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +namespace dust3d +{ + +RopeMesh::RopeMesh(const BuildParameters ¶meters): + m_buildParameters(parameters) +{ +} + +const std::vector &RopeMesh::resultVertices() +{ + return m_resultVertices; +} + +const std::vector> &RopeMesh::resultTriangles() +{ + return m_resultTriangles; +} + +std::pair RopeMesh::findNearestAxis(const Vector3 &direction) +{ + float maxDot = -1; + size_t nearAxisIndex = 0; + int positive = 1; + for (size_t i = 0; i < 3; ++i) { + const auto axis = axisDirection(i); + auto dot = Vector3::dotProduct(axis, direction); + auto positiveDot = std::abs(dot); + if (positiveDot >= maxDot) { + if (dot < 0) + positive = -1; + maxDot = positiveDot; + nearAxisIndex = i; + } + } + return {nearAxisIndex, positive}; +} + +std::vector RopeMesh::calculateCircleVertices(double radius, + size_t points, + const Vector3 &aroundAxis, + const Vector3 &startDirection, + const Vector3 &origin) +{ + constexpr auto roundAngle = 2.0 * Math::Pi; + auto stepAngle = roundAngle / points; + std::vector circlePoints; + circlePoints.reserve(points); + for (double angle = stepAngle * -0.5; + circlePoints.size() < points; + angle += stepAngle) { + circlePoints.push_back(origin + startDirection.rotated(aroundAxis, angle) * radius); + } + return circlePoints; +} + +Vector3 RopeMesh::calculateCircleBaseNormal(const std::vector &vertices) +{ + std::vector edgeDirections(vertices.size()); + for (size_t i = 0; i < edgeDirections.size(); ++i) { + size_t j = (i + 1) % edgeDirections.size(); + edgeDirections[i] = (vertices[j] - vertices[i]).normalized(); + } + Vector3 baseNormal; + for (size_t i = 0; i < edgeDirections.size(); ++i) { + size_t j = (i + 1) % edgeDirections.size(); + baseNormal += Vector3::crossProduct(-edgeDirections[i], edgeDirections[j]); + } + return baseNormal.normalized(); +} + +Vector3 RopeMesh::calculateTubeBaseNormal(const std::vector &vertices) +{ + std::vector edgeDirections(vertices.size()); + for (size_t i = 1; i < edgeDirections.size(); ++i) { + size_t h = i - 1; + edgeDirections[h] = (vertices[i] - vertices[h]).normalized(); + } + Vector3 baseNormal; + for (size_t i = 1; i < edgeDirections.size(); ++i) { + size_t h = i - 1; + // >15 degrees && < 165 degrees + if (std::abs(Vector3::dotProduct(edgeDirections[h], edgeDirections[i])) < 0.966) + baseNormal += Vector3::crossProduct(edgeDirections[h], edgeDirections[i]); + } + if (baseNormal.isZero()) { + for (size_t h = 0; h + 1 < edgeDirections.size(); ++h) { + const auto §ionNormal = edgeDirections[h]; + auto axis = RopeMesh::findNearestAxis(sectionNormal); + baseNormal += axis.second * + Vector3::crossProduct(sectionNormal, RopeMesh::nextAxisDirection(axis.first)).normalized(); + } + } + return baseNormal.normalized(); +} + +void RopeMesh::addRope(const std::vector &positions, bool isCircle) +{ + Vector3 baseNormal = isCircle ? calculateCircleBaseNormal(positions) : calculateTubeBaseNormal(positions); + std::vector> circles; + circles.reserve(positions.size()); + for (size_t j = isCircle ? 0 : 1; j < positions.size(); ++j) { + size_t i = (j + positions.size() - 1) % positions.size(); + auto circlePositions = calculateCircleVertices(m_buildParameters.defaultRadius, + m_buildParameters.sectionSegments, + (positions[j] - positions[i]).normalized(), + baseNormal, + positions[i]); + std::vector indices(circlePositions.size()); + for (size_t k = 0; k < indices.size(); ++k) { + indices[k] = m_resultVertices.size(); + m_resultVertices.push_back(circlePositions[k]); + } + circles.emplace_back(indices); + } + for (size_t j = isCircle ? 0 : 1; j < circles.size(); ++j) { + size_t i = (j + circles.size() - 1) % circles.size(); + const auto &circlesI = circles[i]; + const auto &circlesJ = circles[j]; + for (size_t m = 0; m < circlesI.size(); ++m) { + size_t n = (m + 1) % circlesI.size(); + m_resultTriangles.emplace_back(std::vector { + circlesI[m], circlesI[n], circlesJ[n] + }); + m_resultTriangles.emplace_back(std::vector { + circlesJ[n], circlesJ[m], circlesI[m] + }); + } + } +} + +}; diff --git a/dust3d/mesh/rope_mesh.h b/dust3d/mesh/rope_mesh.h new file mode 100644 index 00000000..c2ed7ef3 --- /dev/null +++ b/dust3d/mesh/rope_mesh.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016-2022 Jeremy HU . All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef DUST3D_MESH_ROPE_MESH_H_ +#define DUST3D_MESH_ROPE_MESH_H_ + +#include +#include + +namespace dust3d +{ + +class RopeMesh +{ +public: + struct BuildParameters + { + double defaultRadius = 0.01; + size_t sectionSegments = 5; + }; + + RopeMesh(const BuildParameters ¶meters); + void addRope(const std::vector &positions, bool isCircle); + const std::vector &resultVertices(); + const std::vector> &resultTriangles(); +private: + std::vector m_resultVertices; + std::vector> m_resultTriangles; + BuildParameters m_buildParameters; + static std::pair findNearestAxis(const Vector3 &direction); + static inline const Vector3 &axisDirection(size_t index) + { + static const std::vector axisList = { + Vector3 {1, 0, 0}, + Vector3 {0, 1, 0}, + Vector3 {0, 0, 1}, + }; + return axisList[index]; + } + static inline const Vector3 &nextAxisDirection(size_t index) + { + return axisDirection((index + 1) % 3); + } + static std::vector calculateCircleVertices(double radius, + size_t points, + const Vector3 &aroundAxis=Vector3(0.0, 0.0, 1.0), + const Vector3 &startDirection=Vector3(0.0, 1.0, 0.0), + const Vector3 &origin=Vector3(0.0, 0.0, 0.0)); + static Vector3 calculateCircleBaseNormal(const std::vector &vertices); + static Vector3 calculateTubeBaseNormal(const std::vector &vertices); +}; + +}; + +#endif diff --git a/dust3d/mesh/stitch_mesh_builder.h b/dust3d/mesh/stitch_mesh_builder.h index 58eaa723..a51133eb 100644 --- a/dust3d/mesh/stitch_mesh_builder.h +++ b/dust3d/mesh/stitch_mesh_builder.h @@ -24,6 +24,7 @@ #define DUST3D_MESH_STITCH_MESH_BUILDER_H_ #include +#include namespace dust3d { @@ -42,6 +43,7 @@ public: std::vector nodes; bool isCircle = false; bool isClosing = false; + Uuid sourceId; }; StitchMeshBuilder(std::vector &&splines);