#include #include #include #include #include #include #include #include #include #include #include #include #include #include "strokemeshbuilder.h" #include "meshstitcher.h" #include "boxmesh.h" #include "meshcombiner.h" #include "util.h" #define WRAP_STEP_BACK_FACTOR 0.1 // 0.1 ~ 0.9 #define WRAP_WELD_FACTOR 0.01 // Allowed distance: WELD_FACTOR * radius size_t StrokeMeshBuilder::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation) { size_t nodeIndex = m_nodes.size(); Node node; node.position = position; node.radius = radius; node.cutTemplate = cutTemplate; node.cutRotation = cutRotation; m_nodes.push_back(node); m_sortedNodeIndices.push_back(nodeIndex); //qDebug() << "addNode" << position << radius; return nodeIndex; } size_t StrokeMeshBuilder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) { size_t edgeIndex = m_edges.size(); Edge edge; edge.nodes = {firstNodeIndex, secondNodeIndex}; m_edges.push_back(edge); m_nodes[firstNodeIndex].edges.push_back(edgeIndex); m_nodes[secondNodeIndex].edges.push_back(edgeIndex); //qDebug() << "addEdge" << firstNodeIndex << secondNodeIndex; return edgeIndex; } const std::vector &StrokeMeshBuilder::generatedVertices() { return m_generatedVertices; } const std::vector> &StrokeMeshBuilder::generatedFaces() { return m_generatedFaces; } const std::vector &StrokeMeshBuilder::generatedVerticesSourceNodeIndices() { return m_generatedVerticesSourceNodeIndices; } void StrokeMeshBuilder::layoutNodes() { std::unordered_set processedNodes; std::queue waitNodes; std::vector threeBranchNodes; for (size_t i = 0; i < m_nodes.size(); ++i) { if (m_nodes[i].edges.size() == 1) { waitNodes.push(i); break; } } if (waitNodes.empty()) return; m_sortedNodeIndices.clear(); while (!waitNodes.empty()) { auto index = waitNodes.front(); waitNodes.pop(); if (processedNodes.find(index) != processedNodes.end()) continue; const auto &node = m_nodes[index]; for (const auto &edgeIndex: node.edges) { const auto &edge = m_edges[edgeIndex]; for (const auto &nodeIndex: edge.nodes) { if (processedNodes.find(nodeIndex) == processedNodes.end()) waitNodes.push(nodeIndex); } } if (node.edges.size() < 3) { m_sortedNodeIndices.push_back(index); } else { threeBranchNodes.push_back(index); } processedNodes.insert(index); } if (m_sortedNodeIndices.size() > 1) { QVector3D sumOfDirections; for (size_t i = 1; i < m_sortedNodeIndices.size(); ++i) { auto firstNodeIndex = m_sortedNodeIndices[i - 1]; auto nextNodeIndex = m_sortedNodeIndices[i]; sumOfDirections += (m_nodes[nextNodeIndex].position - m_nodes[firstNodeIndex].position); } QVector3D layoutDirection = sumOfDirections.normalized(); const std::vector axisList = { QVector3D(1, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 0, 1), }; std::vector> dots; for (size_t i = 0; i < axisList.size(); ++i) { dots.push_back(std::make_pair(qAbs(QVector3D::dotProduct(layoutDirection, axisList[i])), i)); } std::sort(dots.begin(), dots.end(), [](const std::pair &first, const std::pair &second) { return first.first > second.first; }); const auto &headNode = m_nodes[m_sortedNodeIndices[0]]; const auto &tailNode = m_nodes[m_sortedNodeIndices[m_sortedNodeIndices.size() - 1]]; bool needReverse = false; const auto &choosenAxis = dots[0].second; switch (choosenAxis) { case 0: // x if (headNode.position.x() * headNode.position.x() > tailNode.position.x() * tailNode.position.x()) needReverse = true; break; case 1: // y if (headNode.position.y() * headNode.position.y() > tailNode.position.y() * tailNode.position.y()) needReverse = true; break; case 2: // z default: if (headNode.position.z() * headNode.position.z() > tailNode.position.z() * tailNode.position.z()) needReverse = true; break; } if (needReverse) std::reverse(m_sortedNodeIndices.begin(), m_sortedNodeIndices.end()); } std::sort(threeBranchNodes.begin(), threeBranchNodes.end(), [&](const size_t &firstIndex, const size_t &secondIndex) { const Node &firstNode = m_nodes[firstIndex]; const Node &secondNode = m_nodes[secondIndex]; if (firstNode.edges.size() > secondNode.edges.size()) return true; if (firstNode.edges.size() < secondNode.edges.size()) return false; if (firstNode.radius > secondNode.radius) return true; if (firstNode.radius < secondNode.radius) return false; if (firstNode.position.y() > secondNode.position.y()) return true; if (firstNode.position.y() < secondNode.position.y()) return false; if (firstNode.position.z() > secondNode.position.z()) return true; if (firstNode.position.z() < secondNode.position.z()) return false; if (firstNode.position.x() > secondNode.position.x()) return true; if (firstNode.position.x() < secondNode.position.x()) return false; return false; }); m_sortedNodeIndices.insert(m_sortedNodeIndices.begin(), threeBranchNodes.begin(), threeBranchNodes.end()); } void StrokeMeshBuilder::sortNodeIndices() { layoutNodes(); } void StrokeMeshBuilder::prepareNode(size_t nodeIndex) { auto &node = m_nodes[nodeIndex]; node.raysToNeibors.resize(node.edges.size()); std::vector neighborPositions(node.edges.size()); std::vector neighborRadius(node.edges.size()); for (size_t i = 0; i < node.edges.size(); ++i) { size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; node.raysToNeibors[i] = (neighbor.position - node.position).normalized(); neighborPositions[i] = neighbor.position; neighborRadius[i] = neighbor.radius; } if (node.edges.size() == 1) { node.cutNormal = node.raysToNeibors[0]; } else if (node.edges.size() == 2) { node.cutNormal = (node.raysToNeibors[0] - node.raysToNeibors[1]) * 0.5; } auto baseNormalResult = calculateBaseNormal(node.raysToNeibors, neighborPositions, neighborRadius); node.initialBaseNormal = baseNormalResult.first; node.hasInitialBaseNormal = baseNormalResult.second; if (node.hasInitialBaseNormal) node.initialBaseNormal = revisedBaseNormalAcordingToCutNormal(node.initialBaseNormal, node.traverseDirection); } void StrokeMeshBuilder::setNodeOriginInfo(size_t nodeIndex, int nearOriginNodeIndex, int farOriginNodeIndex) { auto &node = m_nodes[nodeIndex]; node.nearOriginNodeIndex = nearOriginNodeIndex; node.farOriginNodeIndex = farOriginNodeIndex; } QVector3D StrokeMeshBuilder::calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection) { const std::vector axisList = { QVector3D {1, 0, 0}, QVector3D {0, 1, 0}, QVector3D {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 = QVector3D::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 = QVector3D::crossProduct(traverseDirection, choosenAxis).normalized(); return reversed ? -baseNormal : baseNormal; } void StrokeMeshBuilder::resolveBaseNormalRecursively(size_t nodeIndex) { auto &node = m_nodes[nodeIndex]; if (node.baseNormalResolved) return; if (node.hasInitialBaseNormal) { resolveBaseNormalForLeavesRecursively(nodeIndex, node.initialBaseNormal); } else { node.baseNormalSearched = true; auto searchResult = searchBaseNormalFromNeighborsRecursively(nodeIndex); if (searchResult.second) { resolveBaseNormalForLeavesRecursively(nodeIndex, searchResult.first); } else { resolveBaseNormalForLeavesRecursively(nodeIndex, calculateBaseNormalFromTraverseDirection(node.traverseDirection)); } } } void StrokeMeshBuilder::resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVector3D &baseNormal) { auto &node = m_nodes[nodeIndex]; if (node.baseNormalResolved) return; node.baseNormalResolved = true; node.baseNormal = baseNormal; for (size_t i = 0; i < node.edges.size(); ++i) { size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; switch (neighbor.edges.size()) { case 1: resolveBaseNormalForLeavesRecursively(neighborIndex, baseNormal); break; case 2: neighbor.hasInitialBaseNormal ? resolveBaseNormalForLeavesRecursively(neighborIndex, neighbor.initialBaseNormal) : resolveBaseNormalForLeavesRecursively(neighborIndex, baseNormal); break; default: // void break; } } } void StrokeMeshBuilder::resolveInitialTraverseDirectionRecursively(size_t nodeIndex, const QVector3D *from, std::set *visited) { if (visited->find(nodeIndex) != visited->end()) return; auto &node = m_nodes[nodeIndex]; node.reversedTraverseOrder = visited->size(); visited->insert(nodeIndex); if (nullptr != from) { node.initialTraverseDirection = (node.position - *from).normalized(); node.hasInitialTraverseDirection = true; } for (size_t i = 0; i < node.edges.size(); ++i) { size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex); resolveInitialTraverseDirectionRecursively(neighborIndex, &node.position, visited); } } void StrokeMeshBuilder::resolveTraverseDirection(size_t nodeIndex) { auto &node = m_nodes[nodeIndex]; if (!node.hasInitialTraverseDirection) { if (node.edges.size() > 0) { size_t neighborIndex = m_edges[node.edges[0]].neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; node.initialTraverseDirection = neighbor.initialTraverseDirection; node.hasInitialTraverseDirection = true; } } if (node.edges.size() == 2) { std::vector neighborIndices = {m_edges[node.edges[0]].neiborOf(nodeIndex), m_edges[node.edges[1]].neiborOf(nodeIndex)}; std::sort(neighborIndices.begin(), neighborIndices.end(), [&](const size_t &firstIndex, const size_t &secondIndex) { return m_nodes[firstIndex].reversedTraverseOrder < m_nodes[secondIndex].reversedTraverseOrder; }); node.traverseDirection = (node.initialTraverseDirection + m_nodes[neighborIndices[1]].initialTraverseDirection).normalized(); } else { node.traverseDirection = node.initialTraverseDirection; } } std::pair StrokeMeshBuilder::searchBaseNormalFromNeighborsRecursively(size_t nodeIndex) { auto &node = m_nodes[nodeIndex]; node.baseNormalSearched = true; for (size_t i = 0; i < node.edges.size(); ++i) { size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; if (neighbor.baseNormalResolved) return {neighbor.baseNormal, true}; } for (size_t i = 0; i < node.edges.size(); ++i) { size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; if (neighbor.hasInitialBaseNormal) return {neighbor.initialBaseNormal, true}; } for (size_t i = 0; i < node.edges.size(); ++i) { size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; if (neighbor.baseNormalSearched) continue; auto searchResult = searchBaseNormalFromNeighborsRecursively(neighborIndex); if (searchResult.second) return searchResult; } return {{}, false}; } bool StrokeMeshBuilder::build() { bool succeed = true; sortNodeIndices(); if (m_sortedNodeIndices.size() < 2) { if (m_sortedNodeIndices.size() == 1) { const Node &node = m_nodes[0]; int subdivideTimes = (node.cutTemplate.size() / 4) - 1; if (subdivideTimes < 0) subdivideTimes = 0; boxmesh(node.position, node.radius, subdivideTimes, m_generatedVertices, m_generatedFaces); m_generatedVerticesSourceNodeIndices.resize(m_generatedVertices.size(), 0); } return true; } { std::set visited; for (auto nodeIndex = m_sortedNodeIndices.rbegin(); nodeIndex != m_sortedNodeIndices.rend(); ++nodeIndex) { resolveInitialTraverseDirectionRecursively(*nodeIndex, nullptr, &visited); } } { for (auto nodeIndex = m_sortedNodeIndices.rbegin(); nodeIndex != m_sortedNodeIndices.rend(); ++nodeIndex) { resolveTraverseDirection(*nodeIndex); } } for (const auto &nodeIndex: m_sortedNodeIndices) { prepareNode(nodeIndex); } if (m_baseNormalAverageEnabled) { QVector3D sumNormal; for (const auto &node: m_nodes) { if (node.hasInitialBaseNormal) { sumNormal += node.initialBaseNormal * node.radius; } } QVector3D averageNormal = sumNormal.normalized(); if (!averageNormal.isNull()) { for (auto &node: m_nodes) { if (node.hasInitialBaseNormal) { node.initialBaseNormal = revisedBaseNormalAcordingToCutNormal(averageNormal, node.traverseDirection); } } } } for (const auto &nodeIndex: m_sortedNodeIndices) { resolveBaseNormalRecursively(nodeIndex); } unifyBaseNormals(); localAverageBaseNormals(); unifyBaseNormals(); for (const auto &nodeIndex: m_sortedNodeIndices) { if (!generateCutsForNode(nodeIndex)) succeed = false; } stitchEdgeCuts(); applyWeld(); applyDeform(); finalizeHollow(); return succeed; } void StrokeMeshBuilder::localAverageBaseNormals() { std::vector localAverageNormals; for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) { const auto &node = m_nodes[nodeIndex]; QVector3D sumOfNormals = node.baseNormal; for (const auto &edgeIndex: node.edges) { const auto &edge = m_edges[edgeIndex]; size_t neighborIndex = edge.neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; sumOfNormals += neighbor.baseNormal; } localAverageNormals.push_back(sumOfNormals.normalized()); } for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) { m_nodes[nodeIndex].baseNormal = localAverageNormals[nodeIndex]; } } bool StrokeMeshBuilder::validateNormal(const QVector3D &normal) { if (normal.isNull()) { return false; } return true; } 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; } std::pair StrokeMeshBuilder::calculateBaseNormal(const std::vector &inputDirects, const std::vector &inputPositions, const std::vector &weights) { std::vector directs = inputDirects; std::vector positions = inputPositions; if (!m_baseNormalOnX || !m_baseNormalOnY || !m_baseNormalOnZ) { for (auto &it: directs) { if (!m_baseNormalOnX) it.setX(0); if (!m_baseNormalOnY) it.setY(0); if (!m_baseNormalOnZ) it.setZ(0); } for (auto &it: positions) { if (!m_baseNormalOnX) it.setX(0); if (!m_baseNormalOnY) it.setY(0); if (!m_baseNormalOnZ) it.setZ(0); } } auto calculateTwoPointsNormal = [&](size_t i0, size_t i1) -> std::pair { auto normal = QVector3D::crossProduct(directs[i0], directs[i1]).normalized(); if (validateNormal(normal)) { return {normal, true}; } return {{}, false}; }; auto calculateThreePointsNormal = [&](size_t i0, size_t i1, size_t i2) -> std::pair { auto normal = QVector3D::normal(positions[i0], positions[i1], positions[i2]); if (validateNormal(normal)) { return {normal, true}; } // >=15 degrees && <= 165 degrees if (abs(QVector3D::dotProduct(directs[i0], directs[i1])) < 0.966) { auto twoPointsResult = calculateTwoPointsNormal(i0, i1); if (twoPointsResult.second) return twoPointsResult; } if (abs(QVector3D::dotProduct(directs[i1], directs[i2])) < 0.966) { auto twoPointsResult = calculateTwoPointsNormal(i1, i2); if (twoPointsResult.second) return twoPointsResult; } if (abs(QVector3D::dotProduct(directs[i2], directs[i0])) < 0.966) { auto twoPointsResult = calculateTwoPointsNormal(i2, i0); if (twoPointsResult.second) return twoPointsResult; } return {{}, false}; }; if (directs.size() <= 1) { return {{}, false}; } else if (directs.size() <= 2) { // >=15 degrees && <= 165 degrees if (abs(QVector3D::dotProduct(directs[0], directs[1])) < 0.966) { auto twoPointsResult = calculateTwoPointsNormal(0, 1); if (twoPointsResult.second) return twoPointsResult; } return {{}, false}; } else if (directs.size() <= 3) { return calculateThreePointsNormal(0, 1, 2); } else { std::vector> weightedIndices; for (size_t i = 0; i < weights.size(); ++i) { weightedIndices.push_back({i, weights[i]}); } std::sort(weightedIndices.begin(), weightedIndices.end(), [](const std::pair &first, const std::pair &second) { return first.second > second.second; }); return calculateThreePointsNormal(weightedIndices[0].first, weightedIndices[1].first, weightedIndices[2].first); } } void StrokeMeshBuilder::insertCutVertices(const std::vector &cut, std::vector &vertices, size_t nodeIndex, const QVector3D &cutDirect, bool cutFlipped) { 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(cutDirect); GeneratedVertexInfo info; info.orderInCut = cutFlipped ? ((cut.size() - indexInCut) % cut.size()) : indexInCut; info.cutSize = cut.size(); m_generatedVerticesInfos.push_back(info); vertices.push_back(vertexIndex); ++indexInCut; } } const StrokeMeshBuilder::CutFaceTransform *StrokeMeshBuilder::nodeAdjustableCutFaceTransform(size_t nodeIndex) { if (nodeIndex >= m_nodes.size()) return nullptr; const auto &node = m_nodes[nodeIndex]; if (!node.hasAdjustableCutFace) return nullptr; return &node.cutFaceTransform; } bool StrokeMeshBuilder::generateCutsForNode(size_t nodeIndex) { if (m_swallowedNodes.find(nodeIndex) != m_swallowedNodes.end()) { //qDebug() << "node" << nodeIndex << "ignore cuts generating because of been swallowed"; return true; } auto &node = m_nodes[nodeIndex]; size_t neighborsCount = node.edges.size(); //qDebug() << "Generate cuts for node" << nodeIndex << "with neighbor count" << neighborsCount; if (1 == neighborsCount) { QVector3D cutNormal = node.cutNormal; std::vector cut; bool cutFlipped = false; makeCut(node.position, node.radius, node.cutTemplate, node.cutRotation, node.baseNormal, cutNormal, node.traverseDirection, cut, &node.cutFaceTransform, &cutFlipped); node.hasAdjustableCutFace = true; std::vector vertices; insertCutVertices(cut, vertices, nodeIndex, cutNormal, cutFlipped); if (qFuzzyIsNull(m_hollowThickness)) { m_generatedFaces.push_back(vertices); } else { m_endCuts.push_back(vertices); } m_edges[node.edges[0]].cuts.push_back({vertices, -cutNormal}); } else if (2 == neighborsCount) { QVector3D cutNormal = node.cutNormal; if (-1 != node.nearOriginNodeIndex && -1 != node.farOriginNodeIndex) { const auto &nearOriginNode = m_nodes[node.nearOriginNodeIndex]; const auto &farOriginNode = m_nodes[node.farOriginNodeIndex]; if (nearOriginNode.edges.size() <= 2 && farOriginNode.edges.size() <= 2) { float nearDistance = node.position.distanceToPoint(nearOriginNode.position); float farDistance = node.position.distanceToPoint(farOriginNode.position); float totalDistance = nearDistance + farDistance; float distanceFactor = nearDistance / totalDistance; const QVector3D *revisedNearCutNormal = nullptr; const QVector3D *revisedFarCutNormal = nullptr; if (distanceFactor <= 0.5) { revisedNearCutNormal = &nearOriginNode.cutNormal; revisedFarCutNormal = &node.cutNormal; } else { distanceFactor = (1.0 - distanceFactor); revisedNearCutNormal = &farOriginNode.cutNormal; revisedFarCutNormal = &node.cutNormal; } distanceFactor *= 1.75; if (QVector3D::dotProduct(*revisedNearCutNormal, *revisedFarCutNormal) <= 0) cutNormal = (*revisedNearCutNormal * (1.0 - distanceFactor) - *revisedFarCutNormal * distanceFactor).normalized(); else cutNormal = (*revisedNearCutNormal * (1.0 - distanceFactor) + *revisedFarCutNormal * distanceFactor).normalized(); if (QVector3D::dotProduct(cutNormal, node.cutNormal) <= 0) cutNormal = -cutNormal; } } std::vector cut; bool cutFlipped = false; makeCut(node.position, node.radius, node.cutTemplate, node.cutRotation, node.baseNormal, cutNormal, node.traverseDirection, cut, &node.cutFaceTransform, &cutFlipped); node.hasAdjustableCutFace = true; std::vector vertices; insertCutVertices(cut, vertices, nodeIndex, cutNormal, cutFlipped); std::vector verticesReversed; verticesReversed = vertices; std::reverse(verticesReversed.begin(), verticesReversed.end()); m_edges[node.edges[0]].cuts.push_back({vertices, -cutNormal}); m_edges[node.edges[1]].cuts.push_back({verticesReversed, cutNormal}); } else if (neighborsCount >= 3) { std::vector offsets(node.edges.size(), 0.0); bool offsetChanged = false; size_t tries = 0; do { offsetChanged = false; //qDebug() << "Try wrap #" << tries; if (tryWrapMultipleBranchesForNode(nodeIndex, offsets, offsetChanged)) { //qDebug() << "Wrap succeed"; return true; } ++tries; } while (offsetChanged); return false; } return true; } bool StrokeMeshBuilder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector &offsets, bool &offsetsChanged) { auto backupVertices = m_generatedVertices; auto backupFaces = m_generatedFaces; auto backupSourceIndices = m_generatedVerticesSourceNodeIndices; auto backupVerticesCutDirects = m_generatedVerticesCutDirects; auto backupVerticesInfos = m_generatedVerticesInfos; auto &node = m_nodes[nodeIndex]; std::vector, QVector3D>> cutsForWrapping; std::vector, QVector3D>> cutsForEdges; bool directSwallowed = false; for (size_t i = 0; i < node.edges.size(); ++i) { QVector3D cutNormal = node.raysToNeibors[i]; size_t edgeIndex = node.edges[i]; size_t neighborIndex = m_edges[edgeIndex].neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; if (neighbor.edges.size() == 2) { size_t anotherEdgeIndex = neighbor.anotherEdge(edgeIndex); size_t neighborsNeighborIndex = m_edges[anotherEdgeIndex].neiborOf(neighborIndex); const auto &neighborsNeighbor = m_nodes[neighborsNeighborIndex]; cutNormal = ((cutNormal + (neighborsNeighbor.position - neighbor.position).normalized()) * 0.5).normalized(); } float distance = (neighbor.position - node.position).length(); if (qFuzzyIsNull(distance)) distance = 0.0001f; float radiusRate = neighbor.radius / node.radius; float tangentTriangleLongEdgeLength = distance + (radiusRate * distance / (1.0 - radiusRate)); float segmentLength = node.radius * (tangentTriangleLongEdgeLength - node.radius) / tangentTriangleLongEdgeLength; float radius = segmentLength / std::sin(std::acos(node.radius / tangentTriangleLongEdgeLength)); std::vector cut; float offsetDistance = 0; offsetDistance = offsets[i] * (distance - node.radius - neighbor.radius); if (offsetDistance < 0) offsetDistance = 0; float finalDistance = node.radius + offsetDistance; if (finalDistance >= distance) { if (swallowEdgeForNode(nodeIndex, i)) { //qDebug() << "Neighbor too near to wrap, swallow it"; offsets[i] = 0; offsetsChanged = true; directSwallowed = true; continue; } } bool cutFlipped = false; makeCut(node.position + cutNormal * finalDistance, radius, node.cutTemplate, node.cutRotation, node.baseNormal, cutNormal, neighbor.traverseDirection, cut, nullptr, &cutFlipped); std::vector vertices; insertCutVertices(cut, vertices, nodeIndex, cutNormal, cutFlipped); cutsForEdges.push_back({vertices, -cutNormal}); std::vector verticesReversed; verticesReversed = vertices; std::reverse(verticesReversed.begin(), verticesReversed.end()); cutsForWrapping.push_back({verticesReversed, cutNormal}); } if (directSwallowed) { m_generatedVertices = backupVertices; m_generatedFaces = backupFaces; m_generatedVerticesSourceNodeIndices = backupSourceIndices; m_generatedVerticesCutDirects = backupVerticesCutDirects; m_generatedVerticesInfos = backupVerticesInfos; return false; } MeshStitcher stitcher; stitcher.setVertices(&m_generatedVertices); std::vector failedEdgeLoops; bool stitchSucceed = stitcher.stitch(cutsForWrapping); std::vector> testFaces = stitcher.newlyGeneratedFaces(); for (const auto &cuts: cutsForWrapping) { testFaces.push_back(cuts.first); } if (stitchSucceed) { stitchSucceed = isManifold(testFaces); if (!stitchSucceed) { //qDebug() << "Mesh stitch but not manifold"; } } if (stitchSucceed) { MeshCombiner::Mesh mesh(m_generatedVertices, testFaces, false); if (mesh.isNull()) { stitchSucceed = false; for (size_t i = 0; i < node.edges.size(); ++i) { failedEdgeLoops.push_back(i); } } } else { stitcher.getFailedEdgeLoops(failedEdgeLoops); } if (!stitchSucceed) { for (const auto &edgeLoop: failedEdgeLoops) { if (offsets[edgeLoop] + WRAP_STEP_BACK_FACTOR < 1.0) { offsets[edgeLoop] += WRAP_STEP_BACK_FACTOR; offsetsChanged = true; } } if (!offsetsChanged) { for (const auto &edgeLoop: failedEdgeLoops) { if (offsets[edgeLoop] + WRAP_STEP_BACK_FACTOR >= 1.0) { if (swallowEdgeForNode(nodeIndex, edgeLoop)) { //qDebug() << "No offset to step back, swallow neighbor instead"; offsets[edgeLoop] = 0; offsetsChanged = true; break; } } } } m_generatedVertices = backupVertices; m_generatedFaces = backupFaces; m_generatedVerticesSourceNodeIndices = backupSourceIndices; m_generatedVerticesCutDirects = backupVerticesCutDirects; m_generatedVerticesInfos = backupVerticesInfos; return false; } // Weld nearby vertices float weldThreshold = node.radius * WRAP_WELD_FACTOR; float allowedMinDist2 = weldThreshold * weldThreshold; for (size_t i = 0; i < node.edges.size(); ++i) { for (size_t j = i + 1; j < node.edges.size(); ++j) { const auto &first = cutsForEdges[i]; const auto &second = cutsForEdges[j]; for (const auto &u: first.first) { for (const auto &v: second.first) { if ((m_generatedVertices[u] - m_generatedVertices[v]).lengthSquared() < allowedMinDist2) { //qDebug() << "Weld" << v << "to" << u; m_weldMap.insert({v, u}); } } } } } for (const auto &face: stitcher.newlyGeneratedFaces()) { m_generatedFaces.push_back(face); } for (size_t i = 0; i < node.edges.size(); ++i) { m_edges[node.edges[i]].cuts.push_back(cutsForEdges[i]); } return true; } bool StrokeMeshBuilder::swallowEdgeForNode(size_t nodeIndex, size_t edgeOrder) { auto &node = m_nodes[nodeIndex]; size_t edgeIndex = node.edges[edgeOrder]; if (m_swallowedEdges.find(edgeIndex) != m_swallowedEdges.end()) { //qDebug() << "No more edge to swallow"; return false; } size_t neighborIndex = m_edges[edgeIndex].neiborOf(nodeIndex); const auto &neighbor = m_nodes[neighborIndex]; if (neighbor.edges.size() != 2) { //qDebug() << "Neighbor is not a simple two edges node to swallow, edges:" << neighbor.edges.size() << "neighbor:" << neighborIndex << "node" << nodeIndex; return false; } size_t anotherEdgeIndex = neighbor.anotherEdge(edgeIndex); if (m_swallowedEdges.find(anotherEdgeIndex) != m_swallowedEdges.end()) { //qDebug() << "Desired edge already been swallowed"; return false; } node.edges[edgeOrder] = anotherEdgeIndex; //qDebug() << "Nodes of edge" << anotherEdgeIndex << "before update:"; //for (const auto &it: m_edges[anotherEdgeIndex].nodes) // qDebug() << it; m_edges[anotherEdgeIndex].updateNodeIndex(neighborIndex, nodeIndex); //qDebug() << "Nodes of edge" << anotherEdgeIndex << "after update:"; //for (const auto &it: m_edges[anotherEdgeIndex].nodes) // qDebug() << it; m_swallowedEdges.insert(edgeIndex); m_swallowedNodes.insert(neighborIndex); //qDebug() << "Swallow edge" << edgeIndex << "for node" << nodeIndex << "neighbor" << neighborIndex << "got eliminated, choosen edge" << anotherEdgeIndex; return true; } void StrokeMeshBuilder::unifyBaseNormals() { std::vector nodeIndices(m_nodes.size()); for (size_t i = 0; i < m_nodes.size(); ++i) { const auto &node = m_nodes[i]; nodeIndices[node.reversedTraverseOrder] = i; } for (size_t i = 1; i < nodeIndices.size(); ++i) { size_t lastIndex = nodeIndices[i - 1]; size_t nodeIndex = nodeIndices[i]; auto &node = m_nodes[nodeIndex]; const auto &last = m_nodes[lastIndex]; if (QVector3D::dotProduct(node.baseNormal, last.baseNormal) <= 0) node.baseNormal = -node.baseNormal; } } QVector3D StrokeMeshBuilder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNormal, const QVector3D &cutNormal) { QVector3D orientedBaseNormal = QVector3D::dotProduct(cutNormal, baseNormal) > 0 ? baseNormal : -baseNormal; // 0.966: < 15 degress if (QVector3D::dotProduct(cutNormal, orientedBaseNormal) > 0.966) { orientedBaseNormal = calculateBaseNormalFromTraverseDirection(cutNormal); } return orientedBaseNormal.normalized(); } void StrokeMeshBuilder::makeCut(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation, QVector3D &baseNormal, QVector3D &cutNormal, const QVector3D &traverseDirection, std::vector &resultCut, CutFaceTransform *cutFaceTransform, bool *cutFlipped) { auto finalCutTemplate = cutTemplate; float degree = 0; if (!qFuzzyIsNull(cutRotation)) { degree = cutRotation * 180; } if (QVector3D::dotProduct(cutNormal, traverseDirection) <= 0) { cutNormal = -cutNormal; std::reverse(finalCutTemplate.begin(), finalCutTemplate.end()); std::rotate(finalCutTemplate.begin(), finalCutTemplate.begin() + finalCutTemplate.size() - 1, finalCutTemplate.end()); if (nullptr != cutFaceTransform) cutFaceTransform->reverse = true; if (nullptr != cutFlipped) *cutFlipped = true; } else { if (nullptr != cutFlipped) *cutFlipped = false; } QVector3D u = QVector3D::crossProduct(cutNormal, baseNormal).normalized(); QVector3D v = QVector3D::crossProduct(u, cutNormal).normalized(); auto uFactor = u * radius; auto vFactor = v * radius; if (nullptr != cutFaceTransform) { cutFaceTransform->scale = radius; cutFaceTransform->translation = position; cutFaceTransform->uFactor = uFactor; cutFaceTransform->vFactor = vFactor; } for (const auto &t: finalCutTemplate) { resultCut.push_back(uFactor * t.x() + vFactor * t.y()); } if (!qFuzzyIsNull(degree)) { QMatrix4x4 rotation; rotation.rotate(degree, cutNormal); baseNormal = rotation * baseNormal; for (auto &positionOnCut: resultCut) { positionOnCut = rotation * positionOnCut; } if (nullptr != cutFaceTransform) cutFaceTransform->rotation = rotation; } for (auto &positionOnCut: resultCut) { positionOnCut += position; } } void StrokeMeshBuilder::stitchEdgeCuts() { for (size_t edgeIndex = 0; edgeIndex < m_edges.size(); ++edgeIndex) { auto &edge = m_edges[edgeIndex]; if (2 == edge.cuts.size()) { MeshStitcher stitcher; stitcher.setVertices(&m_generatedVertices); stitcher.stitch(edge.cuts); for (const auto &face: stitcher.newlyGeneratedFaces()) { m_generatedFaces.push_back(face); } } } } void StrokeMeshBuilder::applyWeld() { if (m_weldMap.empty()) return; std::vector newVertices; std::vector newSourceIndices; std::vector> newFaces; std::vector newVerticesCutDirects; std::vector newVerticesInfos; std::map oldVertexToNewMap; for (const auto &face: m_generatedFaces) { std::vector newIndices; std::set inserted; for (const auto &oldVertex: face) { size_t useOldVertex = oldVertex; size_t newIndex = 0; auto findWeld = m_weldMap.find(useOldVertex); if (findWeld != m_weldMap.end()) { useOldVertex = findWeld->second; } auto findResult = oldVertexToNewMap.find(useOldVertex); if (findResult == oldVertexToNewMap.end()) { newIndex = newVertices.size(); oldVertexToNewMap.insert({useOldVertex, newIndex}); newVertices.push_back(m_generatedVertices[useOldVertex]); newSourceIndices.push_back(m_generatedVerticesSourceNodeIndices[useOldVertex]); newVerticesCutDirects.push_back(m_generatedVerticesCutDirects[useOldVertex]); newVerticesInfos.push_back(m_generatedVerticesInfos[useOldVertex]); } else { newIndex = findResult->second; } if (inserted.find(newIndex) != inserted.end()) continue; inserted.insert(newIndex); newIndices.push_back(newIndex); } if (newIndices.size() < 3) { //qDebug() << "Face been welded"; continue; } newFaces.push_back(newIndices); } m_generatedVertices = newVertices; m_generatedFaces = newFaces; m_generatedVerticesSourceNodeIndices = newSourceIndices; m_generatedVerticesCutDirects = newVerticesCutDirects; m_generatedVerticesInfos = newVerticesInfos; } void StrokeMeshBuilder::setDeformThickness(float thickness) { m_deformThickness = thickness; } void StrokeMeshBuilder::setDeformWidth(float width) { m_deformWidth = width; } void StrokeMeshBuilder::setDeformMapImage(const QImage *image) { m_deformMapImage = image; } void StrokeMeshBuilder::setHollowThickness(float hollowThickness) { m_hollowThickness = hollowThickness; } void StrokeMeshBuilder::setDeformMapScale(float scale) { m_deformMapScale = scale; } QVector3D StrokeMeshBuilder::calculateDeformPosition(const QVector3D &vertexPosition, const QVector3D &ray, const QVector3D &deformNormal, float deformFactor) { QVector3D revisedNormal = QVector3D::dotProduct(ray, deformNormal) < 0.0 ? -deformNormal : deformNormal; QVector3D projectRayOnRevisedNormal = revisedNormal * (QVector3D::dotProduct(ray, revisedNormal) / revisedNormal.lengthSquared()); auto scaledProjct = projectRayOnRevisedNormal * deformFactor; return vertexPosition + (scaledProjct - projectRayOnRevisedNormal); } void StrokeMeshBuilder::finalizeHollow() { if (qFuzzyIsNull(m_hollowThickness)) return; 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); } for (const auto &cut: m_endCuts) { for (size_t i = 0; i < cut.size(); ++i) { size_t j = (i + 1) % cut.size(); std::vector 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); } } } void StrokeMeshBuilder::applyDeform() { 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; if (nullptr != m_deformMapImage) { float degrees = angleInRangle360BetweenTwoVectors(node.baseNormal, ray.normalized(), node.traverseDirection); int x = node.reversedTraverseOrder * m_deformMapImage->width() / m_nodes.size(); int y = degrees * m_deformMapImage->height() / 360.0; if (y >= m_deformMapImage->height()) y = m_deformMapImage->height() - 1; float gray = (float)(qGray(m_deformMapImage->pixelColor(x, y).rgb()) - 127) / 127; position += m_deformMapScale * gray * ray; ray = position - node.position; } QVector3D sum; size_t count = 0; if (!qFuzzyCompare(m_deformThickness, (float)1.0)) { auto deformedPosition = calculateDeformPosition(position, ray, node.baseNormal, m_deformThickness); sum += deformedPosition; ++count; } if (!qFuzzyCompare(m_deformWidth, (float)1.0)) { auto deformedPosition = calculateDeformPosition(position, ray, QVector3D::crossProduct(node.baseNormal, cutDirect), m_deformWidth); sum += deformedPosition; ++count; } if (count > 0) position = sum / count; } } const QVector3D &StrokeMeshBuilder::nodeTraverseDirection(size_t nodeIndex) const { return m_nodes[nodeIndex].traverseDirection; } const QVector3D &StrokeMeshBuilder::nodeBaseNormal(size_t nodeIndex) const { return m_nodes[nodeIndex].baseNormal; } size_t StrokeMeshBuilder::nodeTraverseOrder(size_t nodeIndex) const { return m_nodes[nodeIndex].reversedTraverseOrder; } float radianToDegree(float r) { return r * 180.0 / M_PI; }