Remove stroke mesh builder

master
Jeremy HU 2022-10-19 20:22:17 +11:00
parent 439ea25a86
commit c614da251a
7 changed files with 0 additions and 1677 deletions

View File

@ -300,10 +300,6 @@ HEADERS += ../dust3d/mesh/smooth_normal.h
SOURCES += ../dust3d/mesh/smooth_normal.cc SOURCES += ../dust3d/mesh/smooth_normal.cc
HEADERS += ../dust3d/mesh/stitch_mesh_builder.h HEADERS += ../dust3d/mesh/stitch_mesh_builder.h
SOURCES += ../dust3d/mesh/stitch_mesh_builder.cc SOURCES += ../dust3d/mesh/stitch_mesh_builder.cc
HEADERS += ../dust3d/mesh/stroke_mesh_builder.h
SOURCES += ../dust3d/mesh/stroke_mesh_builder.cc
HEADERS += ../dust3d/mesh/stroke_modifier.h
SOURCES += ../dust3d/mesh/stroke_modifier.cc
HEADERS += ../dust3d/mesh/triangulate.h HEADERS += ../dust3d/mesh/triangulate.h
SOURCES += ../dust3d/mesh/triangulate.cc SOURCES += ../dust3d/mesh/triangulate.cc
HEADERS += ../dust3d/mesh/trim_vertices.h HEADERS += ../dust3d/mesh/trim_vertices.h

View File

@ -33,8 +33,6 @@
#include <dust3d/mesh/section_preview_mesh_builder.h> #include <dust3d/mesh/section_preview_mesh_builder.h>
#include <dust3d/mesh/smooth_normal.h> #include <dust3d/mesh/smooth_normal.h>
#include <dust3d/mesh/stitch_mesh_builder.h> #include <dust3d/mesh/stitch_mesh_builder.h>
#include <dust3d/mesh/stroke_mesh_builder.h>
#include <dust3d/mesh/stroke_modifier.h>
#include <dust3d/mesh/triangulate.h> #include <dust3d/mesh/triangulate.h>
#include <dust3d/mesh/trim_vertices.h> #include <dust3d/mesh/trim_vertices.h>
#include <dust3d/mesh/tube_mesh_builder.h> #include <dust3d/mesh/tube_mesh_builder.h>
@ -737,347 +735,6 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combinePartMesh(const std::st
return mesh; return mesh;
} }
/*
std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combinePartMesh(const std::string &partIdString, bool *hasError, bool *retryable, bool addIntermediateNodes)
{
auto findPart = m_snapshot->parts.find(partIdString);
if (findPart == m_snapshot->parts.end()) {
return nullptr;
}
Uuid partId = Uuid(partIdString);
auto &part = findPart->second;
*retryable = true;
bool isDisabled = String::isTrue(String::valueOrEmpty(part, "disabled"));
std::string __mirroredByPartId = String::valueOrEmpty(part, "__mirroredByPartId");
std::string __mirrorFromPartId = String::valueOrEmpty(part, "__mirrorFromPartId");
bool subdived = String::isTrue(String::valueOrEmpty(part, "subdived"));
bool rounded = String::isTrue(String::valueOrEmpty(part, "rounded"));
bool chamfered = String::isTrue(String::valueOrEmpty(part, "chamfered"));
bool countershaded = String::isTrue(String::valueOrEmpty(part, "countershaded"));
bool smooth = String::isTrue(String::valueOrEmpty(part, "smooth"));
std::string colorString = String::valueOrEmpty(part, "color");
Color partColor = colorString.empty() ? m_defaultPartColor : Color(colorString);
float deformThickness = 1.0;
float deformWidth = 1.0;
float cutRotation = 0.0;
float hollowThickness = 0.0;
auto target = PartTargetFromString(String::valueOrEmpty(part, "target").c_str());
auto base = PartBaseFromString(String::valueOrEmpty(part, "base").c_str());
std::string searchPartIdString = __mirrorFromPartId.empty() ? partIdString : __mirrorFromPartId;
std::string cutFaceString = String::valueOrEmpty(part, "cutFace");
std::vector<Vector2> cutTemplate;
cutFaceStringToCutTemplate(cutFaceString, cutTemplate);
if (chamfered)
chamferFace(&cutTemplate);
std::string cutRotationString = String::valueOrEmpty(part, "cutRotation");
if (!cutRotationString.empty()) {
cutRotation = String::toFloat(cutRotationString);
}
std::string hollowThicknessString = String::valueOrEmpty(part, "hollowThickness");
if (!hollowThicknessString.empty()) {
hollowThickness = String::toFloat(hollowThicknessString);
}
std::string thicknessString = String::valueOrEmpty(part, "deformThickness");
if (!thicknessString.empty()) {
deformThickness = String::toFloat(thicknessString);
}
std::string widthString = String::valueOrEmpty(part, "deformWidth");
if (!widthString.empty()) {
deformWidth = String::toFloat(widthString);
}
bool deformUnified = String::isTrue(String::valueOrEmpty(part, "deformUnified"));
Uuid materialId;
std::string materialIdString = String::valueOrEmpty(part, "materialId");
if (!materialIdString.empty())
materialId = Uuid(materialIdString);
float colorSolubility = 0;
std::string colorSolubilityString = String::valueOrEmpty(part, "colorSolubility");
if (!colorSolubilityString.empty())
colorSolubility = String::toFloat(colorSolubilityString);
float metalness = 0;
std::string metalnessString = String::valueOrEmpty(part, "metallic");
if (!metalnessString.empty())
metalness = String::toFloat(metalnessString);
float roughness = 1.0;
std::string roughnessString = String::valueOrEmpty(part, "roughness");
if (!roughnessString.empty())
roughness = String::toFloat(roughnessString);
Uuid fillMeshFileId;
std::string fillMeshString = String::valueOrEmpty(part, "fillMesh");
if (!fillMeshString.empty()) {
fillMeshFileId = Uuid(fillMeshString);
if (!fillMeshFileId.isNull()) {
*retryable = false;
}
}
auto &partCache = m_cacheContext->parts[partIdString];
partCache.objectNodes.clear();
partCache.objectEdges.clear();
partCache.objectNodeVertices.clear();
partCache.vertices.clear();
partCache.faces.clear();
partCache.color = partColor;
partCache.metalness = metalness;
partCache.roughness = roughness;
partCache.isSuccessful = false;
partCache.joined = (target == PartTarget::Model && !isDisabled);
struct NodeInfo
{
float radius = 0;
Vector3 position;
bool hasCutFaceSettings = false;
float cutRotation = 0.0;
std::string cutFace;
};
std::map<std::string, NodeInfo> nodeInfos;
for (const auto &nodeIdString: m_partNodeIds[searchPartIdString]) {
auto findNode = m_snapshot->nodes.find(nodeIdString);
if (findNode == m_snapshot->nodes.end()) {
continue;
}
auto &node = findNode->second;
float radius = String::toFloat(String::valueOrEmpty(node, "radius"));
float x = (String::toFloat(String::valueOrEmpty(node, "x")) - m_mainProfileMiddleX);
float y = (m_mainProfileMiddleY - String::toFloat(String::valueOrEmpty(node, "y")));
float z = (m_sideProfileMiddleX - String::toFloat(String::valueOrEmpty(node, "z")));
bool hasCutFaceSettings = false;
float cutRotation = 0.0;
std::string cutFace;
const auto &cutFaceIt = node.find("cutFace");
if (cutFaceIt != node.end()) {
cutFace = cutFaceIt->second;
hasCutFaceSettings = true;
const auto &cutRotationIt = node.find("cutRotation");
if (cutRotationIt != node.end()) {
cutRotation = String::toFloat(cutRotationIt->second);
}
}
auto &nodeInfo = nodeInfos[nodeIdString];
nodeInfo.position = Vector3(x, y, z);
nodeInfo.radius = radius;
nodeInfo.hasCutFaceSettings = hasCutFaceSettings;
nodeInfo.cutRotation = cutRotation;
nodeInfo.cutFace = cutFace;
}
std::set<std::pair<std::string, std::string>> edges;
for (const auto &edgeIdString: m_partEdgeIds[searchPartIdString]) {
auto findEdge = m_snapshot->edges.find(edgeIdString);
if (findEdge == m_snapshot->edges.end()) {
continue;
}
auto &edge = findEdge->second;
std::string fromNodeIdString = String::valueOrEmpty(edge, "from");
std::string toNodeIdString = String::valueOrEmpty(edge, "to");
const auto &findFromNodeInfo = nodeInfos.find(fromNodeIdString);
if (findFromNodeInfo == nodeInfos.end()) {
continue;
}
const auto &findToNodeInfo = nodeInfos.find(toNodeIdString);
if (findToNodeInfo == nodeInfos.end()) {
continue;
}
edges.insert({fromNodeIdString, toNodeIdString});
}
bool buildSucceed = false;
std::map<std::string, int> nodeIdStringToIndexMap;
std::map<int, std::string> nodeIndexToIdStringMap;
auto addNodeToPartCache = [&](const std::string &nodeIdString, const NodeInfo &nodeInfo) {
ObjectNode objectNode;
objectNode.partId = Uuid(partIdString);
objectNode.nodeId = Uuid(nodeIdString);
objectNode.origin = nodeInfo.position;
objectNode.radius = nodeInfo.radius;
objectNode.color = partColor;
objectNode.materialId = materialId;
objectNode.countershaded = countershaded;
objectNode.colorSolubility = colorSolubility;
objectNode.metalness = metalness;
objectNode.roughness = roughness;
if (!__mirroredByPartId.empty())
objectNode.mirroredByPartId = Uuid(__mirroredByPartId);
if (!__mirrorFromPartId.empty()) {
objectNode.mirrorFromPartId = Uuid(__mirrorFromPartId);
objectNode.origin.setX(-nodeInfo.position.x());
}
objectNode.joined = partCache.joined;
partCache.objectNodes.push_back(objectNode);
};
auto addEdgeToPartCache = [&](const std::string &firstNodeIdString, const std::string &secondNodeIdString) {
partCache.objectEdges.push_back({
{Uuid(partIdString), Uuid(firstNodeIdString)},
{Uuid(partIdString), Uuid(secondNodeIdString)}
});
};
auto strokeModifier = std::make_unique<StrokeModifier>();
if (smooth)
strokeModifier->enableSmooth();
if (addIntermediateNodes)
strokeModifier->enableIntermediateAddition();
for (const auto &nodeIt: nodeInfos) {
const auto &nodeIdString = nodeIt.first;
const auto &nodeInfo = nodeIt.second;
size_t nodeIndex = 0;
if (nodeInfo.hasCutFaceSettings) {
std::vector<Vector2> nodeCutTemplate;
cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate);
if (chamfered)
chamferFace(&nodeCutTemplate);
nodeIndex = strokeModifier->addNode(nodeInfo.position, nodeInfo.radius, nodeCutTemplate, nodeInfo.cutRotation);
} else {
nodeIndex = strokeModifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate, cutRotation);
}
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
}
for (const auto &edgeIt: edges) {
const std::string &fromNodeIdString = edgeIt.first;
const std::string &toNodeIdString = edgeIt.second;
auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString);
if (findFromNodeIndex == nodeIdStringToIndexMap.end()) {
continue;
}
auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString);
if (findToNodeIndex == nodeIdStringToIndexMap.end()) {
continue;
}
strokeModifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
}
if (subdived)
strokeModifier->subdivide();
if (rounded)
strokeModifier->roundEnd();
strokeModifier->finalize();
std::vector<size_t> sourceNodeIndices;
auto strokeMeshBuilder = std::make_unique<StrokeMeshBuilder>();
strokeMeshBuilder->setDeformThickness(deformThickness);
strokeMeshBuilder->setDeformWidth(deformWidth);
strokeMeshBuilder->setDeformUnified(deformUnified);
strokeMeshBuilder->setHollowThickness(hollowThickness);
if (PartBase::YZ == base) {
strokeMeshBuilder->enableBaseNormalOnX(false);
} else if (PartBase::Average == base) {
strokeMeshBuilder->enableBaseNormalAverage(true);
} else if (PartBase::XY == base) {
strokeMeshBuilder->enableBaseNormalOnZ(false);
} else if (PartBase::ZX == base) {
strokeMeshBuilder->enableBaseNormalOnY(false);
}
for (const auto &node: strokeModifier->nodes()) {
auto nodeIndex = strokeMeshBuilder->addNode(node.position, node.radius, node.cutTemplate, node.cutRotation);
strokeMeshBuilder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex);
}
for (const auto &edge: strokeModifier->edges())
strokeMeshBuilder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
for (const auto &nodeIt: nodeInfos) {
const auto &nodeIdString = nodeIt.first;
const auto &nodeInfo = nodeIt.second;
addNodeToPartCache(nodeIdString, nodeInfo);
}
for (const auto &edgeIt: edges) {
const std::string &fromNodeIdString = edgeIt.first;
const std::string &toNodeIdString = edgeIt.second;
addEdgeToPartCache(fromNodeIdString, toNodeIdString);
}
buildSucceed = strokeMeshBuilder->build();
partCache.vertices = strokeMeshBuilder->generatedVertices();
partCache.faces = strokeMeshBuilder->generatedFaces();
if (!__mirrorFromPartId.empty()) {
for (auto &it: partCache.vertices)
it.setX(-it.x());
for (auto &it: partCache.faces)
std::reverse(it.begin(), it.end());
}
sourceNodeIndices = strokeMeshBuilder->generatedVerticesSourceNodeIndices();
for (size_t i = 0; i < partCache.vertices.size(); ++i) {
const auto &position = partCache.vertices[i];
const auto &source = strokeMeshBuilder->generatedVerticesSourceNodeIndices()[i];
size_t nodeIndex = strokeModifier->nodes()[source].originNodeIndex;
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
partCache.objectNodeVertices.push_back({position, {partIdString, nodeIdString}});
}
bool hasMeshError = false;
std::unique_ptr<MeshCombiner::Mesh> mesh;
if (buildSucceed) {
mesh = std::make_unique<MeshCombiner::Mesh>(partCache.vertices, partCache.faces);
if (mesh->isNull()) {
hasMeshError = true;
}
} else {
hasMeshError = true;
}
if (nullptr != mesh) {
partCache.isSuccessful = true;
}
if (mesh && mesh->isNull()) {
mesh.reset();
}
//if (isDisabled) {
// mesh.reset();
//}
if (target != PartTarget::Model) {
mesh.reset();
}
if (hasMeshError && target == PartTarget::Model) {
*hasError = true;
}
return mesh;
}
*/
const std::map<std::string, std::string>* MeshGenerator::findComponent(const std::string& componentIdString) const std::map<std::string, std::string>* MeshGenerator::findComponent(const std::string& componentIdString)
{ {
const std::map<std::string, std::string>* component = &m_snapshot->rootComponent; const std::map<std::string, std::string>* component = &m_snapshot->rootComponent;

View File

@ -30,7 +30,6 @@
#include <dust3d/base/uuid.h> #include <dust3d/base/uuid.h>
#include <dust3d/mesh/mesh_combiner.h> #include <dust3d/mesh/mesh_combiner.h>
#include <dust3d/mesh/mesh_node.h> #include <dust3d/mesh/mesh_node.h>
#include <dust3d/mesh/stroke_mesh_builder.h>
#include <set> #include <set>
#include <tuple> #include <tuple>
#include <unordered_map> #include <unordered_map>

View File

@ -1,810 +0,0 @@
/*
* Copyright (c) 2016-2021 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 <algorithm>
#include <dust3d/mesh/box_mesh.h>
#include <dust3d/mesh/hole_stitcher.h>
#include <dust3d/mesh/isotropic_remesher.h>
#include <dust3d/mesh/stroke_mesh_builder.h>
#include <dust3d/mesh/triangulate.h>
#include <unordered_set>
namespace dust3d {
size_t StrokeMeshBuilder::Node::nextOrNeighborOtherThan(size_t neighborIndex) const
{
if (this->next != neighborIndex && this->next != this->index)
return this->next;
for (const auto& it : this->neighbors) {
if (it != neighborIndex)
return it;
}
return this->index;
}
void StrokeMeshBuilder::enableBaseNormalOnX(bool enabled)
{
m_baseNormalOnX = enabled;
}
void StrokeMeshBuilder::enableBaseNormalOnY(bool enabled)
{
m_baseNormalOnY = enabled;
}
void StrokeMeshBuilder::enableBaseNormalOnZ(bool enabled)
{
m_baseNormalOnZ = enabled;
}
void StrokeMeshBuilder::enableBaseNormalAverage(bool enabled)
{
m_baseNormalAverageEnabled = enabled;
}
void StrokeMeshBuilder::setDeformThickness(float thickness)
{
m_deformThickness = std::max((float)0.01, thickness);
}
void StrokeMeshBuilder::setDeformWidth(float width)
{
m_deformWidth = std::max((float)0.01, width);
}
void StrokeMeshBuilder::setDeformUnified(bool unified)
{
m_deformUnified = unified;
}
void StrokeMeshBuilder::setHollowThickness(float hollowThickness)
{
m_hollowThickness = hollowThickness;
}
void StrokeMeshBuilder::setNodeOriginInfo(size_t nodeIndex, int nearOriginNodeIndex, int farOriginNodeIndex)
{
auto& node = m_nodes[nodeIndex];
node.nearOriginNodeIndex = nearOriginNodeIndex;
node.farOriginNodeIndex = farOriginNodeIndex;
}
const std::vector<Vector3>& StrokeMeshBuilder::generatedVertices()
{
return m_generatedVertices;
}
const std::vector<std::vector<size_t>>& StrokeMeshBuilder::generatedFaces()
{
return m_generatedFaces;
}
const std::vector<size_t>& StrokeMeshBuilder::generatedVerticesSourceNodeIndices()
{
return m_generatedVerticesSourceNodeIndices;
}
const std::vector<StrokeMeshBuilder::Node>& StrokeMeshBuilder::nodes() const
{
return m_nodes;
}
const std::vector<size_t>& StrokeMeshBuilder::nodeIndices() const
{
return m_nodeIndices;
}
const Vector3& StrokeMeshBuilder::nodeTraverseDirection(size_t nodeIndex) const
{
return m_nodes[nodeIndex].traverseDirection;
}
const Vector3& StrokeMeshBuilder::nodeBaseNormal(size_t nodeIndex) const
{
return m_nodes[nodeIndex].baseNormal;
}
size_t StrokeMeshBuilder::nodeTraverseOrder(size_t nodeIndex) const
{
return m_nodes[nodeIndex].traverseOrder;
}
size_t StrokeMeshBuilder::addNode(const Vector3& position, float radius, const std::vector<Vector2>& cutTemplate, float cutRotation)
{
size_t nodeIndex = m_nodes.size();
Node node;
node.position = position;
node.radius = radius;
node.cutTemplate = cutTemplate;
node.cutRotation = cutRotation;
node.next = nodeIndex;
node.index = nodeIndex;
m_nodes.push_back(node);
return nodeIndex;
}
void StrokeMeshBuilder::addEdge(size_t firstNodeIndex,
size_t secondNodeIndex)
{
auto& fromNode = m_nodes[firstNodeIndex];
fromNode.next = secondNodeIndex;
fromNode.neighbors.push_back(secondNodeIndex);
auto& toNode = m_nodes[secondNodeIndex];
toNode.neighbors.push_back(firstNodeIndex);
}
Vector3 StrokeMeshBuilder::calculateBaseNormalFromTraverseDirection(const Vector3& traverseDirection)
{
const std::vector<Vector3> axisList = {
Vector3 { 1, 0, 0 },
Vector3 { 0, 1, 0 },
Vector3 { 0, 0, 1 },
};
float maxDot = -1;
size_t nearAxisIndex = 0;
bool reversed = false;
for (size_t i = 0; i < axisList.size(); ++i) {
const auto axis = axisList[i];
auto dot = Vector3::dotProduct(axis, traverseDirection);
auto positiveDot = abs(dot);
if (positiveDot >= maxDot) {
reversed = dot < 0;
maxDot = positiveDot;
nearAxisIndex = i;
}
}
// axisList[nearAxisIndex] align with the traverse direction,
// So we pick the next axis to do cross product with traverse direction
const auto& choosenAxis = axisList[(nearAxisIndex + 1) % 3];
auto baseNormal = Vector3::crossProduct(traverseDirection, choosenAxis).normalized();
return reversed ? -baseNormal : baseNormal;
}
std::vector<Vector3> StrokeMeshBuilder::makeCut(const Vector3& cutCenter,
float radius,
const std::vector<Vector2>& cutTemplate,
const Vector3& cutNormal,
const Vector3& baseNormal)
{
std::vector<Vector3> resultCut;
Vector3 u = Vector3::crossProduct(cutNormal, baseNormal).normalized();
Vector3 v = Vector3::crossProduct(u, cutNormal).normalized();
auto uFactor = u * radius;
auto vFactor = v * radius;
for (const auto& t : cutTemplate) {
resultCut.push_back(cutCenter + (uFactor * t.x() + vFactor * t.y()));
}
return resultCut;
}
void StrokeMeshBuilder::insertCutVertices(const std::vector<Vector3>& cut,
std::vector<size_t>* vertices,
size_t nodeIndex,
const Vector3& cutNormal)
{
size_t indexInCut = 0;
for (const auto& position : cut) {
size_t vertexIndex = m_generatedVertices.size();
m_generatedVertices.push_back(position);
m_generatedVerticesSourceNodeIndices.push_back(nodeIndex);
m_generatedVerticesCutDirects.push_back(cutNormal);
GeneratedVertexInfo info;
info.orderInCut = indexInCut;
info.cutSize = cut.size();
m_generatedVerticesInfos.push_back(info);
vertices->push_back(vertexIndex);
++indexInCut;
}
}
std::vector<size_t> StrokeMeshBuilder::edgeloopFlipped(const std::vector<size_t>& edgeLoop)
{
auto reversed = edgeLoop;
std::reverse(reversed.begin(), reversed.end());
std::rotate(reversed.rbegin(), reversed.rbegin() + 1, reversed.rend());
return reversed;
}
void StrokeMeshBuilder::buildMesh()
{
if (1 == m_nodes.size()) {
const Node& node = m_nodes[0];
int subdivideTimes = (int)(node.cutTemplate.size() / 4) - 1;
if (subdivideTimes < 0)
subdivideTimes = 0;
buildBoxMesh(node.position, node.radius, subdivideTimes, m_generatedVertices, m_generatedFaces);
m_generatedVerticesSourceNodeIndices.resize(m_generatedVertices.size(), 0);
m_generatedVerticesCutDirects.resize(m_generatedVertices.size(), node.traverseDirection);
return;
}
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
auto& node = m_nodes[m_nodeIndices[i]];
if (!Math::isZero(node.cutRotation)) {
float degree = node.cutRotation * 180;
node.baseNormal = node.baseNormal.rotated(node.traverseDirection, Math::radiansFromDegrees(degree));
}
auto cutVertices = makeCut(node.position, node.radius, node.cutTemplate,
node.traverseDirection, node.baseNormal);
std::vector<size_t> cut;
insertCutVertices(cutVertices, &cut, node.index, node.traverseDirection);
m_cuts.push_back(cut);
}
}
void StrokeMeshBuilder::interpolateCutEdges()
{
if (m_cuts.empty())
return;
size_t bigCutIndex = 0;
double maxRadius = 0.0;
for (size_t i = 0; i < m_cuts.size(); ++i) {
size_t j = (i + 1) % m_cuts.size();
if (m_cuts[i].size() != m_cuts[j].size())
return;
const auto& nodeIndex = m_generatedVerticesSourceNodeIndices[m_cuts[i][0]];
if (m_nodes[nodeIndex].radius > maxRadius) {
maxRadius = m_nodes[nodeIndex].radius;
bigCutIndex = i;
}
}
const auto& cut = m_cuts[bigCutIndex];
double sumOfLegnth = 0;
std::vector<double> edgeLengths;
edgeLengths.reserve(cut.size());
for (size_t i = 0; i < cut.size(); ++i) {
size_t j = (i + 1) % cut.size();
double length = (m_generatedVertices[cut[i]] - m_generatedVertices[cut[j]]).length();
edgeLengths.push_back(length);
sumOfLegnth += length;
}
double targetLength = 1.2 * sumOfLegnth / cut.size();
std::vector<std::vector<size_t>> newCuts(m_cuts.size());
for (size_t index = 0; index < cut.size(); ++index) {
size_t nextIndex = (index + 1) % cut.size();
for (size_t cutIndex = 0; cutIndex < m_cuts.size(); ++cutIndex) {
newCuts[cutIndex].push_back(m_cuts[cutIndex][index]);
}
const auto& oldEdgeLength = edgeLengths[index];
if (targetLength >= oldEdgeLength)
continue;
size_t newInsertNum = oldEdgeLength / targetLength;
if (newInsertNum < 1)
newInsertNum = 1;
if (newInsertNum > 100)
continue;
float stepFactor = 1.0 / (newInsertNum + 1);
float factor = stepFactor;
for (size_t i = 0; i < newInsertNum && factor < 1.0; factor += stepFactor, ++i) {
float firstFactor = 1.0 - factor;
for (size_t cutIndex = 0; cutIndex < m_cuts.size(); ++cutIndex) {
auto newPosition = m_generatedVertices[m_cuts[cutIndex][index]] * firstFactor + m_generatedVertices[m_cuts[cutIndex][nextIndex]] * factor;
newCuts[cutIndex].push_back(m_generatedVertices.size());
m_generatedVertices.push_back(newPosition);
const auto& oldIndex = m_cuts[cutIndex][index];
m_generatedVerticesCutDirects.push_back(m_generatedVerticesCutDirects[oldIndex]);
m_generatedVerticesSourceNodeIndices.push_back(m_generatedVerticesSourceNodeIndices[oldIndex]);
m_generatedVerticesInfos.push_back(m_generatedVerticesInfos[oldIndex]);
}
}
}
m_cuts = newCuts;
}
void StrokeMeshBuilder::stitchCuts()
{
for (size_t i = m_isRing ? 0 : 1; i < m_nodeIndices.size(); ++i) {
size_t h = (i + m_nodeIndices.size() - 1) % m_nodeIndices.size();
const auto& nodeH = m_nodes[m_nodeIndices[h]];
const auto& nodeI = m_nodes[m_nodeIndices[i]];
const auto& cutH = m_cuts[h];
auto reversedCutI = edgeloopFlipped(m_cuts[i]);
std::vector<std::pair<std::vector<size_t>, Vector3>> edgeLoops = {
{ cutH, -nodeH.traverseDirection },
{ reversedCutI, nodeI.traverseDirection },
};
HoleStitcher stitcher;
stitcher.setVertices(&m_generatedVertices);
stitcher.stitch(edgeLoops);
for (const auto& face : stitcher.newlyGeneratedFaces()) {
m_generatedFaces.push_back(face);
}
}
// Fill endpoints
if (!m_isRing) {
if (m_cuts.size() < 2)
return;
if (!Math::isZero(m_hollowThickness)) {
// Generate mesh for hollow
size_t startVertexIndex = m_generatedVertices.size();
for (size_t i = 0; i < startVertexIndex; ++i) {
const auto& position = m_generatedVertices[i];
const auto& node = m_nodes[m_generatedVerticesSourceNodeIndices[i]];
auto ray = position - node.position;
auto newPosition = position - ray * m_hollowThickness;
m_generatedVertices.push_back(newPosition);
m_generatedVerticesCutDirects.push_back(m_generatedVerticesCutDirects[i]);
m_generatedVerticesSourceNodeIndices.push_back(m_generatedVerticesSourceNodeIndices[i]);
m_generatedVerticesInfos.push_back(m_generatedVerticesInfos[i]);
}
size_t oldFaceNum = m_generatedFaces.size();
for (size_t i = 0; i < oldFaceNum; ++i) {
auto newFace = m_generatedFaces[i];
std::reverse(newFace.begin(), newFace.end());
for (auto& it : newFace)
it += startVertexIndex;
m_generatedFaces.push_back(newFace);
}
std::vector<std::vector<size_t>> revisedCuts = { m_cuts[0],
edgeloopFlipped(m_cuts[m_cuts.size() - 1]) };
for (const auto& cut : revisedCuts) {
for (size_t i = 0; i < cut.size(); ++i) {
size_t j = (i + 1) % cut.size();
std::vector<size_t> quad;
quad.push_back(cut[i]);
quad.push_back(cut[j]);
quad.push_back(startVertexIndex + cut[j]);
quad.push_back(startVertexIndex + cut[i]);
m_generatedFaces.push_back(quad);
}
}
} else {
if (m_cuts[0].size() <= 4) {
m_generatedFaces.push_back(m_cuts[0]);
m_generatedFaces.push_back(edgeloopFlipped(m_cuts[m_cuts.size() - 1]));
} else {
auto remeshAndAddCut = [&](const std::vector<size_t>& inputFace) {
std::vector<std::vector<size_t>> remeshedFaces;
size_t oldVertexCount = m_generatedVertices.size();
isotropicTriangulate(m_generatedVertices, inputFace, &remeshedFaces);
size_t oldIndex = inputFace[0];
for (size_t i = oldVertexCount; i < m_generatedVertices.size(); ++i) {
m_generatedVerticesCutDirects.push_back(m_generatedVerticesCutDirects[oldIndex]);
m_generatedVerticesSourceNodeIndices.push_back(m_generatedVerticesSourceNodeIndices[oldIndex]);
m_generatedVerticesInfos.push_back(m_generatedVerticesInfos[oldIndex]);
}
for (const auto& it : remeshedFaces)
m_generatedFaces.push_back(it);
};
remeshAndAddCut(m_cuts[0]);
remeshAndAddCut(edgeloopFlipped(m_cuts[m_cuts.size() - 1]));
}
}
}
}
void StrokeMeshBuilder::reviseTraverseDirections()
{
std::vector<std::pair<size_t, Vector3>> revised;
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
const auto& node = m_nodes[m_nodeIndices[i]];
if (-1 != node.nearOriginNodeIndex && -1 != node.farOriginNodeIndex) {
const auto& nearOriginNode = m_nodes[node.nearOriginNodeIndex];
const auto& farOriginNode = m_nodes[node.farOriginNodeIndex];
float nearDistance = (node.position - nearOriginNode.position).length();
float farDistance = (node.position - farOriginNode.position).length();
float totalDistance = nearDistance + farDistance;
float distanceFactor = nearDistance / totalDistance;
const Vector3* revisedNearCutNormal = nullptr;
const Vector3* revisedFarCutNormal = nullptr;
if (distanceFactor <= 0.5) {
revisedNearCutNormal = &nearOriginNode.traverseDirection;
revisedFarCutNormal = &node.traverseDirection;
} else {
distanceFactor = (1.0 - distanceFactor);
revisedNearCutNormal = &farOriginNode.traverseDirection;
revisedFarCutNormal = &node.traverseDirection;
}
distanceFactor *= 1.75;
Vector3 newTraverseDirection;
if (Vector3::dotProduct(*revisedNearCutNormal, *revisedFarCutNormal) <= 0)
newTraverseDirection = (*revisedNearCutNormal * (1.0 - distanceFactor) - *revisedFarCutNormal * distanceFactor).normalized();
else
newTraverseDirection = (*revisedNearCutNormal * (1.0 - distanceFactor) + *revisedFarCutNormal * distanceFactor).normalized();
if (Vector3::dotProduct(newTraverseDirection, node.traverseDirection) <= 0)
newTraverseDirection = -newTraverseDirection;
revised.push_back({ node.index, newTraverseDirection });
}
}
for (const auto& it : revised)
m_nodes[it.first].traverseDirection = it.second;
}
void StrokeMeshBuilder::localAverageBaseNormals()
{
std::vector<std::pair<size_t, Vector3>> averaged;
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
size_t h, j;
if (m_isRing) {
h = (i + m_nodeIndices.size() - 1) % m_nodeIndices.size();
j = (i + 1) % m_nodeIndices.size();
} else {
h = i > 0 ? i - 1 : i;
j = i + 1 < m_nodeIndices.size() ? i + 1 : i;
}
const auto& nodeH = m_nodes[m_nodeIndices[h]];
const auto& nodeI = m_nodes[m_nodeIndices[i]];
const auto& nodeJ = m_nodes[m_nodeIndices[j]];
averaged.push_back({ nodeI.index,
(nodeH.baseNormal + nodeI.baseNormal + nodeJ.baseNormal).normalized() });
}
for (const auto& it : averaged)
m_nodes[it.first].baseNormal = it.second;
}
void StrokeMeshBuilder::unifyBaseNormals()
{
for (size_t i = 1; i < m_nodeIndices.size(); ++i) {
size_t h = m_nodeIndices[i - 1];
const auto& nodeH = m_nodes[m_nodeIndices[h]];
auto& nodeI = m_nodes[m_nodeIndices[i]];
if (Vector3::dotProduct(nodeI.baseNormal, nodeH.baseNormal) < 0)
nodeI.baseNormal = -nodeI.baseNormal;
}
}
void StrokeMeshBuilder::reviseNodeBaseNormal(Node& node)
{
Vector3 orientedBaseNormal = Vector3::dotProduct(node.traverseDirection, node.baseNormal) >= 0 ? node.baseNormal : -node.baseNormal;
if (orientedBaseNormal.isZero()) {
orientedBaseNormal = calculateBaseNormalFromTraverseDirection(node.traverseDirection);
}
node.baseNormal = orientedBaseNormal;
}
bool StrokeMeshBuilder::prepare()
{
if (m_nodes.empty())
return false;
if (1 == m_nodes.size()) {
auto& node = m_nodes[0];
node.traverseOrder = 0;
node.traverseDirection = Vector3(0, 1, 0);
node.baseNormal = Vector3(0, 0, 1);
return true;
}
m_nodeIndices = sortedNodeIndices(&m_isRing);
if (m_nodeIndices.empty())
return false;
std::vector<Vector3> edgeDirections;
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
m_nodes[m_nodeIndices[i]].traverseOrder = i;
size_t j;
if (m_isRing) {
j = (i + 1) % m_nodeIndices.size();
} else {
j = i + 1 < m_nodeIndices.size() ? i + 1 : i;
}
edgeDirections.push_back((m_nodes[m_nodeIndices[j]].position - m_nodes[m_nodeIndices[i]].position).normalized());
}
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
size_t h;
if (m_isRing) {
h = (i + m_nodeIndices.size() - 1) % m_nodeIndices.size();
} else {
h = i > 0 ? i - 1 : i;
}
m_nodes[m_nodeIndices[i]].traverseDirection = (edgeDirections[h] + edgeDirections[i]).normalized();
}
reviseTraverseDirections();
// Base plane constraints
if (!m_baseNormalOnX || !m_baseNormalOnY || !m_baseNormalOnZ) {
for (auto& it : edgeDirections) {
if (!m_baseNormalOnX)
it.setX(0);
if (!m_baseNormalOnY)
it.setY(0);
if (!m_baseNormalOnZ)
it.setZ(0);
}
}
std::vector<size_t> validBaseNormalPosArray;
for (size_t i = m_isRing ? 0 : 1; i < m_nodeIndices.size(); ++i) {
size_t h = (i + m_nodeIndices.size() - 1) % m_nodeIndices.size();
// >15 degrees && < 165 degrees
if (abs(Vector3::dotProduct(edgeDirections[h], edgeDirections[i])) < 0.966) {
auto baseNormal = Vector3::crossProduct(edgeDirections[h], edgeDirections[i]);
if (!baseNormal.isZero()) {
if (!validBaseNormalPosArray.empty()) {
const auto& lastNode = m_nodes[m_nodeIndices[validBaseNormalPosArray[validBaseNormalPosArray.size() - 1]]];
if (Vector3::dotProduct(lastNode.baseNormal, baseNormal) < 0)
baseNormal = -baseNormal;
}
auto& node = m_nodes[m_nodeIndices[i]];
node.baseNormal = baseNormal;
validBaseNormalPosArray.push_back(i);
continue;
}
}
}
if (validBaseNormalPosArray.empty()) {
Vector3 baseNormal;
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
const auto& node = m_nodes[m_nodeIndices[i]];
baseNormal += calculateBaseNormalFromTraverseDirection(node.traverseDirection);
}
baseNormal.normalize();
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
auto& node = m_nodes[m_nodeIndices[i]];
node.baseNormal = baseNormal;
}
} else if (1 == validBaseNormalPosArray.size()) {
auto baseNormal = m_nodes[m_nodeIndices[validBaseNormalPosArray[0]]].baseNormal;
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
auto& node = m_nodes[m_nodeIndices[i]];
node.baseNormal = baseNormal;
}
} else {
if (!m_isRing) {
auto prePos = validBaseNormalPosArray[0];
const auto& preNode = m_nodes[m_nodeIndices[prePos]];
auto preBaseNormal = preNode.baseNormal;
auto afterPos = validBaseNormalPosArray[validBaseNormalPosArray.size() - 1];
const auto& afterNode = m_nodes[m_nodeIndices[afterPos]];
auto afterBaseNormal = afterNode.baseNormal;
for (size_t i = 0; i < prePos; ++i) {
auto& node = m_nodes[m_nodeIndices[i]];
node.baseNormal = preBaseNormal;
}
for (size_t i = afterPos + 1; i < m_nodeIndices.size(); ++i) {
auto& node = m_nodes[m_nodeIndices[i]];
node.baseNormal = afterBaseNormal;
}
}
auto updateInBetweenBaseNormal = [this](const Node& nodeU, const Node& nodeV, Node& updateNode) {
float distanceU = (updateNode.position - nodeU.position).length();
float distanceV = (updateNode.position - nodeV.position).length();
float totalDistance = distanceU + distanceV;
float factorU = 1.0 - distanceU / totalDistance;
float factorV = 1.0 - factorU;
auto baseNormal = (nodeU.baseNormal * factorU + nodeV.baseNormal * factorV).normalized();
updateNode.baseNormal = baseNormal;
};
for (size_t k = m_isRing ? 0 : 1; k < validBaseNormalPosArray.size(); ++k) {
size_t u = validBaseNormalPosArray[(k + validBaseNormalPosArray.size() - 1) % validBaseNormalPosArray.size()];
size_t v = validBaseNormalPosArray[k];
const auto& nodeU = m_nodes[m_nodeIndices[u]];
const auto& nodeV = m_nodes[m_nodeIndices[v]];
for (size_t i = (u + 1) % m_nodeIndices.size();
i != v;
i = (i + 1) % m_nodeIndices.size()) {
auto& node = m_nodes[m_nodeIndices[i]];
updateInBetweenBaseNormal(nodeU, nodeV, node);
}
}
if (m_baseNormalAverageEnabled) {
Vector3 baseNormal;
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
const auto& node = m_nodes[m_nodeIndices[i]];
baseNormal += node.baseNormal;
}
baseNormal.normalize();
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
auto& node = m_nodes[m_nodeIndices[i]];
node.baseNormal = baseNormal;
}
} else {
unifyBaseNormals();
localAverageBaseNormals();
for (size_t i = 0; i < m_nodeIndices.size(); ++i) {
reviseNodeBaseNormal(m_nodes[m_nodeIndices[i]]);
}
unifyBaseNormals();
}
}
return true;
}
std::vector<size_t> StrokeMeshBuilder::sortedNodeIndices(bool* isRing)
{
std::vector<size_t> nodeIndices;
size_t startingNodeIndex = 0;
if (!calculateStartingNodeIndex(&startingNodeIndex, isRing))
return nodeIndices;
size_t fromNodeIndex = startingNodeIndex;
std::unordered_set<size_t> visited;
auto nodeIndex = fromNodeIndex;
while (true) {
if (visited.find(nodeIndex) != visited.end())
break;
visited.insert(nodeIndex);
nodeIndices.push_back(nodeIndex);
const auto& node = m_nodes[nodeIndex];
size_t neighborIndex = node.nextOrNeighborOtherThan(fromNodeIndex);
if (neighborIndex == nodeIndex)
break;
fromNodeIndex = nodeIndex;
nodeIndex = neighborIndex;
};
return nodeIndices;
}
bool StrokeMeshBuilder::calculateStartingNodeIndex(size_t* startingNodeIndex,
bool* isRing)
{
if (m_nodes.empty())
return false;
if (1 == m_nodes.size()) {
*startingNodeIndex = 0;
*isRing = false;
return true;
}
auto findNearestNodeWithWorldCenter = [&](const std::vector<size_t>& nodeIndices) {
std::vector<std::pair<size_t, float>> dist2Array;
for (const auto& i : nodeIndices) {
dist2Array.push_back({ i, (float)m_nodes[i].position.lengthSquared() });
}
return std::min_element(dist2Array.begin(), dist2Array.end(),
[](const std::pair<size_t, float>& first,
const std::pair<size_t, float>& second) {
return first.second < second.second;
})
->first;
};
auto findEndpointNodeIndices = [&]() {
std::vector<size_t> endpointIndices;
for (const auto& it : m_nodes) {
if (1 == it.neighbors.size())
endpointIndices.push_back(it.index);
}
return endpointIndices;
};
auto endpointIndices = findEndpointNodeIndices();
if (2 != endpointIndices.size()) {
// Invalid endpoint count, there must be a ring, choose the node which is nearest with world center
std::vector<size_t> nodeIndices(m_nodes.size());
for (size_t i = 0; i < m_nodes.size(); ++i) {
if (2 != m_nodes[i].neighbors.size())
return false;
nodeIndices[i] = i;
}
*startingNodeIndex = findNearestNodeWithWorldCenter(nodeIndices);
*isRing = true;
return true;
}
auto countAlignedDirections = [&](size_t nodeIndex) {
size_t alignedCount = 0;
size_t fromNodeIndex = nodeIndex;
std::unordered_set<size_t> visited;
while (true) {
if (visited.find(nodeIndex) != visited.end())
break;
visited.insert(nodeIndex);
const auto& node = m_nodes[nodeIndex];
size_t neighborIndex = node.nextOrNeighborOtherThan(fromNodeIndex);
if (neighborIndex == nodeIndex)
break;
if (node.next == neighborIndex)
++alignedCount;
fromNodeIndex = nodeIndex;
nodeIndex = neighborIndex;
};
return alignedCount;
};
auto chooseStartingEndpointByAlignedDirections = [&](const std::vector<size_t>& endpointIndices) {
std::vector<std::pair<size_t, size_t>> alignedDirections(endpointIndices.size());
for (size_t i = 0; i < endpointIndices.size(); ++i) {
auto nodeIndex = endpointIndices[i];
alignedDirections[i] = { nodeIndex, countAlignedDirections(nodeIndex) };
}
std::sort(alignedDirections.begin(), alignedDirections.end(), [](const std::pair<size_t, size_t>& first, const std::pair<size_t, size_t>& second) {
return first.second > second.second;
});
if (alignedDirections[0].second > alignedDirections[1].second)
return alignedDirections[0].first;
std::vector<size_t> nodeIndices = { alignedDirections[0].first, alignedDirections[1].first };
return findNearestNodeWithWorldCenter(nodeIndices);
};
*startingNodeIndex = chooseStartingEndpointByAlignedDirections(endpointIndices);
*isRing = false;
return true;
}
Vector3 StrokeMeshBuilder::calculateDeformPosition(const Vector3& vertexPosition, const Vector3& ray, const Vector3& deformNormal, float deformFactor)
{
Vector3 revisedNormal = Vector3::dotProduct(ray, deformNormal) < 0.0 ? -deformNormal : deformNormal;
Vector3 projectRayOnRevisedNormal = revisedNormal * (Vector3::dotProduct(ray, revisedNormal) / revisedNormal.lengthSquared());
auto scaledProjct = projectRayOnRevisedNormal * deformFactor;
return vertexPosition + (scaledProjct - projectRayOnRevisedNormal);
}
void StrokeMeshBuilder::applyDeform()
{
float maxRadius = 0.0;
if (m_deformUnified) {
for (const auto& node : m_nodes) {
if (node.radius > maxRadius)
maxRadius = node.radius;
}
}
for (size_t i = 0; i < m_generatedVertices.size(); ++i) {
auto& position = m_generatedVertices[i];
const auto& node = m_nodes[m_generatedVerticesSourceNodeIndices[i]];
const auto& cutDirect = m_generatedVerticesCutDirects[i];
auto ray = position - node.position;
Vector3 sum;
size_t count = 0;
float deformUnifyFactor = m_deformUnified ? maxRadius / node.radius : 1.0;
if (!Math::isEqual(m_deformThickness, (float)1.0)) {
auto deformedPosition = calculateDeformPosition(position, ray, node.baseNormal, m_deformThickness * deformUnifyFactor);
sum += deformedPosition;
++count;
}
if (!Math::isEqual(m_deformWidth, (float)1.0)) {
auto deformedPosition = calculateDeformPosition(position, ray, Vector3::crossProduct(node.baseNormal, cutDirect), m_deformWidth * deformUnifyFactor);
sum += deformedPosition;
++count;
}
if (count > 0)
position = sum / count;
}
}
bool StrokeMeshBuilder::buildBaseNormalsOnly()
{
return prepare();
}
bool StrokeMeshBuilder::build()
{
if (!prepare())
return false;
buildMesh();
applyDeform();
interpolateCutEdges();
stitchCuts();
return true;
}
}

View File

@ -1,141 +0,0 @@
/*
* Copyright (c) 2016-2021 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_STOKE_MESH_BUILDER_H_
#define DUST3D_MESH_STOKE_MESH_BUILDER_H_
#include <dust3d/base/vector2.h>
#include <dust3d/base/vector3.h>
#include <map>
#include <set>
#include <vector>
namespace dust3d {
class StrokeMeshBuilder {
public:
struct CutFaceTransform {
Vector3 translation;
float scale;
Vector3 uFactor;
Vector3 vFactor;
bool reverse = false;
};
struct Node {
float radius;
Vector3 position;
std::vector<Vector2> cutTemplate;
float cutRotation;
int nearOriginNodeIndex = -1;
int farOriginNodeIndex = -1;
size_t index;
std::vector<size_t> neighbors;
size_t next;
Vector3 cutNormal;
Vector3 traverseDirection;
Vector3 baseNormal;
size_t traverseOrder;
size_t nextOrNeighborOtherThan(size_t neighborIndex) const;
};
size_t addNode(const Vector3& position, float radius, const std::vector<Vector2>& cutTemplate, float cutRotation);
void addEdge(size_t firstNodeIndex, size_t secondNodeIndex);
void setNodeOriginInfo(size_t nodeIndex, int nearOriginNodeIndex, int farOriginNodeIndex);
void setDeformThickness(float thickness);
void setDeformWidth(float width);
void setDeformUnified(bool unified);
void setHollowThickness(float hollowThickness);
void enableBaseNormalOnX(bool enabled);
void enableBaseNormalOnY(bool enabled);
void enableBaseNormalOnZ(bool enabled);
void enableBaseNormalAverage(bool enabled);
bool buildBaseNormalsOnly();
const std::vector<Node>& nodes() const;
const std::vector<size_t>& nodeIndices() const;
const Vector3& nodeTraverseDirection(size_t nodeIndex) const;
const Vector3& nodeBaseNormal(size_t nodeIndex) const;
size_t nodeTraverseOrder(size_t nodeIndex) const;
bool build();
const std::vector<Vector3>& generatedVertices();
const std::vector<std::vector<size_t>>& generatedFaces();
const std::vector<size_t>& generatedVerticesSourceNodeIndices();
static Vector3 calculateDeformPosition(const Vector3& vertexPosition, const Vector3& ray, const Vector3& deformNormal, float deformFactor);
static Vector3 calculateBaseNormalFromTraverseDirection(const Vector3& traverseDirection);
private:
struct GeneratedVertexInfo {
size_t orderInCut;
size_t cutSize;
};
std::vector<Node> m_nodes;
float m_deformThickness = 1.0f;
float m_deformWidth = 1.0f;
float m_cutRotation = 0.0f;
bool m_baseNormalOnX = true;
bool m_baseNormalOnY = true;
bool m_baseNormalOnZ = true;
bool m_baseNormalAverageEnabled = false;
float m_hollowThickness = 0.0f;
bool m_deformUnified = false;
bool m_isRing = false;
std::vector<size_t> m_nodeIndices;
std::vector<Vector3> m_generatedVertices;
std::vector<Vector3> m_generatedVerticesCutDirects;
std::vector<size_t> m_generatedVerticesSourceNodeIndices;
std::vector<GeneratedVertexInfo> m_generatedVerticesInfos;
std::vector<std::vector<size_t>> m_generatedFaces;
std::vector<std::vector<size_t>> m_cuts;
bool prepare();
std::vector<Vector3> makeCut(const Vector3& cutCenter,
float radius,
const std::vector<Vector2>& cutTemplate,
const Vector3& cutNormal,
const Vector3& baseNormal);
void insertCutVertices(const std::vector<Vector3>& cut,
std::vector<size_t>* vertices,
size_t nodeIndex,
const Vector3& cutNormal);
void buildMesh();
std::vector<size_t> sortedNodeIndices(bool* isRing);
bool calculateStartingNodeIndex(size_t* startingNodeIndex,
bool* isRing);
void reviseTraverseDirections();
void localAverageBaseNormals();
void unifyBaseNormals();
std::vector<size_t> edgeloopFlipped(const std::vector<size_t>& edgeLoop);
void reviseNodeBaseNormal(Node& node);
void applyDeform();
void interpolateCutEdges();
void stitchCuts();
};
}
#endif

View File

@ -1,302 +0,0 @@
/*
* Copyright (c) 2016-2021 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/mesh/centripetal_catmull_rom_spline.h>
#include <dust3d/mesh/stroke_modifier.h>
#include <map>
#include <unordered_map>
namespace dust3d {
void StrokeModifier::enableIntermediateAddition()
{
m_intermediateAdditionEnabled = true;
}
void StrokeModifier::enableSmooth()
{
m_smooth = true;
}
size_t StrokeModifier::addNode(const Vector3& position, float radius, const std::vector<Vector2>& cutTemplate, float cutRotation)
{
size_t nodeIndex = m_nodes.size();
Node node;
node.isOriginal = true;
node.position = position;
node.radius = radius;
node.cutTemplate = cutTemplate;
node.cutRotation = cutRotation;
node.originNodeIndex = nodeIndex;
m_nodes.push_back(node);
return nodeIndex;
}
size_t StrokeModifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
{
size_t edgeIndex = m_edges.size();
Edge edge;
edge.firstNodeIndex = firstNodeIndex;
edge.secondNodeIndex = secondNodeIndex;
m_edges.push_back(edge);
return edgeIndex;
}
void StrokeModifier::createIntermediateNode(const Node& firstNode, const Node& secondNode, float factor, Node* resultNode)
{
float firstFactor = 1.0 - factor;
resultNode->position = firstNode.position * firstFactor + secondNode.position * factor;
resultNode->radius = firstNode.radius * firstFactor + secondNode.radius * factor;
if (factor <= 0.5) {
resultNode->originNodeIndex = firstNode.originNodeIndex;
resultNode->nearOriginNodeIndex = firstNode.originNodeIndex;
resultNode->farOriginNodeIndex = secondNode.originNodeIndex;
resultNode->cutRotation = firstNode.cutRotation;
resultNode->cutTemplate = firstNode.cutTemplate;
} else {
resultNode->originNodeIndex = secondNode.originNodeIndex;
resultNode->nearOriginNodeIndex = secondNode.originNodeIndex;
resultNode->farOriginNodeIndex = firstNode.originNodeIndex;
resultNode->cutRotation = secondNode.cutRotation;
resultNode->cutTemplate = secondNode.cutTemplate;
}
}
void StrokeModifier::subdivide()
{
for (auto& node : m_nodes) {
subdivideFace(&node.cutTemplate);
}
}
void StrokeModifier::subdivideFace(std::vector<Vector2>* face)
{
auto oldFace = *face;
face->resize(oldFace.size() * 2);
for (size_t i = 0, n = 0; i < oldFace.size(); ++i) {
size_t h = (i + oldFace.size() - 1) % oldFace.size();
size_t j = (i + 1) % oldFace.size();
(*face)[n++] = oldFace[h] * 0.125 + oldFace[i] * 0.75 + oldFace[j] * 0.125;
(*face)[n++] = (oldFace[i] + oldFace[j]) * 0.5;
}
}
float StrokeModifier::averageCutTemplateEdgeLength(const std::vector<Vector2>& cutTemplate)
{
if (cutTemplate.empty())
return 0;
float sum = 0;
for (size_t i = 0; i < cutTemplate.size(); ++i) {
size_t j = (i + 1) % cutTemplate.size();
sum += (cutTemplate[i] - cutTemplate[j]).length();
}
return sum / cutTemplate.size();
}
void StrokeModifier::roundEnd()
{
std::map<size_t, std::vector<size_t>> neighbors;
for (const auto& edge : m_edges) {
neighbors[edge.firstNodeIndex].push_back(edge.secondNodeIndex);
neighbors[edge.secondNodeIndex].push_back(edge.firstNodeIndex);
}
for (const auto& it : neighbors) {
if (1 == it.second.size()) {
const Node& currentNode = m_nodes[it.first];
const Node& neighborNode = m_nodes[it.second[0]];
Node endNode;
endNode.radius = currentNode.radius * 0.5;
endNode.position = currentNode.position + (currentNode.position - neighborNode.position).normalized() * endNode.radius;
endNode.cutTemplate = currentNode.cutTemplate;
endNode.cutRotation = currentNode.cutRotation;
endNode.originNodeIndex = currentNode.originNodeIndex;
size_t endNodeIndex = m_nodes.size();
m_nodes.push_back(endNode);
addEdge(endNode.originNodeIndex, endNodeIndex);
}
}
}
void StrokeModifier::createIntermediateCutTemplateEdges(std::vector<Vector2>& cutTemplate, float averageCutTemplateLength)
{
std::vector<Vector2> newCutTemplate;
auto pointCount = cutTemplate.size();
float targetLength = averageCutTemplateLength * 1.2;
for (size_t index = 0; index < pointCount; ++index) {
size_t nextIndex = (index + 1) % pointCount;
newCutTemplate.push_back(cutTemplate[index]);
float oldEdgeLength = (cutTemplate[index] - cutTemplate[nextIndex]).length();
if (targetLength >= oldEdgeLength)
continue;
size_t newInsertNum = oldEdgeLength / targetLength;
if (newInsertNum < 1)
newInsertNum = 1;
if (newInsertNum > 100)
continue;
float stepFactor = 1.0 / (newInsertNum + 1);
float factor = stepFactor;
for (size_t i = 0; i < newInsertNum && factor < 1.0; factor += stepFactor, ++i) {
float firstFactor = 1.0 - factor;
newCutTemplate.push_back(cutTemplate[index] * firstFactor + cutTemplate[nextIndex] * factor);
}
}
cutTemplate = newCutTemplate;
}
void StrokeModifier::finalize()
{
if (!m_intermediateAdditionEnabled)
return;
for (auto& node : m_nodes) {
node.averageCutTemplateLength = averageCutTemplateEdgeLength(node.cutTemplate);
createIntermediateCutTemplateEdges(node.cutTemplate, node.averageCutTemplateLength);
}
auto oldEdges = m_edges;
m_edges.clear();
for (const auto& edge : oldEdges) {
const Node& firstNode = m_nodes[edge.firstNodeIndex];
const Node& secondNode = m_nodes[edge.secondNodeIndex];
auto firstAverageCutTemplateEdgeLength = firstNode.averageCutTemplateLength * firstNode.radius;
auto secondAverageCutTemplateEdgeLength = secondNode.averageCutTemplateLength * secondNode.radius;
float targetEdgeLength = (firstAverageCutTemplateEdgeLength + secondAverageCutTemplateEdgeLength) * 0.5;
float currentEdgeLength = (firstNode.position - secondNode.position).length();
if (targetEdgeLength >= currentEdgeLength) {
addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
continue;
}
size_t newInsertNum = currentEdgeLength / targetEdgeLength;
if (newInsertNum < 1)
newInsertNum = 1;
if (newInsertNum > 100) {
addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
continue;
}
float stepFactor = 1.0 / (newInsertNum + 1);
std::vector<size_t> nodeIndices;
nodeIndices.push_back(edge.firstNodeIndex);
float factor = stepFactor;
for (size_t i = 0; i < newInsertNum && factor < 1.0; factor += stepFactor, ++i) {
Node intermediateNode;
const Node& firstNode = m_nodes[edge.firstNodeIndex];
const Node& secondNode = m_nodes[edge.secondNodeIndex];
createIntermediateNode(firstNode, secondNode, factor, &intermediateNode);
size_t intermedidateNodeIndex = m_nodes.size();
nodeIndices.push_back(intermedidateNodeIndex);
m_nodes.push_back(intermediateNode);
}
nodeIndices.push_back(edge.secondNodeIndex);
for (size_t i = 1; i < nodeIndices.size(); ++i) {
addEdge(nodeIndices[i - 1], nodeIndices[i]);
}
}
if (m_smooth)
smooth();
}
void StrokeModifier::smooth()
{
std::unordered_map<int, std::vector<int>> neighborMap;
for (const auto& edge : m_edges) {
neighborMap[edge.firstNodeIndex].push_back(edge.secondNodeIndex);
neighborMap[edge.secondNodeIndex].push_back(edge.firstNodeIndex);
}
int startEndpoint = 0;
for (const auto& edge : m_edges) {
auto findNeighbor = neighborMap.find(edge.firstNodeIndex);
if (findNeighbor == neighborMap.end())
continue;
if (1 != findNeighbor->second.size()) {
auto findNeighborNeighbor = neighborMap.find(edge.secondNodeIndex);
if (findNeighborNeighbor == neighborMap.end())
continue;
if (1 != findNeighborNeighbor->second.size())
continue;
startEndpoint = edge.secondNodeIndex;
} else {
startEndpoint = edge.firstNodeIndex;
}
break;
}
if (-1 == startEndpoint)
return;
int loopIndex = startEndpoint;
int previousIndex = -1;
bool isRing = false;
std::vector<int> loop;
while (-1 != loopIndex) {
loop.push_back(loopIndex);
auto findNeighbor = neighborMap.find(loopIndex);
if (findNeighbor == neighborMap.end())
return;
int nextIndex = -1;
for (const auto& index : findNeighbor->second) {
if (index == previousIndex)
continue;
if (index == startEndpoint) {
isRing = true;
break;
}
nextIndex = index;
break;
}
previousIndex = loopIndex;
loopIndex = nextIndex;
}
CentripetalCatmullRomSpline spline(isRing);
for (size_t i = 0; i < loop.size(); ++i) {
const auto& nodeIndex = loop[i];
const auto& node = m_nodes[nodeIndex];
bool isKnot = node.originNodeIndex == nodeIndex;
spline.addPoint((int)nodeIndex, node.position, isKnot);
}
if (!spline.interpolate())
return;
for (const auto& it : spline.splineNodes()) {
if (-1 == it.source)
continue;
auto& node = m_nodes[it.source];
node.position = it.position;
}
}
const std::vector<StrokeModifier::Node>& StrokeModifier::nodes() const
{
return m_nodes;
}
const std::vector<StrokeModifier::Edge>& StrokeModifier::edges() const
{
return m_edges;
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright (c) 2016-2021 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_STROKE_MODIFIER_H_
#define DUST3D_MESH_STROKE_MODIFIER_H_
#include <dust3d/base/vector3.h>
#include <vector>
namespace dust3d {
class StrokeModifier {
public:
struct Node {
bool isOriginal = false;
Vector3 position;
float radius = 0.0;
std::vector<Vector2> cutTemplate;
float cutRotation = 0.0;
int nearOriginNodeIndex = -1;
int farOriginNodeIndex = -1;
int originNodeIndex = 0;
float averageCutTemplateLength;
};
struct Edge {
size_t firstNodeIndex;
size_t secondNodeIndex;
};
size_t addNode(const Vector3& position, float radius, const std::vector<Vector2>& cutTemplate, float cutRotation);
size_t addEdge(size_t firstNodeIndex, size_t secondNodeIndex);
void subdivide();
void roundEnd();
void enableIntermediateAddition();
void enableSmooth();
const std::vector<Node>& nodes() const;
const std::vector<Edge>& edges() const;
void finalize();
static void subdivideFace(std::vector<Vector2>* face);
private:
std::vector<Node> m_nodes;
std::vector<Edge> m_edges;
bool m_intermediateAdditionEnabled = false;
bool m_smooth = false;
void createIntermediateNode(const Node& firstNode, const Node& secondNode, float factor, Node* resultNode);
float averageCutTemplateEdgeLength(const std::vector<Vector2>& cutTemplate);
void createIntermediateCutTemplateEdges(std::vector<Vector2>& cutTemplate, float averageCutTemplateLength);
void smooth();
};
}
#endif