https://github.com/ialhashim/topo-blend
Revision 39b13612ebd645a65eda854771b517371f2f858a authored by ennetws on 13 March 2015, 18:17:18 UTC, committed by ennetws on 13 March 2015, 18:17:18 UTC
1 parent c702819
Tip revision: 39b13612ebd645a65eda854771b517371f2f858a authored by ennetws on 13 March 2015, 18:17:18 UTC
Create README.md
Create README.md
Tip revision: 39b1361
TaskCurve.cpp
#include "TaskCurve.h"
#include "AbsoluteOrientation.h"
using namespace Structure;
Curve * TaskCurve::targetCurve()
{
Node* n = targetNode();
if(!n || n->type() != CURVE) return NULL;
return (Curve *)n;
}
void TaskCurve::prepareCurve()
{
switch(type)
{
case GROW:
prepareGrowCurve();
break;
case SHRINK:
prepareShrinkCurve();
break;
case SPLIT:
case MERGE:
case MORPH:
prepareMorphCurve();
break;
}
}
void TaskCurve::prepareShrinkCurveOneEdge( Link* l )
{
Node * n = node();
Curve* structure_curve = ((Curve*)n);
Node * base = l->otherNode(n->id);
Vector4d coordBase = l->getCoord(base->id).front();
Vector4d coordSelf = l->getCoord(n->id).front();
Vector3 linkPositionBase = l->position( base->id );
// Curve folding
Array1D_Vector3 deltas = structure_curve->foldTo( coordSelf, false );
deltas = inverseVectors3(deltas);
// Growing / shrinking instructions
property["deltas"].setValue( deltas );
property["orgCtrlPoints"].setValue( structure_curve->curve.mCtrlPoint );
}
void TaskCurve::prepareShrinkCurve()
{
Node *n = node();
QVector<Link*> edges = filterEdges( n, active->getEdges(n->id) );
if(!edges.size()) return; // Something went wrong
Curve* curve = ((Curve*)n);
// Save edges used
property["edges"].setValue( active->getEdgeIDs(edges) );
if(edges.size() == 1)
{
prepareShrinkCurveOneEdge( edges.front() );
}
//Cut node case
else if( isCutting() )
{
prepareShrinkCurveOneEdge( preferredEnd(n, edges, active) );
}
else if(edges.size() == 2)
{
// Links and positions (on myself)
Link * linkA = edges.front();
Link * linkB = edges.back();
Vector3d pointA = linkA->position( n->id );
Vector3d pointB = linkB->position( n->id );
// Geodesic distance between two link positions on the active graph excluding the running tasks and ungrown tasks
QVector< GraphDistance::PathPointPair > path;
QVector<QString> exclude = active->property["activeTasks"].value< QVector<QString> >();
foreach(Node* node, active->nodes){ if (ungrownNode(node->id)) exclude.push_back(node->id);}
GraphDistance gd( active, exclude );
gd.computeDistances( pointA, DIST_RESOLUTION );
gd.smoothPathCoordTo( pointB, path);
if(path.size() == 0) return;
// Use the center of the path as the end point
GraphDistance::PathPointPair endPointCoord = path[path.size() / 2];
Vector3d endPoint = endPointCoord.position(active);
// Separate the path into two for linkA and linkB
// Need old number of positions to shrink to null
int N = path.size(), hN = N / 2;
if (N % 2 == 0) path.insert(hN, path[hN]);
N = path.size();
QVector< GraphDistance::PathPointPair > pathA, pathB;
for (int i = 0; i <= N/2; i++)
{
pathA.push_back( path[N-1-i] );
pathB.push_back( path[i] );
}
property["pathA"].setValue( GraphDistance::positionalPath(active,pathA) );
property["pathB"].setValue( GraphDistance::positionalPath(active,pathB) );
// Encode curve
property["cpCoords"].setValue( Curve::encodeCurve(curve, linkA->position(n->id), linkB->position(n->id)) );
linkA->setProperty("path", pathA);
linkB->setProperty("path", pathB);
// Visualization
n->property["path"].setValue( GraphDistance::positionalPath(active, pathA) );
n->property["path2"].setValue( GraphDistance::positionalPath(active, pathB) );
}
}
void TaskCurve::prepareGrowCurveOneEdge( Structure::Link * tlink )
{
Node *tn = targetNode();
Structure::Curve tcurve (*((Structure::Curve *)tn));
// Get base
Node * tbase = tlink->otherNode(tn->id);
QString baseID = tbase->property["correspond"].toString();
Node* base = active->getNode(baseID);
// Get coordinates on me and my base
Vector4d coordBase = tlink->getCoord(tbase->id).front();
Vector4d coordSelf = tlink->getCoord(tn->id).front();
/// Place curve:
int cpIDX = tcurve.controlPointIndexFromCoord( coordSelf );
// Make origin the position on me in which I will grow from
tcurve.moveBy( -tcurve.controlPoints()[cpIDX] + base->position( coordBase ) );
// Curve folding
Array1D_Vector3 deltas = tcurve.foldTo( coordSelf, true );
// Growing instructions
property["deltas"].setValue( deltas );
property["orgCtrlPoints"].setValue( tcurve.curve.mCtrlPoint );
// Force blended delta
Structure::Link * slink = active->getEdge(tlink->property["correspond"].toInt());
slink->property["blendedDelta"].setValue( tlink->delta() );
property["isGrowSingleEdge"] = true;
}
void TaskCurve::prepareGrowCurve()
{
Node *n = node(), *tn = targetNode();
Curve * curve = (Curve *)n;
Curve * tcurve = (Curve *)tn;
// Find edges to be used in growing
QVector<Link*> tedges;
QVector<Link*> edges = filteredFromTargetEdges();
foreach(Link* edge, edges) tedges.push_back(target->getEdge( edge->property["correspond"].toInt() ));
// Save the edges used
property["edges"].setValue( active->getEdgeIDs(edges) );
if (edges.size() == 1)
{
prepareGrowCurveOneEdge( tedges.front() );
return;
}
if (edges.size() && (isCutting(true) || target->isCutNode(tn->id)))
{
prepareGrowCurveOneEdge( tedges.front() );
return;
}
if (edges.size() > 1)
{
// Make sure the edges are reasonable for curve encoding
QMap< double, QPair<int,int> > edgePairs;
for(int i = 0; i < tedges.size(); i++){
Link *tlinkA = tedges[i];
for(int j = i + 1; j < tedges.size(); j++){
Link *tlinkB = tedges[j];
double dist = (tlinkA->position(tn->id) - tlinkB->position(tn->id)).norm();
edgePairs[dist] = qMakePair(i,j);
}
}
// Prefer most distant edges
double largestDist = edgePairs.keys().last();
if( largestDist < 1e-6 ){
prepareGrowCurveOneEdge( tedges.front() );
return;
}
// Links, nodes and positions on the TARGET
Link *tlinkA = tedges[ edgePairs[largestDist].first ];
Link *tlinkB = tedges[ edgePairs[largestDist].second ];
Node *totherA = tlinkA->otherNode( tn->id );
Node *totherB = tlinkB->otherNode( tn->id );
Vector4d othercoordA = tlinkA->getCoord(totherA->id).front();
Vector4d othercoordB = tlinkB->getCoord(totherB->id).front();
// Corresponding stuff on ACTIVE
Link *linkA = active->getEdge( tlinkA->property["correspond"].toInt() );
Link *linkB = active->getEdge( tlinkB->property["correspond"].toInt() );
if(!linkA || !linkB) return;
Node *otherA = active->getNode( totherA->property["correspond"].toString() );
Node *otherB = active->getNode( totherB->property["correspond"].toString() );
Vector3d pointA = otherA->position(othercoordA);
Vector3d pointB = otherB->position(othercoordB);
// Geodesic distance between two link positions on the active graph excluding the running tasks
QVector<QString> excludeNodes = active->property["activeTasks"].value< QVector<QString> >();
GraphDistance gd( active, excludeNodes );
gd.computeDistances( pointA, DIST_RESOLUTION );
QVector< GraphDistance::PathPointPair > path;
NodeCoord rpoint( otherB->id, othercoordB );
gd.smoothPathCoordTo( rpoint, path );
// Very short paths
if(path.size() < 4){
prepareGrowCurveOneEdge( tedges.front() );
return;
}
// Use the center of the path as the start point
GraphDistance::PathPointPair startPointCoord = path[path.size() / 2];
Vector3d startPoint = startPointCoord.position( active );
// Separate the path into two for linkA and linkB
int N = path.size(), hN = N / 2;
if (N % 2 == 0) path.insert(hN, path[hN]);
QVector<GraphDistance::PathPointPair> pathA, pathB;
for (int i = 0; i < hN; i++)
{
pathA.push_back(path[hN+1+i]);
pathB.push_back(path[hN-1-i]);
}
property["pathA"].setValue( GraphDistance::positionalPath(active,pathA) );
property["pathB"].setValue( GraphDistance::positionalPath(active,pathB) );
QVector<Link*> edges;
edges.push_back( active->getEdge( tlinkA->property["correspond"].toInt() ) );
edges.push_back( active->getEdge( tlinkB->property["correspond"].toInt() ) );
property["edges"].setValue( active->getEdgeIDs(edges) );
// Encode curve
property["cpCoords"].setValue( Structure::Curve::encodeCurve(tcurve, tlinkA->position(tn->id), tlinkB->position(tn->id)) );
// Initial position of curve node
Vector4d midCoord(0.5,0.5,0.5,0.5);
curve->foldTo(midCoord, true);
curve->curve.translate(startPoint - curve->position(midCoord));
linkA->setProperty("path", pathA);
linkB->setProperty("path", pathB);
// Visualization
n->property["path"].setValue( GraphDistance::positionalPath(active, pathA) );
n->property["path2"].setValue( GraphDistance::positionalPath(active, pathB) );
return;
}
}
void TaskCurve::encodeCurve( const Vector4d& coordinateA, const Vector4d& coordinateB )
{
Node * n = node(), *tn = targetNode();
//// Parameters needed for morphing
Vector3 sourceA = n->position(coordinateA), sourceB = n->position(coordinateB);
Vector3 targetA = tn->position(coordinateA), targetB = tn->position(coordinateB);
// Case: looped curves
{
Vector4d mid( 0.5, 0.5, 0.5, 0.5 );
Vector3 sourceMid = n->position(mid), targetMid = tn->position(mid);
// Source
{
double distBA = (sourceB - sourceA).norm(), distAM = (sourceA - sourceMid).norm(), distBM = (sourceB - sourceMid).norm();
if( distBA < qMax(distAM, distBM) )
distAM < distBM ? sourceA = sourceMid : sourceB = sourceMid;
}
// Target
{
double distBA = (targetB - targetA).norm(), distAM = (targetA - targetMid).norm(), distBM = (targetB - targetMid).norm();
if( distBA < qMax(distAM, distBM) )
distAM < distBM ? targetA = targetMid : targetB = targetMid;
}
}
// Two ends
property["sourceA"].setValue( sourceA ); property["sourceB"].setValue( sourceB );
property["targetA"].setValue( targetA ); property["targetB"].setValue( targetB );
// Encoding
property["cpCoords"].setValue( Structure::Curve::encodeCurve((Curve*)n, sourceA, sourceB) );
property["cpCoordsT"].setValue( Structure::Curve::encodeCurve((Curve*)tn, targetA, targetB) );
}
void TaskCurve::prepareMorphCurve()
{
encodeCurve( Vector4d(0,0,0,0), Vector4d(1,1,1,1) );
// Check crossing
if ( (isReady = true) && isCrossing() )
prepareCrossingMorphCurve();
else
prepareMorphEdges();
}
void TaskCurve::prepareCrossingMorphCurve()
{
Node * n = node();
QVector<Link*> edges = filterEdges(n, active->getEdges(n->id));
// Case: filter out edges that will change end to non-grown nodes
if( true )
{
QVector<Link*> keep;
foreach(Link* l, edges){
NodeCoord futureNodeCord = futureLinkCoord(l);
if(!ungrownNode(futureNodeCord.first))
keep.push_back(l);
}
if(keep.size()) edges = keep;
}
if( edges.isEmpty() )
{
edges.push_back( active->getEdges(n->id).front() );
}
if (edges.size() == 1)
{
// Start and end
Link * link = edges.front();
Vector3d start = link->positionOther(n->id);
NodeCoord futureNodeCord = futureLinkCoord(link);
Vector3d end = active->position(futureNodeCord.first,futureNodeCord.second);
// Geodesic distances on the active graph excluding the running tasks
QVector< GraphDistance::PathPointPair > path;
QVector<QString> exclude = active->property["activeTasks"].value< QVector<QString> >();
// Exclude un-grown cutting
{
foreach(Node * n, active->nodes)
{
if( futureNodeCord.first != n->id && ungrownNode(n->id) )
{
bool isCuttingNode = n->property["task"].value<Task*>()->isCutting(true);
if( isCuttingNode )
continue;
else
exclude.push_back( n->id );
}
}
}
GraphDistance gd( active, exclude );
gd.computeDistances( end, DIST_RESOLUTION );
gd.smoothPathCoordTo( start, path );
// Check
if(path.size() < 2) { path.clear(); path.push_back(GraphDistance::PathPointPair( PathPoint(futureNodeCord.first, futureNodeCord.second))); }
property["path"].setValue( GraphDistance::positionalPath(active, path) );
// Save links paths
path.back() = GraphDistance::PathPointPair( PathPoint(futureNodeCord.first, futureNodeCord.second) );
link->setProperty("path", path);
property["isSingleCrossing"] = true;
}
if( edges.size() == 2 )
{
// Start and end for both links
Link * linkA = edges.front();
Link * linkB = edges.back();
NodeCoord futureNodeCordA = futureLinkCoord(linkA);
NodeCoord futureNodeCordB = futureLinkCoord(linkB);
Vector3d startA = linkA->positionOther(n->id);
Vector3d endA = active->position(futureNodeCordA.first,futureNodeCordA.second);
Vector3d startB = linkB->positionOther(n->id);
Vector3d endB = active->position(futureNodeCordB.first,futureNodeCordB.second);
// Geodesic distances on the active graph excluding the running tasks
QVector< GraphDistance::PathPointPair > pathA, pathB;
QVector<QString> exclude = active->property["activeTasks"].value< QVector<QString> >();
GraphDistance gdA( active, exclude ), gdB( active, exclude );
gdA.computeDistances( endA, DIST_RESOLUTION );
gdA.smoothPathCoordTo( startA, pathA );
gdB.computeDistances( endB, DIST_RESOLUTION );
gdB.smoothPathCoordTo( startB, pathB );
// Checks
if(pathA.size() < 2) { pathA.clear(); pathA.push_back(GraphDistance::PathPointPair( PathPoint(futureNodeCordA.first, futureNodeCordA.second))); }
if(pathB.size() < 2) { pathB.clear(); pathB.push_back(GraphDistance::PathPointPair( PathPoint(futureNodeCordB.first, futureNodeCordB.second))); }
property["pathA"].setValue( GraphDistance::positionalPath(active, pathA) );
property["pathB"].setValue( GraphDistance::positionalPath(active, pathB) );
// Make sure we end correctly
pathA.back() = GraphDistance::PathPointPair( PathPoint(futureNodeCordA.first, futureNodeCordA.second) );
pathB.back() = GraphDistance::PathPointPair( PathPoint(futureNodeCordB.first, futureNodeCordB.second) );
// Save links paths
linkA->setProperty("path", pathA);
linkB->setProperty("path", pathB);
// Encode with these two links
encodeCurve(linkA->getCoord(n->id).front(), linkB->getCoord(n->id).front());
}
property["edges"].setValue( active->getEdgeIDs(edges) );
}
void TaskCurve::executeCurve(double t)
{
switch(type)
{
case GROW:
case SHRINK:
{
if ( property.contains("deltas") ) foldCurve(t);
else executeCrossingCurve(t);
}
break;
case MORPH:
{
if( property["isCrossing"].toBool())
executeCrossingCurve(t);
else
executeMorphCurve(t);
}
break;
}
}
void TaskCurve::foldCurve( double t )
{
Node * n = node();
Curve* structure_curve = ((Curve*)n);
// Grow curve
Array1D_Vector3 cpts = property["orgCtrlPoints"].value<Array1D_Vector3>();
Array1D_Vector3 deltas = property["deltas"].value<Array1D_Vector3>();
if(cpts.size() != deltas.size()) return;
for(int u = 0; u < structure_curve->curve.mNumCtrlPoints; u++)
structure_curve->curve.mCtrlPoint[u] = cpts[u] + (deltas[u] * t);
// Placement
QVector<Link*> edges = active->getEdges( property["edges"].value< QVector<int> >() );
// Only valid edges
QVector<Link*> keep;
foreach(Link* l, edges) if(l->hasNode(n->id)) keep.push_back(l);
edges = keep;
// Something went wrong..
if( edges.isEmpty() ){
edges = active->getEdges(n->id);
if( edges.isEmpty() ){
QString tnodeID = n->property["correspond"].toString();
Node * selectedOther = NULL;
Link * selectedTargetEdge = NULL;
foreach( Link * tlink, target->getEdges(tnodeID) ){
Node * other = active->getNode(target->getNode(tlink->otherNode(tnodeID)->id)->property["correspond"].toString());
if(!other || other->property["taskIsDone"].toBool())
{
selectedOther = other;
selectedTargetEdge = tlink;
}
}
edges.push_back( active->addEdge(n, selectedOther, selectedTargetEdge->getCoord(tnodeID), selectedTargetEdge->getCoordOther(tnodeID) ) );
}
}
Link * l = edges.front();
Vector3 posOnMe = l->position(n->id);
Vector3 posOnBase = l->positionOther(n->id);
// Get delta
Vector3 delta = l->property["blendedDelta"].value<Vector3d>();
if(this->type == Task::GROW)
{
Link * tl = target->getEdge( l->property["correspond"].toInt() );
delta = tl->delta();
}
// Delta to me
if(l->n1->id == n->id) delta *= -1;
structure_curve->moveBy( posOnBase + delta - posOnMe );
}
void TaskCurve::executeCrossingCurve( double t )
{
Node *n = node();
QVector<Link*> edges = active->getEdges( property["edges"].value< QVector<int> >() );
if (property.contains("path"))
{
// Blend the geometry
executeMorphCurve(t);
// Move it to the correct position
Array1D_Vector3 path = property["path"].value< Array1D_Vector3 >();
int idx = t * (path.size() - 1);
Vector3 point = path[idx];
Link* link = edges.front();
Vector3 oldPos = link->position(n->id);
// Blend Deltas, directions are the same as source
Structure::Link *slink = edges.front();
Vector3d sDelta = slink->property["delta"].value<Vector3d>();
if (type == Task::GROW) sDelta = Vector3d(0,0,0);
Structure::Link* tlink = target->getEdge(slink->property["correspond"].toInt());
Vector3d tDelta = tlink->property["delta"].value<Vector3d>();
if (type == Task::SHRINK) tDelta = Vector3d(0,0,0);
Vector3d delta = AlphaBlend(t, sDelta, tDelta);
// Deltas to myself
if (slink->n1->id == n->id) delta *= -1;
Vector3 newPos = point + delta;
// Place
n->moveBy(newPos - oldPos);
}
// Walk along using two edges
if(property.contains("pathA") && property.contains("pathB"))
{
Array1D_Vector3 pathA = property["pathA"].value< Array1D_Vector3 >();
Array1D_Vector3 pathB = property["pathB"].value< Array1D_Vector3 >();
int nA = pathA.size(), nB = pathB.size();
if(nA == 0 || nB == 0 || edges.isEmpty()) return;
int idxA = t * (pathA.size() - 1);
int idxB = t * (pathB.size() - 1);
// Move to next step
Vector3 pointA = pathA[idxA];
Vector3 pointB = pathB[idxB];
// Decode
SheetEncoding cpCoords = property["cpCoords"].value<SheetEncoding>();
SheetEncoding cpCoordsT = property["cpCoordsT"].value<SheetEncoding>();
// Blend Deltas, directions are the same as source
Structure::Link *slinkA = edges.front(), *slinkB = edges.back();
Vector3d sDeltaA = slinkA->property["delta"].value<Vector3d>();
Vector3d sDeltaB = slinkB->property["delta"].value<Vector3d>();
if (type == Task::GROW) {sDeltaA = Vector3d(0,0,0); sDeltaB = Vector3d(0,0,0);}
Structure::Link* tlinkA = target->getEdge(slinkA->property["correspond"].toInt());
Structure::Link* tlinkB = target->getEdge(slinkB->property["correspond"].toInt());
Vector3d tDeltaA = tlinkA->property["delta"].value<Vector3d>();
Vector3d tDeltaB = tlinkB->property["delta"].value<Vector3d>();
if (type == Task::SHRINK) {tDeltaA = Vector3d(0,0,0); tDeltaB = Vector3d(0,0,0);}
Vector3d deltaA = AlphaBlend(t, sDeltaA, tDeltaA);
Vector3d deltaB = AlphaBlend(t, sDeltaB, tDeltaB);
// Deltas to myself
if (slinkA->n1->id == n->id) deltaA *= -1;
if (slinkB->n1->id == n->id) deltaB *= -1;
Array1D_Vector3 newPnts = Curve::decodeCurve(property["cpCoords"].value<CurveEncoding>(), pointA + deltaA, pointB + deltaB);
Array1D_Vector3 newPntsT = Curve::decodeCurve(property["cpCoordsT"].value<CurveEncoding>(), pointA + deltaA, pointB + deltaB);
if(!newPntsT.size()) newPntsT = newPnts;
Array1D_Vector3 blendedPnts;
for(int i = 0; i < (int)newPnts.size(); i++)
{
blendedPnts.push_back( AlphaBlend(t, newPnts[i], newPntsT[i]) );
}
n->setControlPoints( blendedPnts );
}
}
void TaskCurve::executeMorphCurve( double t )
{
Node *n = node();
// Blend controlling "line segment"
Vector3 pointA = AlphaBlend(t, property["sourceA"].value<Vector3>(), property["targetA"].value<Vector3>());
Vector3 pointB = AlphaBlend(t, property["sourceB"].value<Vector3>(), property["targetB"].value<Vector3>());
// Blend geometry of skeleton
CurveEncoding cpCoords = property["cpCoords"].value<CurveEncoding>();
CurveEncoding cpCoordsT = property["cpCoordsT"].value<CurveEncoding>();
Array1D_Vector3 newPnts = Curve::decodeCurve(property["cpCoords"].value<CurveEncoding>(), pointA, pointB);
Array1D_Vector3 newPntsT = Curve::decodeCurve(property["cpCoordsT"].value<CurveEncoding>(), pointA, pointB);
Array1D_Vector3 blendedPnts;
for(int i = 0; i < (int)newPnts.size(); i++)
blendedPnts.push_back( AlphaBlend(t, newPnts[i], newPntsT[i]) );
n->setControlPoints( blendedPnts );
}
Computing file changes ...