Remove stroke mesh builder
parent
439ea25a86
commit
c614da251a
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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