Implement rope mesh builder

master
Jeremy HU 2022-10-08 07:26:30 +11:00
parent 78893729b4
commit 5b06f64fdd
12 changed files with 321 additions and 28 deletions

View File

@ -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

View File

@ -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)

View File

@ -68,11 +68,13 @@ ModelMesh::ModelMesh(ModelOpenGLVertex *triangleVertices, int vertexNum) :
{
}
ModelMesh::ModelMesh(const std::vector<dust3d::Vector3> &vertices, const std::vector<std::vector<size_t>> &triangles,
ModelMesh::ModelMesh(const std::vector<dust3d::Vector3> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<std::vector<dust3d::Vector3>> &triangleVertexNormals,
const dust3d::Color &color,
float metalness,
float roughness)
float roughness,
const std::vector<std::tuple<dust3d::Color, float/*metalness*/, float/*roughness*/>> *vertexProperties)
{
m_triangleVertexCount = (int)triangles.size() * 3;
m_triangleVertices = new ModelOpenGLVertex[m_triangleVertexCount];
@ -83,10 +85,6 @@ ModelMesh::ModelMesh(const std::vector<dust3d::Vector3> &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<dust3d::Vector3> &vertices, const std::ve
dest->normX = srcNormal->x();
dest->normY = srcNormal->y();
dest->normZ = srcNormal->z();
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;

View File

@ -13,11 +13,13 @@
class ModelMesh
{
public:
ModelMesh(const std::vector<dust3d::Vector3> &vertices, const std::vector<std::vector<size_t>> &triangles,
ModelMesh(const std::vector<dust3d::Vector3> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<std::vector<dust3d::Vector3>> &triangleVertexNormals,
const dust3d::Color &color=dust3d::Color::createWhite(),
float metalness=0.0,
float roughness=0.0);
float roughness=0.0,
const std::vector<std::tuple<dust3d::Color, float/*metalness*/, float/*roughness*/>> *vertexProperties=nullptr);
ModelMesh(dust3d::Object &object);
ModelMesh(ModelOpenGLVertex *triangleVertices, int vertexNum);
ModelMesh(const ModelMesh &mesh);

View File

@ -31,7 +31,7 @@ namespace dust3d
namespace Math
{
const double Pi = 3.14159265358979323846;
constexpr double Pi = 3.14159265358979323846;
inline bool isZero(double number)
{

View File

@ -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);

View File

@ -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};

View File

@ -26,6 +26,7 @@
#include <dust3d/base/part_base.h>
#include <dust3d/base/snapshot_xml.h>
#include <dust3d/base/cut_face.h>
#include <dust3d/mesh/rope_mesh.h>
#include <dust3d/mesh/stitch_mesh_builder.h>
#include <dust3d/mesh/stroke_mesh_builder.h>
#include <dust3d/mesh/stroke_modifier.h>
@ -411,11 +412,16 @@ void MeshGenerator::flattenLinks(const std::unordered_map<size_t, size_t> &links
}
std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineStitchingMesh(const std::vector<std::string> &partIdStrings,
const std::vector<std::string> &componentIdStrings,
GeneratedComponent &componentCache)
{
std::vector<StitchMeshBuilder::Spline> splines;
splines.reserve(partIdStrings.size());
for (const auto &partIdString: partIdStrings) {
std::vector<Uuid> 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<StitchMeshBuilder::Node> builderNodes;
std::map<std::string, size_t> builderNodeIdStringToIndexMap;
for (const auto &nodeIdString: m_partNodeIds[partIdString]) {
@ -473,15 +479,14 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combineStitchingMesh(const st
splines.emplace_back(StitchMeshBuilder::Spline {
std::move(orderedBuilderNodes),
isCircle,
isClosing
isClosing,
componentIds[partIndex]
});
}
auto stitchMeshBuilder = std::make_unique<StitchMeshBuilder>(std::move(splines));
stitchMeshBuilder->build();
// stitchMeshBuilder->splines();
collectSharedQuadEdges(stitchMeshBuilder->generatedVertices(),
stitchMeshBuilder->generatedFaces(),
&componentCache.sharedQuadEdges);
@ -491,6 +496,51 @@ std::unique_ptr<MeshCombiner::Mesh> 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<Vector3> 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<dust3d::Color, float/*metalness*/, float/*roughness*/> {
stitchingLinePreview.color,
stitchingLinePreview.metalness,
stitchingLinePreview.roughness
};
auto lineProperty = std::tuple<dust3d::Color, float/*metalness*/, float/*roughness*/> {
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<size_t> {
startIndex + ropeTriangles[0],
startIndex + ropeTriangles[1],
startIndex + ropeTriangles[2]
});
}
addComponentPreview(spline.sourceId, ComponentPreview(stitchingLinePreview));
}
return mesh;
}
@ -996,17 +1046,8 @@ std::unique_ptr<MeshCombiner::Mesh> 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, ":")));
}
}

View File

@ -82,6 +82,7 @@ public:
Color color = Color(1.0, 1.0, 1.0);
float metalness = 0.0;
float roughness = 1.0;
std::vector<std::tuple<dust3d::Color, float/*metalness*/, float/*roughness*/>> vertexProperties;
};
MeshGenerator(Snapshot *snapshot);
@ -144,6 +145,7 @@ private:
GeneratedComponent &componentCache);
std::unique_ptr<MeshCombiner::Mesh> combineMultipleMeshes(std::vector<std::tuple<std::unique_ptr<MeshCombiner::Mesh>, CombineMode, std::string>> &&multipleMeshes, bool recombine=true);
std::unique_ptr<MeshCombiner::Mesh> combineStitchingMesh(const std::vector<std::string> &partIdStrings,
const std::vector<std::string> &componentIdStrings,
GeneratedComponent &componentCache);
std::string componentColorName(const std::map<std::string, std::string> *component);
void collectUncombinedComponent(const std::string &componentIdString);

157
dust3d/mesh/rope_mesh.cc Normal file
View File

@ -0,0 +1,157 @@
/*
* Copyright (c) 2016-2022 Jeremy HU <jeremy-at-dust3d dot org>. 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 <dust3d/base/debug.h>
#include <dust3d/base/math.h>
#include <dust3d/mesh/rope_mesh.h>
namespace dust3d
{
RopeMesh::RopeMesh(const BuildParameters &parameters):
m_buildParameters(parameters)
{
}
const std::vector<Vector3> &RopeMesh::resultVertices()
{
return m_resultVertices;
}
const std::vector<std::vector<size_t>> &RopeMesh::resultTriangles()
{
return m_resultTriangles;
}
std::pair<size_t, int> 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<Vector3> 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<Vector3> 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<Vector3> &vertices)
{
std::vector<Vector3> 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<Vector3> &vertices)
{
std::vector<Vector3> 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 &sectionNormal = 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<Vector3> &positions, bool isCircle)
{
Vector3 baseNormal = isCircle ? calculateCircleBaseNormal(positions) : calculateTubeBaseNormal(positions);
std::vector<std::vector<size_t>> 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<size_t> 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<size_t> {
circlesI[m], circlesI[n], circlesJ[n]
});
m_resultTriangles.emplace_back(std::vector<size_t> {
circlesJ[n], circlesJ[m], circlesI[m]
});
}
}
}
};

74
dust3d/mesh/rope_mesh.h Normal file
View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2016-2022 Jeremy HU <jeremy-at-dust3d dot org>. 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 <vector>
#include <dust3d/base/vector3.h>
namespace dust3d
{
class RopeMesh
{
public:
struct BuildParameters
{
double defaultRadius = 0.01;
size_t sectionSegments = 5;
};
RopeMesh(const BuildParameters &parameters);
void addRope(const std::vector<Vector3> &positions, bool isCircle);
const std::vector<Vector3> &resultVertices();
const std::vector<std::vector<size_t>> &resultTriangles();
private:
std::vector<Vector3> m_resultVertices;
std::vector<std::vector<size_t>> m_resultTriangles;
BuildParameters m_buildParameters;
static std::pair<size_t, int> findNearestAxis(const Vector3 &direction);
static inline const Vector3 &axisDirection(size_t index)
{
static const std::vector<Vector3> 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<Vector3> 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<Vector3> &vertices);
static Vector3 calculateTubeBaseNormal(const std::vector<Vector3> &vertices);
};
};
#endif

View File

@ -24,6 +24,7 @@
#define DUST3D_MESH_STITCH_MESH_BUILDER_H_
#include <dust3d/base/vector3.h>
#include <dust3d/base/uuid.h>
namespace dust3d
{
@ -42,6 +43,7 @@ public:
std::vector<Node> nodes;
bool isCircle = false;
bool isClosing = false;
Uuid sourceId;
};
StitchMeshBuilder(std::vector<Spline> &&splines);