summaryrefslogtreecommitdiff
path: root/graphics/asymptote/flatguide.h
blob: e960f1f8683333210e8be9de352ede51cd4ed3a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*****
 * flatguide.h
 * Andy Hammerlindl 2005/02/23
 *
 * The data structure that builds up a knotlist.  This is done by calling in
 * order the methods to set knots, specifiers, and tensions.
 * Used by the guide solving routines.
 *
 * NOTE: figure out how nullpath{}..a should be handled.
 *****/

#ifndef FLATGUIDE_H
#define FLATGUIDE_H

#include "knot.h"
#include "guideflags.h"

namespace camp {

class flatguide
{
  // A cached solution of the path.  When traversing through a tree of guides,
  // if a cycle tag is encountered, then the path is solved up to that point.
  // If the guide continues from there (which rarely occurs in practice), all of
  // the control points solved are added as control specifiers, and then solved
  // into a path again.  In the (usual) case that a cycle ends a path, the
  // cached path avoids this second pass.
  bool solved;

  // Used by reverse(guide) to indicate the presence of an unresolved
  // interior cycle.
  bool precycle;

  path p;

  cvector<knot> nodes;

  // Information before the first knot.  For a non-cyclic guide, this is
  // ignored.  For a cyclic guide, it may be useful, but I can't determine a
  // sensible way to use it yet.
  tension tout;
  spec *out;

  // Information for the next knot to come.
  tension tin;
  spec *in;

  static spec open;

  tension& tref(side s)
  {
    switch (s) {
      case OUT:
        return nodes.empty() ? tout : nodes.back().tout;
      case IN:
      default:
        return tin;
    }
  }

  // Returns a reference to a spec* so that it may be assigned.
  spec*& sref(side s)
  {
    switch (s) {
      case OUT:
        return nodes.empty() ? out : nodes.back().out;
      case IN:
      default:
        return in;
    }
  }

  void addPre(path& p, Int j);
  void addPoint(path& p, Int j);
  void addPost(path& p, Int j);

  void clearNodes() {
    nodes.clear();
    in=&open;
    tin=tension();
  }
  void clearPath() {
    p=path();
    solved=false;
  }

  void uncheckedAdd(path p, bool allowsolve=true);

  // Sets solved to false, indicating that the path has been updated since last
  // being solved.  Also, copies a solved path back in as knots and control
  // specifiers, as it will have to be solved again.
  void update() {
    if (solved) {
      solved=false;
      clearNodes();
      add(p);
      clearPath();
    }
  }

public:
  flatguide()
    : solved(true), precycle(false), p(), out(&open), in(&open) {}

  Int size() const {
    return (Int) nodes.size();
  }

  knot Nodes(Int i) const {
    return nodes[i];
  }

  void setTension(tension t, side s) {
    update();
    tref(s)=t;
  }
  void setSpec(spec *p, side s) {
    assert(p);
    update();
    spec *&ref=sref(s);
    // Control specifiers trump normal direction specifiers.
    if (!ref || !ref->controlled() || p->controlled())
      ref=p;
  }

  void add(pair z) {
    update();
    // Push the pair onto the vector as a knot, using the current in-specifier
    // and in-tension for the in side for the knot. Use default values for the
    // out side, as those will be set after the point is added.
    nodes.push_back(knot(z,in,&open,tin,tension()));

    // Reset the in-spec and in-tension to defaults;
    tin=tension();
    in=&open;
  }

  // Reverts to an empty state.
  void add(path p, bool allowsolve=true) {
    update();
    uncheckedAdd(p,allowsolve);
  }

  void clear() {
    clearNodes();
    clearPath();
  }

  void close() {
    if(!nodes.empty()) {
      nodes.front().in=in;
      nodes.front().tin=tin;
    }
  }

  void resolvecycle() {
    if(!nodes.empty())
      nodes.push_back(nodes.front());
  }

  void precyclic(bool b) {
    precycle=b;
  }

  bool precyclic() {
    return precycle;
  }

  // Once all information has been added, release the flat result.
  simpleknotlist list(bool cycles=false) {
    if(cycles && !nodes.empty()) close();
    return simpleknotlist(nodes,cycles);
  }

  // Yield a path from the guide as represented here.
  path solve(bool cycles=false) {
    if (solved)
      return p;
    else {
      simpleknotlist l=list(cycles);
      p=camp::solve(l);
      solved=true;
      return p;
    }
  }
};

} // namespace camp

#endif // FLATGUIDE_H