/* * Copyright (c) 2007-2010 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * This is a generic (graph-based) geometric constraint solver. * * Phase 1 - Recursive graph analysis: * This phase determines whether the problem is well constrained, and if so, * the sequence of construction steps to follow. * o We first look at the constraints between the nodes and generate a set * of graphs representing rigid clusters. * o If a ring of three clusters share exactly one node with each other, * the clusters can be brought into alignment and merged into a larger * cluster. If any other cluster is sharing two elements with the newly * generated cluster, they are also merged. * o We continue searching for rings of 3 constrained clusters and merging * them. If we end up with a single, rigid cluster, the problem is * well-constrained. * * Phase 2 - Construction phase: * We simply follow the order of cluster formation to place elements * in the same order. The position of an unknown element with respect * to two other constrained elements can be computed using systems of * equations (linear, linear-quadratic and quadratic). Where multiple * solutions are possible, we optimize for minimum displacement from * the original point. */ #include #include "sk.h" /* * Look for nodes in clOrig that share exactly two edges with cluster cl * and merge them into it (deleting them from clOrig). Since our elements * have two degrees of freedom, any element connected to a rigid cluster * by two constraints can be merged in that cluster. */ static void MergeConstrainedRings(SK *sk, SK_Cluster *clOrig, SK_Cluster *cl) { SK_Constraint *ct, *ctPair[2]; SK_Node *nUnknown, *nKnown[2]; Uint i, count; Debug(sk, "Solver: MergeConstrainedRings(Cluster%u)\n", (Uint)cl->name); restart: TAILQ_FOREACH(nUnknown, &sk->nodes, nodes) { if (nUnknown->flags & (SK_NODE_SUPCONSTRAINTS|SK_NODE_FIXED)) { continue; } count = 0; TAILQ_FOREACH(ct, &clOrig->edges, constraints) { if (ct->n1 == nUnknown && SK_NodeInCluster(ct->n2, cl)) { if (count < 2) { ctPair[count] = ct; nKnown[count] = ct->n2; } count++; } else if (ct->n2 == nUnknown && SK_NodeInCluster(ct->n1, cl)) { if (count < 2) { ctPair[count] = ct; nKnown[count] = ct->n1; } count++; } } if (count != 2) { continue; } SK_AddInsn(sk, SK_COMPOSE_RING, nUnknown, nKnown[0], nKnown[1], SK_DupConstraint(ctPair[0]), SK_DupConstraint(ctPair[1])); for (i = 0; i < 2; i++) { SK_AddConstraintCopy(cl, ctPair[i]); SK_DelConstraint(clOrig, ctPair[i]); } /* XXX is this needed? */ goto restart; } } /* * Look for any sketch cluster that shares two elements with the given * cluster, and merge them into a single cluster. * * cl must not be already attached to sk. */ static void MergeConstrainedClusters(SK *sk, SK_Cluster *clMerged) { SK_Cluster *cl; SK_Constraint *ct; Uint count; Debug(sk, "Solver: MergeConstrainedClusters(Cluster%u)\n", (Uint)clMerged->name); restart: TAILQ_FOREACH(cl, &sk->clusters, clusters) { count = 0; TAILQ_FOREACH(ct, &cl->edges, constraints) { if (SK_NodeInCluster(ct->n1, clMerged) || SK_NodeInCluster(ct->n2, clMerged)) count++; } if (count == 2) { Debug(sk, "Solver: Merging cluster%d into cluster%d (pair)\n", cl->name, clMerged->name); SK_CopyCluster(cl, clMerged); TAILQ_REMOVE(&sk->clusters, cl, clusters); SK_FreeCluster(cl); Free(cl); goto restart; /* Cluster chain changed */ } } } /* * Check if the sketch is well-constrained. If DOF analysis ended with a * single cluster, and all nodes within the cluster have two constraint * edges, the sketch is well-constrained. */ static void UpdateConstraintStatus(SK *sk) { Uint count = 0; SK_Cluster *cl; SK_Constraint *ct; SK_Node *node; /* Count the constraint edges per node in the original graph. */ TAILQ_FOREACH(node, &sk->nodes, nodes) { node->nEdges = 0; node->flags &= ~(SK_NODE_CHECKED); } TAILQ_FOREACH(cl, &sk->clusters, clusters) { count++; } TAILQ_FOREACH(ct, &sk->ctGraph.edges, constraints) { ct->n1->nEdges++; ct->n2->nEdges++; } /* Check the constrainedness status of all nodes. */ TAILQ_FOREACH(node, &sk->nodes, nodes) { if (node->flags & (SK_NODE_SUPCONSTRAINTS|SK_NODE_FIXED| SK_NODE_CHECKED)) { continue; } if (node->ops->constrained != NULL) { switch (node->ops->constrained(node)) { case SK_UNDER_CONSTRAINED: goto under; #if 0 case SK_OVER_CONSTRAINED: goto over; #endif default: break; } } SKNODE(node)->flags |= SK_NODE_CHECKED; } /* * The sketch is under-constrained if the cluster merging * phase fails to produce a single cluster. */ if (count > 1) { SK_SetStatus(sk, SK_UNDER_CONSTRAINED, _("Underconstrained (%u clusters)"), count); return; } SK_SetStatus(sk, SK_WELL_CONSTRAINED, _("Well-constrained")); return; #if 0 over: SK_SetStatus(sk, SK_OVER_CONSTRAINED, _("Overconstrained (%s)"), node->name); return; #endif under: SK_SetStatus(sk, SK_UNDER_CONSTRAINED, _("Underconstrained (%s)"), node->name); } /* * Analyze the constraint graph, determine its constrainedness and * generate a sketch placement program. */ int SK_Solve(SK *sk) { SK_Cluster clOrig; SK_Cluster *cl, *clRing[3], *clPair[2]; SK_Constraint *ct; SK_Node *node; Uint i, j, count, nRing; AG_MutexLock(&sk->lock); if (TAILQ_EMPTY(&sk->ctGraph.edges)) /* Nothing to do */ goto out; /* * First Phase: Degree of freedom analysis. */ /* Copy the source graph since we will modify its elements. */ SK_FreeClusters(sk); SK_FreeInsns(sk); SK_InitCluster(&clOrig, 1); SK_CopyCluster(&sk->ctGraph, &clOrig); while (!TAILQ_EMPTY(&clOrig.edges)) { cl = Malloc(sizeof(SK_Cluster)); SK_InitCluster(cl, SK_GenClusterName(sk)); /* Start with any edge and find n2 from n1. */ ct = TAILQ_FIRST(&clOrig.edges); Debug(sk, "Solver: Starting DOF analysis with %s-%s\n", ct->n1->name, ct->n2->name); SK_AddConstraintCopy(cl, ct); SK_AddInsn(sk, SK_COMPOSE_PAIR, ct->n1, ct->n2, SK_DupConstraint(ct)); SK_DelConstraint(&clOrig, ct); /* Keep merging constrained rings into this cluster. */ MergeConstrainedRings(sk, &clOrig, cl); TAILQ_INSERT_TAIL(&sk->clusters, cl, clusters); } /* * Search for constrained rings of 3 clusters and merge them into * larger clusters. */ merge_rings: nRing = 0; TAILQ_FOREACH(node, &sk->nodes, nodes) { if (node->flags & SK_NODE_SUPCONSTRAINTS) { continue; } count = 0; TAILQ_FOREACH(cl, &sk->clusters, clusters) { if (SK_NodeInCluster(node, cl)) { if (count < 2) { clPair[count] = cl; } count++; } } if (count == 2) { Debug(sk, "Solver: %s is shared by Cluster%u and Cluster%u\n", node->name, (Uint)clPair[0]->name, (Uint)clPair[1]->name); for (j = 0; j < 2; j++) { for (i = 0; i < nRing; i++) { if (clRing[i] == clPair[j]) break; } if (i == nRing && nRing < 3) { clRing[nRing++] = clPair[j]; } } if (nRing == 3) break; } } if (nRing == 3) { SK_Cluster *clMerged; clMerged = Malloc(sizeof(SK_Cluster)); SK_InitCluster(clMerged, SK_GenClusterName(sk)); Debug(sk, "Solver: Merging ring: Cluster%u-Cluster%u-Cluster%u -> " "Cluster%u\n", clRing[0]->name, clRing[1]->name, clRing[2]->name, clMerged->name); for (i = 0; i < 3; i++) { SK_CopyCluster(clRing[i], clMerged); TAILQ_REMOVE(&sk->clusters, clRing[i], clusters); SK_FreeCluster(clRing[i]); Free(clRing[i]); } /* * Merge any other cluster sharing two elements with * the new cluster. */ MergeConstrainedClusters(sk, clMerged); TAILQ_INSERT_TAIL(&sk->clusters, clMerged, clusters); goto merge_rings; } UpdateConstraintStatus(sk); out: AG_MutexUnlock(&sk->lock); return (0); }