/*
* Copyright (c) 2012 Hypertriton, Inc.
*
* 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.
*/
#include
#include
#include "mu.h"
#include "mu_placement.h"
#include "mu_gui.h"
#include
#include
int muInitedSubsystem = 0;
const char *muConstraintNames[] = {
N_("Distance"),
N_("Incidence"),
N_("Angle"),
N_("Perpendicular"),
N_("Parallel"),
N_("Tangent"),
};
void
MU_InitSubsystem(void)
{
if (muInitedSubsystem++ > 0)
return (0);
AG_RegisterNamespace("Agar-MU", "MU_", "http://libagar.org/");
/* Register our base Agar object classes. */
AG_RegisterClass(&muClass);
/* Initialize GUI facilities for Edit() */
MU_InitGUI();
}
void
MU_DestroySubsystem(void)
{
if (--muInitedSubsystem > 0) {
return;
}
MU_DestroyGUI();
AG_UnregisterNamespace("Agar-MU");
}
/* Create a new piece. */
MU *
MU_New(void *parent, const char *name)
{
char nameGen[AG_OBJECT_NAME_MAX];
MU *mu;
if (name == NULL) {
AG_ObjectGenName(parent, &muClass, nameGen, sizeof(nameGen));
} else {
if (parent != NULL &&
AG_ObjectFindChild(parent, name) != NULL) {
AG_SetError(_("%s: Existing child object %s"),
OBJECT(parent)->name, name);
return (NULL);
}
}
mu = Malloc(sizeof(MU));
AG_ObjectInit(mu, &muClass);
AG_ObjectSetNameS(mu, (name != NULL) ? name : nameGen);
AG_ObjectAttach(parent, mu);
return (mu);
}
static void
Init(void *obj)
{
MU *mu = obj;
OBJECT(mu)->flags |= AG_OBJECT_REOPEN_ONLOAD;
mu->flags = 0;
mu->status = MU_WELL_CONSTRAINED;
Strlcpy(mu->statusText, _("New piece"), sizeof(mu->statusText));
mu->nSolutions = 0;
MU_InitCluster(&mu->ctGraph, 0);
TAILQ_INIT(&mu->parts);
TAILQ_INIT(&mu->clusters);
TAILQ_INIT(&mu->insns);
}
/* Create a new part */
MU_Part *
MU_PartNew(MU *mu, const char *name, const char *nameFull)
{
MU_Part *part;
part = Malloc(sizeof(MU_Part));
part->mu = mu;
Strlcpy(part->name, name, sizeof(part->name));
Strlcpy(part->nameFull, nameFull, sizeof(part->nameFull));
AG_TextInit(&part->descr, 500);
TAILQ_INIT(&part->scores);
return (part);
}
/* Create a new staff under a part */
MU_Score *
MU_ScoreNew(MU_Part *part, enum mu_clef clef)
{
MU_Part *part;
part = Malloc(sizeof(MU_Part));
Strlcpy(part->name, name, sizeof(part->name));
AG_TextInit(&part->longName, 100);
AG_TextInit(&part->descr, 500);
TAILQ_INIT(&part->scores);
return (part);
}
MU_Note *
MU_NoteNew(MU *mu)
{
MU_Note *note;
Uint32 noteID = 1;
while (MU_FindNote(mu, noteID, type) != NULL) {
if (++noteID >= MU_NOTE_ID_MAX)
AG_FatalError("Out of note IDs");
}
note = Malloc(sizeof(MU_Note));
MU_NoteInit(note, &muNoteOps, noteID, 0);
MU_NoteAttach(pNote, node);
return (node);
}
void
MU_NoteInit(void *np, const void *ops, Uint32 noteID, Uint flags)
{
MU_Note *n = np;
n->ops = (const MU_NoteOps *)ops;
n->id = noteID;
AG_Snprintf(n->name, sizeof(n->name), "%s%u", n->ops->name, noteID);
n->flags = flags;
n->sk = NULL;
n->pNote = NULL;
n->cons = Malloc(sizeof(MU_Constraint *));
n->nCons = 0;
M_MatIdentity44v(&n->T);
TAILQ_INIT(&n->cnodes);
}
/* Change the name string associated with a node. */
void
MU_NoteSetName(void *p, const char *fmt, ...)
{
MU_Note *node = p;
va_list ap;
va_start(ap, fmt);
AG_Vsnprintf(node->name, sizeof(node->name), fmt, ap);
va_end(ap);
}
/* Free a node and detach/free any child nodes. */
static void
MU_FreeNote(SK *sk, MU_Note *node)
{
MU_Note *cnode, *cnodeNext;
for (cnode = TAILQ_FIRST(&node->cnodes);
cnode != TAILQ_END(&node->cnodes);
cnode = cnodeNext) {
cnodeNext = TAILQ_NEXT(cnode, sknodes);
TAILQ_REMOVE(&sk->nodes, cnode, nodes);
MU_FreeNote(sk, cnode);
}
if (node->ops->destroy != NULL) {
node->ops->destroy(node);
}
Free(node->cons);
Free(node);
}
static void
Reset(void *obj)
{
SK *sk = obj;
if (sk->root != NULL) {
MU_FreeNote(sk, sk->root);
sk->root = NULL;
}
TAILQ_INIT(&sk->nodes);
MU_InitRoot(sk);
MU_FreeCluster(&sk->ctGraph);
MU_FreeClusters(sk);
MU_FreeInsns(sk);
}
static int
MU_NoteSaveData(SK *sk, MU_Note *node, AG_DataSource *buf)
{
MU_Note *chldNote;
TAILQ_FOREACH(chldNote, &node->cnodes, sknodes) {
if (MU_NoteSaveData(sk, chldNote, buf) == -1)
return (-1);
}
if (node->ops->save != NULL &&
node->ops->save(sk, node, buf) == -1) {
AG_SetError("NoteSave: %s", AG_GetError());
return (-1);
}
return (0);
}
static int
MU_SaveNoteGeneric(SK *sk, MU_Note *node, AG_DataSource *buf)
{
MU_Note *cnode;
Uint32 ncnodes;
off_t bsize_offs, ncnodes_offs;
/* Save generic node information. */
AG_WriteString(buf, node->ops->name);
bsize_offs = AG_Tell(buf);
AG_Seek(buf, sizeof(Uint32), AG_SEEK_CUR);
AG_WriteUint32(buf, node->handle);
AG_WriteString(buf, node->name);
AG_WriteUint16(buf, (Uint16)node->flags);
M_WriteMatrix44(buf, &node->T);
/* Save the child nodes recursively. */
ncnodes_offs = AG_Tell(buf);
ncnodes = 0;
AG_Seek(buf, sizeof(Uint32), AG_SEEK_CUR);
TAILQ_FOREACH(cnode, &node->cnodes, sknodes) {
if (MU_SaveNoteGeneric(sk, cnode, buf) == -1) {
return (-1);
}
ncnodes++;
}
AG_WriteUint32At(buf, ncnodes, ncnodes_offs);
/* Save the total block size to allow the loader to skip. */
AG_WriteUint32At(buf, AG_Tell(buf)-bsize_offs, bsize_offs);
return (0);
}
static int
Save(void *obj, AG_DataSource *buf)
{
SK *sk = obj;
MU_Constraint *ct;
Uint32 count;
off_t offs;
AG_MutexLock(&sk->lock);
AG_WriteUint32(buf, sk->flags);
/* Save the generic part of all nodes. */
if (MU_SaveNoteGeneric(sk, sk->root, buf) == -1)
goto fail;
/* Save the data part of all nodes. */
if (MU_NoteSaveData(sk, sk->root, buf) == -1)
goto fail;
/* Save the graph of geometric constraints. */
offs = AG_Tell(buf);
count = 0;
AG_Seek(buf, sizeof(Uint32), AG_SEEK_CUR);
TAILQ_FOREACH(ct, &sk->ctGraph.edges, constraints) {
AG_WriteUint32(buf, (Uint32)ct->type);
AG_WriteUint32(buf, (Uint32)ct->uType);
MU_WriteRef(buf, ct->n1);
MU_WriteRef(buf, ct->n2);
switch (ct->type) {
case MU_DISTANCE:
M_WriteReal(buf, ct->ct_distance);
break;
case MU_ANGLE:
M_WriteReal(buf, ct->ct_angle);
break;
default:
break;
}
count++;
}
AG_WriteUint32At(buf, count, offs);
AG_MutexUnlock(&sk->lock);
return (0);
fail:
AG_MutexUnlock(&sk->lock);
return (-1);
}
/* Load the data part of a node. */
static int
MU_LoadNoteData(SK *sk, MU_Note *node, AG_DataSource *buf)
{
MU_Note *chldNote;
TAILQ_FOREACH(chldNote, &node->cnodes, sknodes) {
if (MU_LoadNoteData(sk, chldNote, buf) == -1)
return (-1);
}
if (node->ops->load != NULL &&
node->ops->load(sk, node, buf) == -1) {
AG_SetError("%s: %s", node->name, AG_GetError());
return (-1);
}
return (0);
}
/* Load the generic part of a node. */
static int
MU_LoadNoteGeneric(SK *sk, MU_Note **rnode, AG_DataSource *buf)
{
char type[MU_TYPE_NAME_MAX];
MU_Note *node;
Uint32 bsize, nchildren, j;
Uint32 handle;
Uint i;
/* Load generic node information. */
AG_CopyString(type, buf, sizeof(type));
bsize = AG_ReadUint32(buf);
for (i = 0; i < skElementsCnt; i++) {
if (strcmp(skElements[i]->name, type) == 0)
break;
}
if (i == skElementsCnt) {
if (sk->flags & MU_SKIP_UNKNOWN_FEATURES) {
fprintf(stderr, "%s: skipping feature (%s/%luB)\n",
OBJECT(sk)->name, type, (Ulong)bsize);
AG_Seek(buf, bsize, AG_SEEK_CUR);
*rnode = NULL;
return (0);
} else {
AG_SetError("Unimplemented node class: %s (%luB)",
type, (Ulong)bsize);
return (-1);
}
}
node = Malloc(skElements[i]->size);
handle = AG_ReadUint32(buf);
skElements[i]->init(node, handle);
node->sk = sk;
AG_CopyString(node->name, buf, sizeof(node->name));
node->flags = (Uint)AG_ReadUint16(buf);
M_ReadMatrix44v(buf, &node->T);
/* Load the child nodes recursively. */
nchildren = AG_ReadUint32(buf);
for (j = 0; j < nchildren; j++) {
MU_Note *cnode;
if (MU_LoadNoteGeneric(sk, &cnode, buf) == -1) {
AG_SetError("subnode: %s", AG_GetError());
return (-1);
}
if (cnode != NULL)
MU_NoteAttach(node, cnode);
}
*rnode = node;
return (0);
}
static int
Load(void *obj, AG_DataSource *buf, const AG_Version *ver)
{
char unitKey[128];
SK *sk = obj;
Uint32 i, count;
MU_Constraint *ct;
AG_MutexLock(&sk->lock);
sk->flags = (Uint)AG_ReadUint32(buf);
AG_CopyString(unitKey, buf, sizeof(unitKey));
/* Free the existing root node. */
if (sk->root != NULL) {
MU_FreeNote(sk, sk->root);
sk->root = NULL;
}
TAILQ_INIT(&sk->nodes);
/*
* Load the generic part of all nodes. We need to load the data
* afterwards to properly resolve interdependencies.
*/
if (MU_LoadNoteGeneric(sk, &sk->root, buf) == -1) {
goto fail;
}
TAILQ_INSERT_HEAD(&sk->nodes, sk->root, nodes);
/* Load the data part of all nodes. */
if (MU_LoadNoteData(sk, sk->root, buf) == -1)
goto fail;
/* Load the geometric constraint data. */
count = AG_ReadUint32(buf);
for (i = 0; i < count; i++) {
ct = AG_Malloc(sizeof(MU_Constraint));
ct->type = (enum sk_constraint_type)AG_ReadUint32(buf);
ct->uType = (enum sk_constraint_type)AG_ReadUint32(buf);
ct->n1 = MU_ReadRef(buf, sk, NULL);
ct->n2 = MU_ReadRef(buf, sk, NULL);
switch (ct->type) {
case MU_DISTANCE:
ct->ct_distance = M_ReadReal(buf);
break;
case MU_ANGLE:
ct->ct_angle = M_ReadReal(buf);
break;
default:
break;
}
MU_NoteAddConstraint(ct->n1, ct);
MU_NoteAddConstraint(ct->n2, ct);
TAILQ_INSERT_TAIL(&sk->ctGraph.edges, ct, constraints);
}
MU_Update(sk);
AG_MutexUnlock(&sk->lock);
return (0);
fail:
AG_MutexUnlock(&sk->lock);
AG_ObjectReset(sk);
return (-1);
}
/*
* Compute the product of the transform matrices of the given node and its
* parents in order. T is initialized to identity.
*/
void
MU_GetNoteTransform(void *p, M_Matrix44 *T)
{
MU_Note *node = p;
MU_Note *cnode = node;
TAILQ_HEAD_(sk_node) rnodes = TAILQ_HEAD_INITIALIZER(rnodes);
/*
* Build a list of parent nodes and multiply their matrices in order
* (ugly but faster than computing the product of their inverses).
*/
while (cnode != NULL) {
TAILQ_INSERT_HEAD(&rnodes, cnode, rnodes);
if (cnode->pNote == NULL) {
break;
}
cnode = cnode->pNote;
}
M_MatIdentity44v(T);
TAILQ_FOREACH(cnode, &rnodes, rnodes)
M_MatMult44v(T, &cnode->T);
}
/*
* Compute the product of the inverse transform matrices of the given node
* and its parents.
*/
void
MU_GetNoteTransformInverse(void *p, M_Matrix44 *T)
{
MU_Note *node = p;
MU_Note *cnode = node;
TAILQ_HEAD_(sk_node) rnodes = TAILQ_HEAD_INITIALIZER(rnodes);
M_MatIdentity44v(T);
while (cnode != NULL) {
TAILQ_INSERT_TAIL(&rnodes, cnode, rnodes);
if (cnode->pNote == NULL) {
break;
}
cnode = cnode->pNote;
}
TAILQ_FOREACH(cnode, &rnodes, rnodes) {
M_Matrix44 Tinv;
Tinv = M_MatInvert44(cnode->T);
M_MatMult44v(T, &Tinv);
}
}
void
MU_NoteRedraw(void *p, MU_View *skv)
{
MU_Note *node = p;
if (node->ops->redraw != NULL)
node->ops->redraw(node, skv);
}
/* Return the orientation vector of a node. */
M_Vector3
MU_NoteDir(void *p)
{
MU_Note *node = p;
M_Matrix44 T;
M_Vector3 v = M_VecK3(); /* Convention */
MU_GetNoteTransform(node, &T);
M_MatMult44Vector3v(&v, &T);
return (v);
}
/* Associate a constraint with a node. */
void
MU_NoteAddConstraint(void *pNote, MU_Constraint *ct)
{
MU_Note *node = pNote;
Uint i;
for (i = 0; i < node->nCons; i++) {
if (node->cons[i] == ct)
return;
}
node->cons = Realloc(node->cons, (node->nCons+1) *
sizeof(MU_Constraint *));
node->cons[node->nCons++] = ct;
}
/* Dissociate a constraint from a node. */
void
MU_NoteDelConstraint(void *pNote, MU_Constraint *ct)
{
MU_Note *node = pNote;
Uint i;
for (i = 0; i < node->nCons; i++) {
if (node->cons[i] != ct) {
continue;
}
if (i < node->nCons-1) {
memmove(&node->cons[i], &node->cons[i+1],
(node->nCons-i-1)*sizeof(MU_Constraint *));
}
node->nCons--;
break;
}
}
/* Search a node by handle and class. */
void *
MU_FindNote(SK *sk, Uint32 handle, const char *type)
{
MU_Note *node;
TAILQ_FOREACH(node, &sk->nodes, nodes) {
if (node->handle == handle &&
strcmp(node->ops->name, type) == 0)
return (node);
}
AG_SetError("No such node: %u", handle);
return (NULL);
}
/* Search a node by name only. */
void *
MU_FindNoteByName(SK *sk, const char *name)
{
MU_Note *node;
TAILQ_FOREACH(node, &sk->nodes, nodes) {
if (strcmp(node->name, name) == 0)
return (node);
}
AG_SetError("No such node: %s", name);
return (NULL);
}
/* Attach a node to another node in the sketch. */
void
MU_NoteAttach(void *ppNote, void *pcNote)
{
MU_Note *pNote = ppNote;
MU_Note *cNote = pcNote;
cNote->sk = pNote->sk;
cNote->pNote = pNote;
TAILQ_INSERT_TAIL(&pNote->cnodes, cNote, sknodes);
TAILQ_INSERT_TAIL(&pNote->sk->nodes, cNote, nodes);
}
/* Detach a node from its parent in the sketch. */
void
MU_NoteDetach(void *ppNote, void *pcNote)
{
MU_Note *pNote = ppNote;
MU_Note *cNote = pcNote;
MU_Note *subnode;
SK *sk = pNote->sk;
MU_Constraint *ct;
while ((subnode = TAILQ_FIRST(&cNote->cnodes)) != NULL) {
MU_NoteDetach(cNote, subnode);
}
free_constraints:
TAILQ_FOREACH(ct, &sk->ctGraph.edges, constraints) {
if (ct->n1 == cNote || ct->n2 == cNote) {
MU_DelConstraint(&sk->ctGraph, ct);
goto free_constraints;
}
}
TAILQ_REMOVE(&pNote->cnodes, cNote, sknodes);
TAILQ_REMOVE(&sk->nodes, cNote, nodes);
cNote->sk = NULL;
cNote->pNote = NULL;
}
/* Move a node up in the order of rendering. */
void
MU_NoteMoveUp(void *p)
{
MU_Note *node = p;
MU_Note *prev;
AG_ObjectLock(node->sk);
if (node != TAILQ_FIRST(&node->pNote->cnodes)) {
prev = TAILQ_PREV(node, sk_nodeq, sknodes);
TAILQ_REMOVE(&node->pNote->cnodes, node, sknodes);
TAILQ_INSERT_BEFORE(prev, node, sknodes);
}
AG_ObjectUnlock(node->sk);
}
/* Move a node down in the order of rendering. */
void
MU_NoteMoveDown(void *p)
{
MU_Note *node = p;
MU_Note *next = TAILQ_NEXT(node, sknodes);
AG_ObjectLock(node->sk);
if (next != NULL) {
TAILQ_REMOVE(&node->pNote->cnodes, node, sknodes);
TAILQ_INSERT_AFTER(&node->pNote->cnodes, next, node, sknodes);
}
AG_ObjectUnlock(node->sk);
}
/* Move a node to the tail of its parent's child list. */
void
MU_NoteMoveTail(void *p)
{
MU_Note *n = p;
AG_ObjectLock(n->sk);
TAILQ_REMOVE(&n->pNote->cnodes, n, sknodes);
TAILQ_INSERT_TAIL(&n->pNote->cnodes, n, sknodes);
AG_ObjectUnlock(n->sk);
}
/* Move a node to the head of its parent's child list. */
void
MU_NoteMoveHead(void *p)
{
MU_Note *n = p;
AG_ObjectLock(n->sk);
TAILQ_REMOVE(&n->pNote->cnodes, n, sknodes);
TAILQ_INSERT_HEAD(&n->pNote->cnodes, n, sknodes);
AG_ObjectUnlock(n->sk);
}
/* Move a node to a different parent node. */
void
MU_NoteMoveToParent(void *pNote, void *pParent)
{
MU_Note *n = pNote;
MU_Note *pOld = n->pNote;
MU_Note *pNew = pParent;
AG_ObjectLock(n->sk);
TAILQ_REMOVE(&pOld->cnodes, n, sknodes);
TAILQ_INSERT_TAIL(&pNew->cnodes, n, sknodes);
AG_ObjectUnlock(n->sk);
}
/*
* Detach and free a node (and its children) from the sketch. Annotations
* and constraints referencing the node are automatically deleted as well.
*/
int
MU_NoteDel(void *p)
{
MU_Note *node = p;
SK *sk = node->sk;
MU_Constraint *ct;
if (node == sk->root) {
AG_SetError("Cannot delete root node");
return (-1);
}
if (!MU_NoteOfClass(node, "Annot:Dimension:*")) {
MU_Dimension *dim;
deldims:
MU_FOREACH_NODE_CLASS(dim, sk, sk_dimension,
"Annot:Dimension:*") {
if (dim->n1 == node || dim->n2 == node) {
SKNODE(dim)->ops->del(dim);
goto deldims;
}
}
}
delcts:
TAILQ_FOREACH(ct, &sk->ctGraph.edges, constraints) {
if (ct->n1 == node || ct->n2 == node) {
MU_NoteDelConstraint(ct->n1, ct);
MU_NoteDelConstraint(ct->n2, ct);
MU_DelConstraint(&sk->ctGraph, ct);
goto delcts;
}
}
MU_NoteDetach(node->pNote, node);
MU_FreeNote(sk, node);
return (0);
}
void
MU_WriteRef(AG_DataSource *buf, void *pNote)
{
MU_Note *node = pNote;
AG_WriteString(buf, node->ops->name);
AG_WriteUint32(buf, node->handle);
}
void *
MU_ReadRef(AG_DataSource *buf, SK *sk, const char *expType)
{
char rType[MU_TYPE_NAME_MAX];
AG_CopyString(rType, buf, sizeof(rType));
if (expType != NULL) {
if (strcmp(rType, expType) != 0) {
AG_FatalError("Unexpected reference type: `%s' "
"(expecting %s)", rType, expType);
}
}
return (MU_FindNote(sk, AG_ReadUint32(buf), rType));
}
/*
* Perform a proximity query with the given vector against all elements
* of the given type (or all elements if type is NULL), and return the
* closest item.
*
* The closest point of the closest item is also returned in vC.
*/
void *
MU_ProximitySearch(SK *sk, const char *type, const M_Vector3 *v, M_Vector3 *vC,
void *nodeIgnore)
{
MU_Note *node, *nClosest = NULL;
M_Real rClosest = M_INFINITY, p;
M_Vector3 vClosest = M_VecGet3(M_INFINITY, M_INFINITY, 0.0);
TAILQ_FOREACH(node, &sk->nodes, nodes) {
if (node == nodeIgnore ||
node->ops->proximity == NULL) {
continue;
}
if (type != NULL &&
strcmp(node->ops->name, type) != 0) {
continue;
}
p = node->ops->proximity(node, v, vC);
if (p < rClosest) {
rClosest = p;
nClosest = node;
vClosest.x = vC->x;
vClosest.y = vC->y;
}
}
vC->x = vClosest.x;
vC->y = vClosest.y;
vC->z = 0.0;
return (nClosest);
}
/* Search a node by name in a sketch. */
/* XXX use a hash table */
MU_Cluster *
MU_FindCluster(SK *sk, Uint32 name)
{
MU_Cluster *cl;
TAILQ_FOREACH(cl, &sk->clusters, clusters) {
if (cl->name == name)
return (cl);
}
AG_SetError("No such cluster: %u", name);
return (NULL);
}
/* Allocate a new cluster name. */
Uint
MU_GenClusterName(SK *sk)
{
Uint name = 1;
while (MU_FindCluster(sk, name) != NULL) {
if (++name >= MU_CLUSTER_ID_MAX)
AG_FatalError("Out of cluster IDs");
}
return (name);
}
void
MU_InitCluster(MU_Cluster *cl, Uint32 name)
{
cl->name = name;
TAILQ_INIT(&cl->edges);
}
void
MU_CopyCluster(const MU_Cluster *clSrc, MU_Cluster *clDst)
{
MU_Constraint *ct;
TAILQ_FOREACH(ct, &clSrc->edges, constraints)
MU_AddConstraintCopy(clDst, ct);
}
void
MU_FreeCluster(MU_Cluster *cl)
{
MU_Constraint *ct;
while ((ct = TAILQ_FIRST(&cl->edges)) != NULL) {
TAILQ_REMOVE(&cl->edges, ct, constraints);
Free(ct);
}
}
void
MU_FreeClusters(SK *sk)
{
MU_Cluster *cl;
while ((cl = TAILQ_FIRST(&sk->clusters)) != NULL) {
TAILQ_REMOVE(&sk->clusters, cl, clusters);
MU_FreeCluster(cl);
Free(cl);
}
}
void
MU_FreeInsns(SK *sk)
{
MU_Insn *si;
while ((si = TAILQ_FIRST(&sk->insns)) != NULL) {
TAILQ_REMOVE(&sk->insns, si, insns);
Free(si);
}
}
/* Create a new constraint edge in the given constraint graph. */
MU_Constraint *
MU_AddConstraint(MU_Cluster *cl, void *node1, void *node2,
enum sk_constraint_type type, ...)
{
MU_Constraint *ct;
va_list ap;
if (MU_FindConstraint(cl, MU_CONSTRAINT_ANY, node1, node2) != NULL) {
AG_SetError(_("Existing constraint; new %s constraint would "
"overconstraint sketch."),
skConstraintNames[type]);
return (NULL);
}
ct = Malloc(sizeof(MU_Constraint));
ct->uType = type;
ct->n1 = node1;
ct->n2 = node2;
va_start(ap, type);
switch (type) {
case MU_DISTANCE:
ct->ct_distance = (M_Real)va_arg(ap, double);
break;
case MU_ANGLE:
ct->ct_angle = (M_Real)va_arg(ap, double);
break;
default:
break;
}
va_end(ap);
switch (type) {
case MU_INCIDENT:
ct->type = MU_DISTANCE;
ct->ct_distance = 0.0;
break;
case MU_PERPENDICULAR:
ct->type = MU_ANGLE;
ct->ct_angle = M_PI/2.0;
break;
case MU_PARALLEL:
ct->type = MU_ANGLE;
ct->ct_angle = 0.0;
break;
default:
ct->type = ct->uType;
break;
}
TAILQ_INSERT_TAIL(&cl->edges, ct, constraints);
return (ct);
}
/* Duplicate a constraint. */
MU_Constraint *
MU_DupConstraint(const MU_Constraint *ct1)
{
MU_Constraint *ct2;
ct2 = Malloc(sizeof(MU_Constraint));
ct2->type = ct1->type;
ct2->uType = ct1->uType;
ct2->n1 = ct1->n1;
ct2->n2 = ct1->n2;
switch (ct1->type) {
case MU_DISTANCE:
ct2->ct_distance = ct1->ct_distance;
break;
case MU_ANGLE:
ct2->ct_angle = ct1->ct_angle;
break;
default:
break;
}
return (ct2);
}
/* Duplicate a constraint edge. */
MU_Constraint *
MU_AddConstraintCopy(MU_Cluster *clDst, const MU_Constraint *ct)
{
switch (ct->type) {
case MU_DISTANCE:
return MU_AddConstraint(clDst, ct->n1, ct->n2, MU_DISTANCE,
ct->ct_distance);
case MU_ANGLE:
return MU_AddConstraint(clDst, ct->n1, ct->n2, MU_ANGLE,
ct->ct_angle);
default:
break;
}
return MU_AddConstraint(clDst, ct->n1, ct->n2, ct->type);
}
/* Destroy a constraint edge. */
void
MU_DelConstraint(MU_Cluster *cl, MU_Constraint *ct)
{
TAILQ_REMOVE(&cl->edges, ct, constraints);
Free(ct);
}
/* Destroy a constraint edge matching the argument. */
int
MU_DelSimilarConstraint(MU_Cluster *cl, const MU_Constraint *ctRef)
{
MU_Constraint *ct;
if ((ct = MU_FindSimilarConstraint(cl, ctRef)) == NULL) {
AG_SetError("No matching constraint");
return (-1);
}
MU_DelConstraint(cl, ct);
return (0);
}
int
MU_CompareConstraints(const MU_Constraint *ct1, const MU_Constraint *ct2)
{
if (ct1->type == ct2->type &&
((ct1->n1 == ct2->n1 && ct1->n2 == ct2->n2) ||
(ct1->n1 == ct2->n2 && ct1->n2 == ct2->n1))) {
#if 1
switch (ct1->type) {
case MU_DISTANCE:
return (int)(ct1->ct_distance - ct2->ct_distance);
case MU_ANGLE:
return (int)(ct1->ct_angle - ct2->ct_angle);
default:
return (0);
}
#else
return (0);
#endif
}
return (1);
}
/*
* Search a constraint graph for any constraint of a given type between
* two given nodes.
*/
MU_Constraint *
MU_FindConstraint(const MU_Cluster *cl, enum sk_constraint_type type,
void *n1, void *n2)
{
MU_Constraint *ct;
TAILQ_FOREACH(ct, &cl->edges, constraints) {
if ((ct->type == type || type == MU_CONSTRAINT_ANY) &&
((ct->n1 == n1 && ct->n2 == n2) ||
(ct->n1 == n2 && ct->n2 == n1)))
return (ct);
}
return (NULL);
}
/* Search a constraint graph for a constraint matching the argument. */
MU_Constraint *
MU_FindSimilarConstraint(const MU_Cluster *cl, const MU_Constraint *ct)
{
MU_Constraint *ct2;
TAILQ_FOREACH(ct2, &cl->edges, constraints) {
if (MU_CompareConstraints(ct2, ct) == 0)
return (ct2);
}
return (NULL);
}
/*
* Check if the given two nodes share a constraint edge in the given
* constraint graph.
*/
MU_Constraint *
MU_ConstrainedNotes(const MU_Cluster *cl, const MU_Note *n1, const MU_Note *n2)
{
MU_Constraint *ct;
TAILQ_FOREACH(ct, &cl->edges, constraints) {
if ((ct->n1 == n1 && ct->n2 == n2) ||
(ct->n1 == n2 && ct->n2 == n1))
return (ct);
}
return (NULL);
}
/*
* Return the number of constraints for the given node, not counting incidence
* constraints between line segments and their endpoints.
*/
Uint
MU_NoteConstraintCount(const MU_Cluster *cl, void *node)
{
MU_Note *nOther;
MU_Constraint *ct;
Uint count = 0;
/* TODO use nCons */
TAILQ_FOREACH(ct, &cl->edges, constraints) {
if (ct->n1 != node && ct->n2 != node) {
continue;
}
nOther = (ct->n1 == node) ? ct->n2 : ct->n1;
if (ct->type == MU_DISTANCE && ct->ct_distance == 0.0) {
if (MU_NoteOfClass(node, "Point:*") &&
MU_NoteOfClass(nOther, "Line:*")) {
continue;
}
if (MU_NoteOfClass(node, "Line:*") &&
MU_NoteOfClass(nOther, "Point:*")) {
continue;
}
}
count++;
}
return (count);
}
/* Evaluate whether the given node is in the given constraint graph. */
/* XXX optimize */
int
MU_NoteInCluster(const MU_Note *node, const MU_Cluster *cl)
{
MU_Constraint *ct;
TAILQ_FOREACH(ct, &cl->edges, constraints) {
if (ct->n1 == node || ct->n2 == node)
return (1);
}
return (0);
}
/*
* Count the number of edges between a given node and any other node
* in the given subgraph clSub of graph cl.
*/
Uint
MU_ConstraintsToSubgraph(const MU_Cluster *clOrig, const MU_Note *node,
const MU_Cluster *clSub, MU_Constraint *rv[2])
{
MU_Constraint *ct;
Uint count = 0;
TAILQ_FOREACH(ct, &clOrig->edges, constraints) {
if ((ct->n1 == node && MU_NoteInCluster(ct->n2, clSub)) ||
(ct->n2 == node && MU_NoteInCluster(ct->n1, clSub))) {
if (count < 2) {
rv[count] = ct;
}
count++;
}
}
return (count);
}
/* Add a construction step for the construction phase of the solver. */
MU_Insn *
MU_AddInsn(SK *sk, enum sk_insn_type type, ...)
{
MU_Insn *si;
va_list ap;
si = Malloc(sizeof(MU_Insn));
si->type = type;
va_start(ap, type);
switch (type) {
case MU_COMPOSE_PAIR:
si->n[0] = va_arg(ap, void *);
si->n[1] = va_arg(ap, void *);
si->ct01 = va_arg(ap, void *);
#ifdef SG_DEBUG
if (si->n[0] == NULL || si->n[1] == NULL || si->ct01 == NULL ||
si->ct01->type >= MU_CONSTRAINT_LAST)
AG_FatalError("Bad args");
#endif
Debug(sk, "+ COMPOSE_PAIR(%s,%s)\n",
si->n[0]->name, si->n[1]->name);
break;
case MU_COMPOSE_RING:
si->n[0] = va_arg(ap, void *);
si->n[1] = va_arg(ap, void *);
si->n[2] = va_arg(ap, void *);
si->ct01 = va_arg(ap, void *);
si->ct02 = va_arg(ap, void *);
#ifdef SG_DEBUG
if (si->n[0] == NULL || si->n[1] == NULL || si->n[2] == NULL ||
si->ct01 == NULL || si->ct02 == NULL ||
si->ct01->type >= MU_CONSTRAINT_LAST ||
si->ct02->type >= MU_CONSTRAINT_LAST)
AG_FatalError("Bad args");
#endif
Debug(sk, "+ COMPOSE_RING(%s,%s,%s)\n",
si->n[0]->name, si->n[1]->name, si->n[2]->name);
break;
}
va_end(ap);
TAILQ_INSERT_TAIL(&sk->insns, si, insns);
return (si);
}
static int
MU_ComposePair(SK *sk, const MU_Insn *insn)
{
MU_Note *n, *n1;
MU_Constraint *ct = insn->ct01;
int i, rv = -1;
if (MU_FIXED(insn->n[0]) && MU_FIXED(insn->n[1])) {
AG_SetError("Attempt to place two fixed entities");
return (-1);
}
if (MU_FIXED(insn->n[0])) {
n = insn->n[1];
n1 = insn->n[0];
} else if (MU_FIXED(insn->n[1])) {
n = insn->n[0];
n1 = insn->n[1];
} else {
if (MU_MOVED(insn->n[0])) {
n = insn->n[1];
n1 = insn->n[0];
} else if (MU_MOVED(insn->n[1])) {
n = insn->n[0];
n1 = insn->n[1];
} else {
n = insn->n[0];
n1 = insn->n[1];
}
}
Debug(sk, "Constructing: %s(%s) => %s\n",
n1->name, skConstraintNames[ct->type], n->name);
for (i = 0; i < skConstraintPairFnCount; i++) {
const MU_ConstraintPairFn *fn = &skConstraintPairFns[i];
if (fn->ctType != ct->type) {
continue;
}
if (MU_NoteOfClass(n, fn->type1) &&
MU_NoteOfClass(n1, fn->type2)) {
rv = fn->fn(ct, (void *)n, (void *)n1);
break;
} else
if (MU_NoteOfClass(n1, fn->type1) &&
MU_NoteOfClass(n, fn->type2)) {
rv = fn->fn(ct, (void *)n1, (void *)n);
break;
}
}
if (i == skConstraintPairFnCount) {
AG_SetError("Illegal case: %s(%s,%s)",
skConstraintNames[ct->type], n->ops->name, n1->ops->name);
return (-1);
}
return (rv);
}
static int
MU_ComposeRing(SK *sk, const MU_Insn *insn)
{
MU_Note *n = insn->n[0];
MU_Note *n1 = insn->n[1];
MU_Note *n2 = insn->n[2];
MU_Constraint *ct1 = insn->ct01;
MU_Constraint *ct2 = insn->ct02;
int i, rv = 0;
if (MU_FIXED(n)) {
AG_SetError("Attempt to place fixed entity");
return (-1);
}
Debug(sk, "Constructing: %s,%s(%s,%s) => %s\n",
n1->name, n2->name,
skConstraintNames[ct1->type],
skConstraintNames[ct2->type],
n->name);
for (i = 0; i < skConstraintRingFnCount; i++) {
const MU_ConstraintRingFn *fn = &skConstraintRingFns[i];
if ((fn->ctType1 == ct1->type ||
fn->ctType1 == MU_CONSTRAINT_ANY) &&
(fn->ctType2 == ct2->type ||
fn->ctType2 == MU_CONSTRAINT_ANY) &&
MU_NoteOfClass(n, fn->type1) &&
MU_NoteOfClass(n1, fn->type2) &&
MU_NoteOfClass(n2, fn->type3)) {
rv = fn->fn(n, ct1, n1, ct2, n2);
if (rv == 0) {
n->flags |= MU_NOTE_KNOWN;
}
break;
} else if
((fn->ctType1 == ct2->type ||
fn->ctType1 == MU_CONSTRAINT_ANY) &&
(fn->ctType2 == ct1->type ||
fn->ctType2 == MU_CONSTRAINT_ANY) &&
MU_NoteOfClass(n, fn->type1) &&
MU_NoteOfClass(n2, fn->type2) &&
MU_NoteOfClass(n1, fn->type3)) {
rv = fn->fn(n, ct2, n2, ct1, n1);
if (rv == 0) {
n->flags |= MU_NOTE_KNOWN;
}
break;
}
}
if (i == skConstraintRingFnCount) {
AG_SetError("Illegal case: %s([%s:%s], [%s:%s])",
n->ops->name,
skConstraintNames[ct1->type],
n1->ops->name,
skConstraintNames[ct2->type],
n2->ops->name);
return (-1);
}
return (rv);
}
int
MU_ExecInsn(SK *sk, const MU_Insn *insn)
{
switch (insn->type) {
case MU_COMPOSE_PAIR:
return MU_ComposePair(sk, insn);
case MU_COMPOSE_RING:
return MU_ComposeRing(sk, insn);
default:
AG_SetError("Illegal instruction: 0x%x", insn->type);
return (-1);
}
}
void
MU_ClearProgramState(SK *sk)
{
MU_Note *node;
sk->nSolutions = 0;
TAILQ_FOREACH(node, &sk->nodes, nodes)
sk->flags &= ~(MU_NOTE_KNOWN);
}
int
MU_ExecProgram(SK *sk)
{
MU_Insn *si;
Uint i = 0;
TAILQ_FOREACH(si, &sk->insns, insns) {
if (MU_ExecInsn(sk, si) == -1) {
MU_SetStatus(sk, MU_INVALID, _("Error(%u): %s"),
i, AG_GetError());
return (-1);
}
i++;
}
return (0);
}
void
MU_SetStatus(SK *sk, enum mu_solver_status status, const char *fmt, ...)
{
va_list ap;
sk->status = status;
va_start(ap, fmt);
AG_Vsnprintf(sk->statusText, sizeof(sk->statusText), fmt, ap);
va_end(ap);
}
AG_ObjectClass muClass = {
"MU",
sizeof(MU),
{ 0,0 },
Init,
Reset,
NULL, /* destroy */
Load,
Save,
MU_Edit
};