1 /**
2   This module provides a 2D geometric path type. A Path is a 2D shape defined
3   by a sequence of line or curve segments. It can be open or closed, and can
4   have multiple sub paths.
5 
6   Build a path...
7 
8   ---
9   Path!float path;
10   path.moveTo(0,0);
11   path.lineTo(10,10);
12   path.quadTo(20,20,30,30);
13   path.close();
14   ---
15 
16   Commands can be chained...
17 
18   ---
19   path.moveTo(0,0).lineTo(10,10).quadTo(20,20,30,30).close();
20   ---
21 
22   You can use chained PathIterator adaptor functions...
23 
24   ---
25   DrawPath(path.offset(100,100).retro);
26   ---
27   
28   To modify path you use assignment, so for example to scale a path you assign a
29   scaled version of it to itself, the assign methods will check for self
30   assignment and do it in place if possible. 
31 
32   ---
33   path = path.retro.offset(10,10);
34   ---
35 
36   Copyright: Chris Jones
37   License: Boost Software License, Version 1.0
38   Authors: Chris Jones
39 */
40 
41 module dg2d.path;
42 
43 import dg2d.scalar;
44 import dg2d.point;
45 import dg2d.misc;
46 import dg2d.pathiterator;
47 import std.traits;
48 
49 /**
50   Defines the commands used to build a path.
51 */
52 
53 enum PathCmd : ubyte
54 {
55     empty = 0,        /// empty path, end of path, or error
56     move  = 1,        /// start a new (sub) path
57     line  = 2 | 128,  /// line 
58     quad  = 3 | 128,  /// quadratic curve
59     cubic = 4 | 128,  /// cubic bezier
60 }
61 
62 /** Number of points to advance for a given command */
63 
64 int advance(PathCmd cmd) { return cmd & 7; }
65 
66 /**
67   Is the command linked, IE does it use the end poin of the previous command
68   as its first point.
69 */
70 
71 int linked(PathCmd cmd) { return cmd >> 7; }
72 
73 /**
74   A 2D geometric path type.
75   
76   The path is built from a sequence of path commands like moveTo or lineTo 
77   etc. Each new command uses the previous end point as its first point except
78   for the moveTo command, that is used to start a new sub path. Each point in
79   a command is tagged with a command type. The shared point between two
80   commands is always tagged for the previous command.
81 */
82 
83 struct Path
84 {
85     // prevent Path from being passed by value
86     
87     @disable this(this); 
88 
89     /** Frees the memory used by the path. */
90 
91     ~this()
92     {
93         dg2dFree(m_points);
94         dg2dFree(m_cmds);
95     }
96 
97     /** Move to x,y */
98 
99     ref Path moveTo(Scalar x, Scalar y)
100     {
101         makeRoomFor(1);
102         m_cmds[m_length] = PathCmd.move;
103         m_points[m_length].x = x;
104         m_points[m_length].y = y;
105         m_lastMove = m_length;
106         m_length++;
107         return this;
108     }
109 
110     /** Move to point */
111 
112     ref Path moveTo(Point point)
113     {
114         return moveTo(point.x, point.y);
115     }
116 
117     /** Line to x,y */
118 
119     ref Path lineTo(Scalar x, Scalar y)
120     {
121         assert(m_lastMove >= 0);
122         makeRoomFor(1);
123         m_cmds[m_length] = PathCmd.line;
124         m_points[m_length].x = x;
125         m_points[m_length].y = y;
126         m_length++;
127         return this;
128     }
129 
130     /** Line to point */
131 
132     ref Path lineTo(Point point)
133     {
134         return lineTo(point.x, point.y);
135     }
136 
137     /** Close the current subpath. This draws a line back to the
138     previous move command. */ 
139 
140     ref Path close()
141     {
142         assert(m_lastMove >= 0);
143         makeRoomFor(1);
144         m_cmds[m_length] = PathCmd.line;
145         m_points[m_length].x = m_points[m_lastMove].x;
146         m_points[m_length].y = m_points[m_lastMove].y;
147         m_length++;
148         return this;
149     }
150 
151     /** Add a quadratic curve */
152 
153     ref Path quadTo(Scalar x1, Scalar y1, Scalar x2, Scalar y2)
154     {
155         assert(m_lastMove >= 0);
156         makeRoomFor(2);
157         m_cmds[m_length] = PathCmd.quad;
158         m_points[m_length].x = x1;
159         m_points[m_length].y = y1;
160         m_cmds[m_length+1] = PathCmd.quad;
161         m_points[m_length+1].x = x2;
162         m_points[m_length+1].y = y2;
163         m_length += 2;
164         return this;
165     }
166 
167     /** Add a cubic curve */
168 
169     ref Path cubicTo(Scalar x1, Scalar y1, Scalar x2, Scalar y2, Scalar x3, Scalar y3)
170     {
171         assert(m_lastMove >= 0);
172         makeRoomFor(3);
173         m_cmds[m_length] = PathCmd.cubic;
174         m_points[m_length].x = x1;
175         m_points[m_length].y = y1;
176         m_cmds[m_length+1] = PathCmd.cubic;
177         m_points[m_length+1].x = x2;
178         m_points[m_length+1].y = y2;
179         m_cmds[m_length+2] = PathCmd.cubic;
180         m_points[m_length+2].x = x3;
181         m_points[m_length+2].y = y3;
182         m_length += 3;
183         return this;
184     }
185 
186     /** Get the coordinates of last move, IE. the start of current sub path */
187 
188     Point lastMoveTo()
189     {
190         assert(m_lastMove >= 0);
191         return m_points[m_lastMove];
192     }
193 
194     /** get a slice of the path, need to be careful as this doesnt
195     do anything regarding aligning on full commands. */
196     
197     auto opSlice(size_t from, size_t to)
198     {
199         return slice(this, from, to);
200     }
201 
202     /** Copy rhs to this path, this handles self assignment, even if the
203     rhs is a bunch of adaptors on top of the same path */
204 
205     void opAssign(P)(auto ref P rhs)
206         if (isPathIterator!P)
207     {
208         // Same path and cant be done in place
209 
210         if ((rhs.source == &this) && (!rhs.inPlace))
211         {
212             Path temp;
213             temp.append(rhs);
214             swap(m_points, temp.m_points);
215             swap(m_cmds, temp.m_cmds);
216             m_length = temp.m_length;
217             m_capacity = temp.m_capacity;
218             m_lastMove = temp.m_lastMove;
219         }
220         else
221         {
222             // either not self assignment, or doesnt need special handling
223 
224             setCapacity(rhs.length);
225 
226             foreach(size_t i; 0..rhs.length)
227             {
228                 m_points[i] = rhs[i];
229                 m_cmds[i] = rhs.cmd(i);
230                 if (m_cmds[i] == PathCmd.move) m_lastMove = i;
231             }
232 
233             m_length = rhs.length;
234         }
235     }
236 
237     /** reset the path */
238 
239     void reset()
240     {
241         m_length = 0;
242     }
243 
244     /**  append to the path */
245 
246     void append(P)(auto ref P rhs)
247         if (isPathIterator!(P))
248     {
249         if (rhs.length == 0) return;
250         makeRoomFor(rhs.length);
251 
252         foreach(i; 0..rhs.length)
253         {
254             m_points[m_length+i] = rhs[i];
255             m_cmds[m_length+i] = rhs.cmd(i);
256             if (m_cmds[i] == PathCmd.move) m_lastMove = i;
257         }
258         m_length += rhs.length;
259     }
260 
261     /** get the point at given index */
262 
263     ref Point opIndex(size_t idx)
264     {
265         return m_points[idx];
266     }
267 
268     /** get the command at given index */
269 
270     PathCmd cmd(size_t idx)
271     {
272         assert(idx < m_length);
273         return m_cmds[idx];
274     }
275 
276     /** the length of the path in points */
277 
278     size_t length()
279     {
280         return m_length;
281     }
282 
283     /** Return the path source.
284     */
285 
286     void* source()
287     {
288         return &this;
289     }
290 
291     /** Can be modified in place?
292     */
293 
294     bool inPlace()
295     {
296         return true;
297     }
298 
299 private:
300 
301     // make sure theres enough room for "num" extra coords
302 
303     void makeRoomFor(size_t num)
304     {
305         size_t reqlen = m_length+num;
306         if (reqlen <= m_capacity) return;
307         setCapacity(reqlen);
308     }
309 
310     // set capacity
311 
312     void setCapacity(size_t newcap)
313     {
314         assert(newcap >= m_length);
315         newcap = roundUpPow2(newcap|31);
316         if (newcap == 0) assert(0); // overflowed
317         m_points = dg2dRealloc(m_points,newcap);
318         m_cmds = dg2dRealloc(m_cmds,newcap);
319         m_capacity = newcap;
320     }
321 
322     // member vars
323 
324     size_t m_length;
325     size_t m_capacity;
326     Point* m_points;
327     PathCmd* m_cmds;   
328     size_t m_lastMove = -1;
329 }
330