Remove stroke mesh builder
parent
439ea25a86
commit
c614da251a
|
@ -300,10 +300,6 @@ HEADERS += ../dust3d/mesh/smooth_normal.h
|
|||
SOURCES += ../dust3d/mesh/smooth_normal.cc
|
||||
HEADERS += ../dust3d/mesh/stitch_mesh_builder.h
|
||||
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
|
||||
SOURCES += ../dust3d/mesh/triangulate.cc
|
||||
HEADERS += ../dust3d/mesh/trim_vertices.h
|
||||
|
|
|
@ -33,8 +33,6 @@
|
|||
#include <dust3d/mesh/section_preview_mesh_builder.h>
|
||||
#include <dust3d/mesh/smooth_normal.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/trim_vertices.h>
|
||||
#include <dust3d/mesh/tube_mesh_builder.h>
|
||||
|
@ -737,347 +735,6 @@ std::unique_ptr<MeshCombiner::Mesh> MeshGenerator::combinePartMesh(const std::st
|
|||
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>* component = &m_snapshot->rootComponent;
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include <dust3d/base/uuid.h>
|
||||
#include <dust3d/mesh/mesh_combiner.h>
|
||||
#include <dust3d/mesh/mesh_node.h>
|
||||
#include <dust3d/mesh/stroke_mesh_builder.h>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue