1 /** 2 This module provides path iterator related stuff. 3 4 Copyright: Chris Jones 5 License: Boost Software License, Version 1.0 6 Authors: Chris Jones 7 */ 8 9 module dg2d.pathiterator; 10 11 import dg2d.scalar; 12 import dg2d.point; 13 import dg2d.path; 14 import dg2d.misc; 15 import std.traits; 16 17 /** 18 Returns true if T is a PathIterator. Not exactly an iterator in traditional 19 sense, its an array like API but you still use it to iterate over the path. 20 21 T must have the following methods... 22 23 Point opIndex(size_t idx) 24 PathCmd cmd(size_t idx) 25 size_t length() 26 void* source() 27 bool inPlace() 28 */ 29 30 enum bool isPathIterator(T) = 31 is(typeof(T.opIndex(0)) == Point) 32 && is(typeof(T.cmd(0)) == PathCmd) 33 && is(typeof(T.length()) == size_t) 34 && is(typeof(T.source()) == void*) 35 && is(typeof(T.inPlace()) == bool); 36 37 /** 38 Returns a PathIterator slice of path[from..to]. 39 */ 40 41 auto slice(T)(auto ref T path, size_t from, size_t to) 42 if (isPathIterator!T) 43 { 44 struct SlicePath 45 { 46 public: 47 ref Point opIndex(size_t idx) 48 { 49 assert(idx < m_length); 50 return m_path.opIndex(m_start+idx); 51 } 52 Scalar cmd(size_t idx) 53 { 54 assert(idx < m_length); 55 return m_path.cmd(m_start+idx); 56 } 57 size_t length() 58 { 59 return m_length; 60 } 61 static if (__traits(isRef, path)) // grab path by pointer 62 { 63 this (ref T path, size_t from, size_t to) 64 { 65 assert(from <= to && to <= path.length); 66 m_path = &path; 67 m_start = from; 68 m_length = to-from; 69 } 70 private T* m_path; 71 } 72 else // grab path by value 73 { 74 this (T path, size_t from, size_t to) 75 { 76 assert(from <= to && to <= path.length); 77 m_path = path; 78 m_start = from; 79 m_length = to-from; 80 } 81 private T m_path; 82 } 83 private: 84 size_t m_start; 85 size_t m_length; 86 } 87 88 return SlicePath(path, from, to); 89 } 90 91 /** 92 Iterate the path one command / segment at a time. 93 94 The returned iterator has the following methods... 95 96 reset() - resets the iterator to the start of the path 97 next() - advance to the next command 98 PathCmd cmd() - the current command 99 Point opIndex(idx) - get segment coordinates 100 101 When you use [] / opIndex the index is for indexing into the current 102 segment, so if the current command is a line, 0 will be the first point, 103 1 will be the end point. A cubic curve command can be indexed 0,1,2 or 3. 104 It is bounds checked in debug mode. 105 106 When all the commands are exhausted cmd() will return PathCmd.empty 107 */ 108 109 auto segments(T)(auto ref T path) 110 { 111 struct SegmentsPath 112 { 113 void reset() 114 { 115 m_pos = 0; 116 m_segtype = (m_path.length > 0) ? m_path.cmd(0) : PathCmd.empty; 117 assert(m_segtype == PathCmd.empty || m_segtype == PathCmd.move); 118 } 119 void next() 120 { 121 m_pos += m_segtype.advance; 122 m_segtype = (m_pos < m_path.length) ? m_path.cmd(m_pos) : PathCmd.empty; 123 m_pos -= m_segtype.linked; 124 assert((m_pos+m_segtype.advance) <= m_path.length); 125 } 126 Point opIndex(size_t idx) 127 { 128 assert(idx < m_segtype.advance); 129 return m_path.opIndex(m_pos+idx); 130 } 131 PathCmd cmd() 132 { 133 return m_segtype; 134 } 135 static if (__traits(isRef, path)) // grab path by pointer 136 { 137 this(ref T path) 138 { 139 m_path = &path; 140 reset(); 141 } 142 private T* m_path; 143 } 144 else // grab path by value 145 { 146 this(Path path) 147 { 148 m_path = path; 149 reset(); 150 } 151 private T m_path; 152 } 153 private: 154 PathCmd m_segtype; 155 size_t m_pos; 156 } 157 158 return SegmentsPath(path); 159 } 160 161 /** 162 Offset the path by x,y. 163 */ 164 165 auto offset(T)(auto ref T path, Scalar x, Scalar y) 166 if (isPathIterator!T) 167 { 168 struct OffsetPath 169 { 170 public: 171 Point opIndex(size_t idx) 172 { 173 return Point(m_path.opIndex(idx).x+m_x,m_path.opIndex(idx).y+m_y); 174 } 175 PathCmd cmd(size_t idx) 176 { 177 return m_path.cmd(idx); 178 } 179 size_t length() { return m_path.length; } 180 void* source() { return m_path.source; } 181 bool inPlace() { return m_path.inPlace; } 182 private: 183 static if (__traits(isRef, path)) 184 private T* m_path; 185 else 186 private T m_path; 187 Scalar m_x; 188 Scalar m_y; 189 } 190 static if (__traits(isRef, path)) 191 return OffsetPath(&path, x, y); 192 else 193 return OffsetPath(path, x, y); 194 } 195 196 /** 197 Scale the path by sx,sy 198 */ 199 200 auto scale(T,F)(auto ref T path, F sx, F sy) 201 if (isPathIterator!T) 202 { 203 alias FloatType = typeof(T[0].x); 204 205 struct ScalePath 206 { 207 public: 208 Point!FloatType opIndex(size_t idx) 209 { 210 return Point!FloatType(m_path.opIndex(idx).x*m_sx,m_path.opIndex(idx).y*m_sy); 211 } 212 PathCmd cmd(size_t idx) 213 { 214 return m_path.cmd(idx); 215 } 216 size_t length() { return m_path.length; } 217 void* source() { return m_path.source; } 218 bool inPlace() { return m_path.inPlace; } 219 private: 220 static if (__traits(isRef, path)) 221 T* m_path; 222 else 223 T m_path; 224 FloatType m_sx; 225 FloatType m_sy; 226 } 227 static if (__traits(isRef, path)) 228 return ScalePath(&path, cast(FloatType) sx, cast(FloatType) sy); 229 else 230 return ScalePath(path, cast(FloatType) sx, cast(FloatType) sy); 231 } 232 233 /** 234 Scale the path by sx,sy relative to focus_x,focus_y 235 */ 236 237 auto scale(T,F)(auto ref T path, F sx, F sy, F focus_x, F focus_y) 238 if (isPathIterator!T) 239 { 240 alias FloatType = typeof(T[0].x); 241 242 struct ScalePath 243 { 244 public: 245 Point!FloatType opIndex(size_t idx) 246 { 247 return Point!FloatType( 248 (m_path.opIndex(idx).x-m_ctrx)*m_sx+m_ctrx, 249 (m_path.opIndex(idx).y-m_ctry)*m_sy+m_ctrx 250 ); 251 } 252 PathCmd cmd(size_t idx) 253 { 254 return m_path.cmd(idx); 255 } 256 size_t length() { return m_path.length; } 257 void* source() { return m_path.source; } 258 bool inPlace() { return m_path.inPlace; } 259 private: 260 static if (__traits(isRef, path)) 261 T* m_path; 262 else 263 T m_path; 264 FloatType m_sx,m_sy,m_ctrx,m_ctry; 265 } 266 static if (__traits(isRef, path)) 267 return ScalePath(&path, cast(FloatType) sx, cast(FloatType) sy, 268 cast(FloatType) focus_x, cast(FloatType) focus_y); 269 else 270 return ScalePath(path, cast(FloatType) sx, cast(FloatType) sy, 271 cast(FloatType) focus_x, cast(FloatType) focus_y); 272 } 273 274 /** 275 The path in reverse. 276 */ 277 278 auto retro(T)(auto ref T path) 279 if (isPathIterator!T) 280 { 281 struct RetroPath 282 { 283 public: 284 Point opIndex(size_t idx) 285 { 286 return m_path.opIndex(m_lastidx-idx); 287 } 288 PathCmd cmd(size_t idx) 289 { 290 if (idx == 0) 291 { 292 return PathCmd.move; 293 } 294 else 295 { 296 return m_path.cmd(path.length-idx); 297 } 298 } 299 size_t length() { return m_path.length; } 300 void* source() { return m_path.source; } 301 bool inPlace() { return false; } // cant be done in place 302 private: 303 static if (__traits(isRef, path)) 304 T* m_path; 305 else 306 T m_path; 307 size_t m_lastidx; 308 } 309 // (path.length-1) will wrap arround when path.length = 0 but 310 // theres no need to check for it because if length is zero 311 // then all possible indexes are invalid anyway 312 static if (__traits(isRef, path)) 313 return RetroPath(&path, path.length-1); 314 else 315 return RetroPath(path, path.length-1); 316 } 317 318 /** 319 Rotate the path around (0,0) 320 */ 321 322 auto rotate(T)(auto ref T path, Scalar angle) 323 if (isPathIterator!T) 324 { 325 struct RotatePath 326 { 327 public: 328 Point opIndex(size_t idx) 329 { 330 Scalar tx = m_path.opIndex(idx).x; 331 Scalar ty = m_path.opIndex(idx).y; 332 return Point(tx*m_cos-ty*m_sin, tx*m_sin+ty*m_cos); 333 } 334 PathCmd cmd(size_t idx) 335 { 336 return m_path.cmd(idx); 337 } 338 size_t length() { return m_path.length; } 339 void* source() { return m_path.source; } 340 bool inPlace() { return m_path.inPlace; } 341 private: 342 static if (__traits(isRef, path)) 343 T* m_path; 344 else 345 T m_path; 346 Scalar m_sin,m_cos; 347 } 348 349 import std.math; 350 351 static if (__traits(isRef, path)) 352 return RotatePath(&path, sin(angle*2*PI/360), cos(angle*2*PI/360)); 353 else 354 return RotatePath(path, sin(angle*2*PI/360), cos(angle*2*PI/360)); 355 } 356 357 /** 358 Rotate path around (x,y) 359 */ 360 361 auto rotate(T)(auto ref T path, Scalar pivot_x, Scalar pivot_y, Scalar angle) 362 if (isPathIterator!T) 363 { 364 struct RotatePath 365 { 366 public: 367 Point opIndex(size_t idx) 368 { 369 Scalar tx = m_path.opIndex(idx).x - m_px; 370 Scalar ty = m_path.opIndex(idx).y - m_py; 371 return Point(tx*m_cos-ty*m_sin+m_px, tx*m_sin+ty*m_cos+m_py); 372 } 373 PathCmd cmd(size_t idx) 374 { 375 return m_path.cmd(idx); 376 } 377 size_t length() { return m_path.length; } 378 void* source() { return m_path.source; } 379 bool inPlace() { return m_path.inPlace; } 380 private: 381 static if (__traits(isRef, path)) 382 T* m_path; 383 else 384 T m_path; 385 Scalar m_px,m_py,m_sin,m_cos; 386 } 387 import std.math; 388 static if (__traits(isRef, path)) 389 return RotatePath(&path, pivot_x, pivot_y, sin(angle*2*PI/360), cos(angle*2*PI/360)); 390 else 391 return RotatePath(path, pivot_x, pivot_y, sin(angle*2*PI/360), cos(angle*2*PI/360)); 392 } 393 394 /** 395 Calculate center of path. 396 397 This calculates the boudning box and returns the center of that. 398 */ 399 400 auto centerOf(T)(T path) 401 if (isPathIterator!T) 402 { 403 return path.boundingBox.center; 404 } 405 406 /** 407 Calculate the bounding box of path. 408 */ 409 410 auto boundingBox(T)(T path) 411 if (isPathIterator!T) 412 { 413 alias FloatType = typeof(T.opIndex(0).x); 414 Rect!FloatType bounds; 415 416 foreach(i; 0..path.length) 417 { 418 bounds.xMin = min(bounds.xMin,path[i].x); 419 bounds.xMax = max(bounds.xMax,path[i].x); 420 bounds.yMin = min(bounds.yMin,path[i].y); 421 bounds.yMax = max(bounds.yMax,path[i].y); 422 } 423 return bounds; 424 } 425 426 /** 427 Append two paths 428 */ 429 430 auto append(T,P)(auto ref T path0, auto ref P path1) 431 if (isPathIterator!T && isPathIterator!P) 432 { 433 struct AppendPaths 434 { 435 public: 436 Point opIndex(size_t idx) 437 { 438 if (idx < m_path0.length) return m_path0[idx]; 439 return m_path1[idx-m_path0.length]; 440 } 441 PathCmd cmd(size_t idx) 442 { 443 if (idx < m_path0.length) return m_path0.cmd(idx); 444 return m_path1.cmd(idx-m_path0.length); 445 } 446 size_t length() { return m_path0.length+m_path1.length; } 447 void* source() { return null; } 448 bool inPlace() { return false; } 449 private: 450 static if (__traits(isRef, path0)) 451 T* m_path0; 452 else 453 T m_path0; 454 static if (__traits(isRef, path1)) 455 P* m_path1; 456 else 457 P m_path1; 458 } 459 460 // probably a better way to do this?? 461 462 static if (__traits(isRef, path0)) 463 static if (__traits(isRef, path1)) 464 return AppendPaths(&path0, &path1); 465 else 466 return AppendPaths(&path0, path1); 467 else 468 static if (__traits(isRef, path1)) 469 return AppendPaths(path0, &path1); 470 else 471 return AppendPaths(path0, path1); 472 } 473 474