271 lines
9.4 KiB
C++
271 lines
9.4 KiB
C++
#include <QVector2D>
|
|
#include <QDebug>
|
|
#include <unordered_map>
|
|
#include "strokemodifier.h"
|
|
#include "util.h"
|
|
#include "centripetalcatmullromspline.h"
|
|
|
|
void StrokeModifier::enableIntermediateAddition()
|
|
{
|
|
m_intermediateAdditionEnabled = true;
|
|
}
|
|
|
|
void StrokeModifier::enableSmooth()
|
|
{
|
|
m_smooth = true;
|
|
}
|
|
|
|
size_t StrokeModifier::addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &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) {
|
|
subdivideFace2D(&node.cutTemplate);
|
|
}
|
|
}
|
|
|
|
float StrokeModifier::averageCutTemplateEdgeLength(const std::vector<QVector2D> &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 ¤tNode = 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<QVector2D> &cutTemplate, float averageCutTemplateLength)
|
|
{
|
|
std::vector<QVector2D> 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];
|
|
//float edgeLengthThreshold = (firstNode.radius + secondNode.radius) * 0.75;
|
|
auto firstAverageCutTemplateEdgeLength = firstNode.averageCutTemplateLength * firstNode.radius;
|
|
auto secondAverageCutTemplateEdgeLength = secondNode.averageCutTemplateLength * secondNode.radius;
|
|
float targetEdgeLength = (firstAverageCutTemplateEdgeLength + secondAverageCutTemplateEdgeLength) * 0.5;
|
|
//if (targetEdgeLength < edgeLengthThreshold)
|
|
// targetEdgeLength = edgeLengthThreshold;
|
|
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;
|
|
}
|
|
|