cosmetic: setf->setq
[cl-vectors.git] / paths.lisp
blobc36175bcc077866733b54dbe914394fd11b175ed
1 ;;;; cl-vectors -- Rasterizer and paths manipulation library
2 ;;;; Copyright (C) 2007 Frédéric Jolliton <frederic@jolliton.com>
3 ;;;;
4 ;;;; This library is free software; you can redistribute it and/or
5 ;;;; modify it under the terms of the Lisp Lesser GNU Public License
6 ;;;; (http://opensource.franz.com/preamble.html), known as the LLGPL.
7 ;;;;
8 ;;;; This library is distributed in the hope that it will be useful, but
9 ;;;; WITHOUT ANY WARRANTY; without even the implied warranty of
10 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Lisp
11 ;;;; Lesser GNU Public License for more details.
13 ;;;; This file provides facilities to create and manipulate vectorial paths.
15 #+nil(error "This file assume that #+NIL is never defined.")
17 (in-package #:net.tuxee.paths)
19 (defvar *bezier-distance-tolerance* 0.5
20 "The default distance tolerance used when rendering Bezier
21 curves.")
23 (defvar *bezier-angle-tolerance* 0.05
24 "The default angle tolerance (in radian) used when rendering
25 Bezier curves")
27 (defvar *arc-length-tolerance* 1.0
28 "The maximum length of segment describing an arc.")
30 (defvar *miter-limit* 4.0
31 "Miter limit before reverting to bevel joint. Must be >=1.0.")
33 ;;;--[ Math utilities ]------------------------------------------------------
35 ;;; http://mathworld.wolfram.com/Line-LineIntersection.html
36 (defun line-intersection (x1 y1 x2 y2
37 x3 y3 x4 y4)
38 "Compute the intersection between 2 lines (x1,y1)-(x2,y2)
39 and (x3,y3)-(x4,y4). Return the coordinates of the intersection
40 points as 2 values. If the 2 lines are colinears, return NIL."
41 (flet ((det (a b c d)
42 (- (* a d)
43 (* b c))))
44 (let* ((dx1 (- x2 x1))
45 (dy1 (- y2 y1))
46 (dx2 (- x4 x3))
47 (dy2 (- y4 y3))
48 (d (det dx2 dy2 dx1 dy1)))
49 (unless (zerop d)
50 (let ((a (det x1 y1 x2 y2))
51 (b (det x3 y3 x4 y4)))
52 (values (/ (det a dx1 b dx2) d)
53 (/ (det a dy1 b dy2) d)))))))
55 (defun line-intersection/delta (x1 y1 dx1 dy1
56 x2 y2 dx2 dy2)
57 "Compute the intersection between the line by (x1,y1) and
58 direction (dx1,dy1) and the line by (x2,y2) and
59 direction (dx2,dy2). Return the coordinates of the intersection
60 points as 2 values. If the 2 lines are colinears, return NIL."
61 (flet ((det (a b c d)
62 (- (* a d)
63 (* b c))))
64 (let ((d (det dx2 dy2 dx1 dy1)))
65 (unless (zerop d)
66 (let ((a (det x1 y1 (+ x1 dx1) (+ y1 dy1)))
67 (b (det x2 y2 (+ x2 dx2) (+ y2 dy2))))
68 (values (/ (det a dx1 b dx2) d)
69 (/ (det a dy1 b dy2) d)))))))
71 (defun normalize (x y &optional (length 1.0))
72 "Normalize the vector (X,Y) such that its length is LENGTH (or
73 1.0 if unspecified.) Return the component of the resulting vector
74 as 2 values. Return NIL if the input vector had a null length."
75 (if (zerop length)
76 (values 0.0 0.0)
77 (let ((norm (/ (sqrt (+ (* x x) (* y y))) length)))
78 (unless (zerop norm)
79 (values (/ x norm) (/ y norm))))))
81 (defun line-normal (x1 y1 x2 y2)
82 "Normalize the vector (X2-X1,Y2-Y1). See NORMALIZE."
83 (normalize (- x2 x1) (- y2 y1)))
85 ;;;--[ Points ]--------------------------------------------------------------
87 ;;; Points are supposed to be immutable
89 (declaim (inline make-point point-x point-y))
90 (defun make-point (x y) (cons x y))
91 (defun point-x (point) (car point))
92 (defun point-y (point) (cdr point))
94 ;;; Utility functions for points
96 (defun p+ (p1 p2)
97 (make-point (+ (point-x p1) (point-x p2))
98 (+ (point-y p1) (point-y p2))))
100 (defun p- (p1 p2)
101 (make-point (- (point-x p1) (point-x p2))
102 (- (point-y p1) (point-y p2))))
104 (defun p* (point scale &optional (scale-y scale))
105 (make-point (* (point-x point) scale)
106 (* (point-y point) scale-y)))
108 (defun point-rotate (point angle)
109 "Rotate POINT by ANGLE radian around the origin."
110 (let ((x (point-x point))
111 (y (point-y point)))
112 (make-point (- (* x (cos angle)) (* y (sin angle)))
113 (+ (* y (cos angle)) (* x (sin angle))))))
115 (defun point-angle (point)
116 "Compute the angle of POINT relatively to the X axis."
117 (atan (point-y point) (point-x point)))
119 (defun point-norm (point)
120 "Compute the distance of POINT from origin."
121 (sqrt (+ (expt (point-x point) 2)
122 (expt (point-y point) 2))))
124 ;; (point-norm (p- p2 p1))
125 (defun point-distance (p1 p2)
126 "Compute the distance between P1 and P2."
127 (sqrt (+ (expt (- (point-x p2) (point-x p1)) 2)
128 (expt (- (point-y p2) (point-y p1)) 2))))
130 ;; (p* (p+ p1 p2) 0.5)
131 (defun point-middle (p1 p2)
132 "Compute the point between P1 and P2."
133 (make-point (/ (+ (point-x p1) (point-x p2)) 2.0)
134 (/ (+ (point-y p1) (point-y p2)) 2.0)))
136 ;;;--[ Paths ]---------------------------------------------------------------
138 (defstruct path
139 (type :open-polyline :type (member :open-polyline :closed-polyline :polygon))
140 (orientation :unknown :type (member :unknown :cw :ccw))
141 (knots (make-array 0 :adjustable t :fill-pointer 0))
142 (interpolations (make-array 0 :adjustable t :fill-pointer 0)))
144 (defun create-path (type)
145 "Create a new path of the given type. The type must be one of
146 the following keyword:
148 :open-polyline -- An open polyline path,
149 :closed-polyline -- A closed polyline path,
150 :polygon -- Like :closed-polyline, but implicitly filled."
151 (assert (member type '(:open-polyline :closed-polyline :polygon)))
152 (make-path :type type))
154 (defun path-clear (path)
155 "Clear the path such that it is empty."
156 (setf (path-orientation path) :unknown
157 (fill-pointer (path-knots path)) 0
158 (fill-pointer (path-interpolations path)) 0))
160 (defun path-reset (path knot)
161 "Reset the path such that it is a single knot."
162 (path-clear path)
163 (vector-push-extend knot (path-knots path))
164 (vector-push-extend (make-straight-line) (path-interpolations path)))
166 (defun path-extend (path interpolation knot)
167 "Extend the path to KNOT, with INTERPOLATION."
168 (vector-push-extend interpolation (path-interpolations path))
169 (vector-push-extend knot (path-knots path))
170 ;; Extending the path can change how the orientation is
171 ;; auto-detected.
172 (setf (path-orientation path) :unknown))
174 (defun path-concatenate (path interpolation other-path)
175 "Append OTHER-PATH to PATH, joined by INTERPOLATION."
176 (let ((interpolations (path-interpolations other-path))
177 (knots (path-knots other-path)))
178 (loop for i below (length knots)
179 do (path-extend path
180 (interpolation-clone (if (and (zerop i) interpolation)
181 interpolation
182 (aref interpolations i)))
183 (aref knots i)))))
185 (defun path-replace (path other-path)
186 "Replace PATH with contents of OTHER-PATH."
187 (path-clear path)
188 (path-concatenate path nil other-path))
190 (defun path-size (path)
191 "Return the number of knots on the path."
192 (length (path-knots path)))
194 (defun path-last-knot (path)
195 "Return the last knot of the path. Return NIL if the path is
196 empty."
197 (let ((knots (path-knots path)))
198 (when (plusp (length knots))
199 (aref knots (1- (length knots))))))
201 (defun path-guess-orientation (path)
202 "Guess the orientation of the path.
204 This is implemented loosely because we don't take care about
205 interpolations. We only consider a polygon described by the
206 knots. However, it should work..
208 Update path orientation flag, and returns either :CW or :CCW."
209 (let ((knots (path-knots path)))
210 (let ((loose-area (loop for last-knot-index = (1- (length knots)) then knot-index
211 for knot-index below (length knots)
212 sum (- (* (point-x (aref knots last-knot-index))
213 (point-y (aref knots knot-index)))
214 (* (point-x (aref knots knot-index))
215 (point-y (aref knots last-knot-index)))))))
216 (setf (path-orientation path) (if (plusp loose-area) :ccw :cw)))))
218 (defun path-orient (path orientation &optional other-paths)
219 "Orient the path in the given orientation.
221 If OTHER-PATHS is specified, then the paths are reversed
222 inconditionnaly if PATH is also reversed."
223 (assert (member orientation '(:cw :ccw)) (orientation) "Expected either :CW or :CCW")
224 (when (eq (path-orientation path) :unknown)
225 (path-guess-orientation path))
226 (unless (eq (path-orientation path) orientation)
227 (path-reverse path)
228 (map nil #'path-reverse other-paths))
229 (values))
231 ;;; Iterators
233 (defgeneric path-iterator-reset (iterator)
234 (:documentation "Reset the iterator before the first knot."))
236 (defgeneric path-iterator-next (iterator)
237 (:documentation "Move the iterator to the next knot, and return
238 3 values: INTERPOLATION, KNOT and END-P. INTERPOLATION is the
239 interpolation between the previous knot and the current one. For
240 the first iteration, INTERPOLATION is usually the implicit
241 straight line between the last knot and the first knot. KNOT and
242 INTERPOLATION are null if the path is empty. END-P is true if the
243 knot is the last on the path or if the path is empty."))
245 (defun path-from-iterator (iterator type)
246 "Construct a new path from the given iterator."
247 (let ((path (create-path type)))
248 (loop
249 (multiple-value-bind (iterator knot end-p) (path-iterator-next iterator)
250 (path-extend path iterator knot)
251 (when end-p
252 (return path))))))
254 ;;; Classic iterator
256 (defstruct path-iterator-state
257 path index)
259 (defun path-iterator (path)
260 (make-path-iterator-state :path path :index nil))
262 (defmethod path-iterator-reset ((iterator path-iterator-state))
263 (setf (path-iterator-state-index iterator) nil))
265 (defmethod path-iterator-next ((iterator path-iterator-state))
266 (let* ((index (path-iterator-state-index iterator))
267 (path (path-iterator-state-path iterator))
268 (knots (path-knots path))
269 (interpolations (path-interpolations path)))
270 (cond
271 ((zerop (length knots))
272 (values nil nil t))
274 ;; Update index to the next place
275 (setf index
276 (setf (path-iterator-state-index iterator)
277 (if (null index) 0 (mod (1+ index) (length knots)))))
278 (values (aref interpolations index)
279 (aref knots index)
280 (= index (1- (length knots))))))))
282 ;;; Segmented iterator
284 ;;; This iterator iterate over segmented interpolation, if the
285 ;;; interpolation is matched by the predicate. This is useful for
286 ;;; algorithms that doesn't handle certain type of interpolations.
287 ;;; The predicate could test the type, but also certain type of
288 ;;; interpolation (such as arc of circle vs arc of ellipse, or degree
289 ;;; of the Bezier curves.)
291 ;;; Note: I use PI prefix instead of PATH-ITERATOR to shorten names.
293 (defstruct pi-segmented-state
294 path index predicate end-p queue)
296 (defun path-iterator-segmented (path &optional (predicate (constantly t)))
297 (make-pi-segmented-state :path path :index nil
298 :predicate predicate
299 :end-p nil :queue nil))
301 (defmethod path-iterator-reset ((iterator pi-segmented-state))
302 (setf (pi-segmented-state-index iterator) nil
303 (pi-segmented-state-queue iterator) nil))
305 (defmethod path-iterator-next ((iterator pi-segmented-state))
306 (flet ((update-queue (interpolation k1 k2 last-p)
307 (let (new-queue)
308 (interpolation-segment interpolation k1 k2 (lambda (p) (push p new-queue)))
309 (push k2 new-queue)
310 (setf (pi-segmented-state-end-p iterator) last-p
311 (pi-segmented-state-queue iterator) (nreverse new-queue))))
312 (dequeue ()
313 (let* ((knot (pop (pi-segmented-state-queue iterator)))
314 (end-p (and (pi-segmented-state-end-p iterator)
315 (null (pi-segmented-state-queue iterator)))))
316 (values (make-straight-line) knot (when end-p t)))))
317 (cond
318 ((pi-segmented-state-queue iterator)
319 ;; Queue is not empty, process it first.
320 (dequeue))
322 ;; Either refill the queue, or return the next straight line
323 ;; from the sub iterator.
324 (let* ((index (pi-segmented-state-index iterator))
325 (path (pi-segmented-state-path iterator))
326 (knots (path-knots path))
327 (interpolations (path-interpolations path)))
328 (cond
329 ((zerop (length knots))
330 ;; Empty path.
331 (values nil nil t))
333 ;; Update index to the next place
334 (setf index
335 (setf (pi-segmented-state-index iterator)
336 (if (null index) 0 (mod (1+ index) (length knots)))))
337 (let ((interpolation (aref interpolations index))
338 (knot (aref knots index))
339 (end-p (= index (1- (length knots)))))
340 ;; Check if we have to segment the next interpolation
341 (if (funcall (pi-segmented-state-predicate iterator)
342 interpolation)
343 (let ((previous-index (mod (1- index) (length knots))))
344 (update-queue interpolation
345 (aref knots previous-index)
346 knot end-p)
347 (dequeue))
348 (values interpolation knot end-p))))))))))
350 ;;; Iterate distinct
352 ;;; This iterator filter out identical knots. That is, the knots with
353 ;;; the same positions, with any interpolation. (All interpolations
354 ;;; currently implemented are empty when knot around them are not
355 ;;; distinct.)
357 ;;; When cyclic-p is true, the first knot of the iterator is the first
358 ;;; knot distinct from the first knot of the reference iterator.
360 ;;; When cyclic-p is false, the first knot of the iterator if the
361 ;;; first knot of the reference iterator, and if the path ends with a
362 ;;; knot which is not distinct from the first, it is kept.
364 (defclass filter-distinct-state ()
365 ((iterator :initarg :iterator)
366 (cyclic-p :initarg :cyclic-p)
367 (fixed :initarg :fixed)
368 (next :initarg :next)
369 (next-is-end-p)))
371 (defun filter-distinct (iterator &optional (preserve-cyclic-end-p nil))
372 (make-instance 'filter-distinct-state
373 :iterator iterator
374 :cyclic-p (not preserve-cyclic-end-p)
375 :fixed nil
376 :next nil))
378 (defmethod path-iterator-reset ((iterator filter-distinct-state))
379 (with-slots ((sub iterator) next next-is-end-p) iterator
380 (path-iterator-reset sub)
381 (setf next nil
382 next-is-end-p nil)))
384 (defmethod path-iterator-next ((iterator filter-distinct-state))
385 (with-slots ((sub iterator) cyclic-p fixed next next-is-end-p) iterator
386 (when fixed
387 ;; constant result cached
388 (return-from path-iterator-next (values-list fixed)))
389 (labels ((get-next ()
390 "Get the next knot information as a list (not as
391 multiple values)."
392 (multiple-value-list (path-iterator-next sub)))
393 (distinct-p (a b)
394 "Test if A and B have distinct knots."
395 (not (zerop (point-distance (second a) (second b)))))
396 (move-to-next (previous loop-p)
397 "Move iterator to find a knot distinct from the
398 PREVIOUS. Also indicate if the resulting knot is
399 the first of the sub iterator, and if end of path
400 was encountered. This is needed to compute the
401 effective END-P flag for the resulting iterator."
402 (loop
403 with first-p = (third previous)
404 with end-encountered-p = (third previous)
405 for current = (get-next)
406 until (or (distinct-p previous current)
407 (and (not loop-p) first-p))
408 do (setf first-p (third current))
409 when (third current)
410 do (setf end-encountered-p t)
411 finally (return (values current first-p end-encountered-p)))))
412 (let (result)
413 (unless next
414 ;; First time we iterate.
415 (setf next-is-end-p nil)
416 (let ((first (get-next)))
417 (cond
418 ((or (not (second first))
419 (third first))
420 ;; It was an empty path or a single knot path. Cache it
421 ;; and returns it for each further iterations.
422 (setf fixed first
423 result first))
424 (cyclic-p
425 (multiple-value-bind (first-in-cycle first-p end-p) (move-to-next first nil)
426 (declare (ignore first-p))
427 (cond
428 (end-p
429 (setf (third first) t
430 fixed first
431 result first))
433 (setf next first-in-cycle)))))
435 (setf next first)))))
436 (unless result
437 ;; We copy NEXT because we need to modify RESULT, and since
438 ;; NEXT is kept for the next iteration, we take care of not
439 ;; modifying it.
440 (setf result (copy-seq next)
441 (third result) next-is-end-p)
442 (multiple-value-bind (current first-p end-encountered-p) (move-to-next next cyclic-p)
443 (setf next current)
444 ;; Set end marker
445 (cond
446 (cyclic-p
447 (setf next-is-end-p first-p)
448 (when (and end-encountered-p (not first-p))
449 (setf (third result) t)))
451 (setf (third result) end-encountered-p)))))
452 (values-list result)))))
454 ;;; Misc
456 (defun path-clone (path)
457 (let ((new-interpolations (copy-seq (path-interpolations path))))
458 (loop for i below (length new-interpolations)
459 do (setf (aref new-interpolations i)
460 (interpolation-clone (aref new-interpolations i))))
461 (let ((new-path (create-path (path-type path))))
462 (setf (path-knots new-path) (copy-seq (path-knots path))
463 (path-interpolations new-path) new-interpolations
464 (path-orientation new-path) (path-orientation path))
465 new-path)))
467 (defun path-reverse (path)
468 ;; reverse the order of knots
469 (setf (path-knots path) (nreverse (path-knots path)))
470 ;; reverse the order of interpolations 1..n (not the first one,
471 ;; which is the implicit straight line.)
472 (loop with interpolations = (path-interpolations path)
473 with length = (length interpolations)
474 for i from 1 upto (floor (1- length) 2)
475 do (rotatef (aref interpolations i)
476 (aref interpolations (- length i))))
477 ;; reverse each interpolation
478 (loop for interpolation across (path-interpolations path)
479 do (interpolation-reverse interpolation))
480 (unless (eq (path-orientation path) :unknown)
481 (setf (path-orientation path) (ecase (path-orientation path)
482 (:cw :ccw)
483 (:ccw :cw))))
484 path)
486 (defun path-reversed (path)
487 (let ((new-path (path-clone path)))
488 (path-reverse new-path)
489 new-path))
491 (defmacro do-path ((path interpolation knot) &body body)
492 (let ((path-sym (gensym))
493 (knots (gensym))
494 (interpolations (gensym))
495 (index (gensym)))
496 `(symbol-macrolet ((,interpolation (aref ,interpolations ,index))
497 (,knot (aref ,knots ,index)))
498 (loop
499 with ,path-sym = ,path
500 with ,knots = (path-knots ,path-sym)
501 with ,interpolations = (path-interpolations ,path-sym)
502 for ,index below (length ,knots)
503 do (progn ,@body)))))
505 (defun path-translate (path vector)
506 "Translate the whole path accordingly to VECTOR."
507 (if (listp path)
508 (dolist (path-item path)
509 (path-translate path-item vector))
510 (unless (and (zerop (point-x vector))
511 (zerop (point-y vector)))
512 (do-path (path interpolation knot)
513 (setf knot (p+ knot vector))
514 (interpolation-translate interpolation vector))))
515 path)
517 (defun path-rotate (path angle &optional center)
518 "Rotate the whole path by ANGLE radian around CENTER (which is
519 the origin if unspecified.)"
520 (if (listp path)
521 (dolist (path-item path)
522 (path-rotate path-item angle center))
523 (unless (zerop angle)
524 (when center
525 (path-translate path (p* center -1.0)))
526 (do-path (path interpolation knot)
527 (setf knot (point-rotate knot angle))
528 (interpolation-rotate interpolation angle))
529 (when center
530 (path-translate path center))))
531 path)
533 (defun path-scale (path scale-x scale-y &optional center)
534 "Scale the whole path by (SCALE-X,SCALE-Y) from CENTER (which
535 is the origin if unspecified.) Warning: not all interpolations
536 support non uniform scaling (when scale-x /= scale-y)."
537 ;;; FIXME: What to do about path-orientation?
538 (if (listp path)
539 (dolist (path-item path)
540 (path-scale path-item scale-x scale-y center))
541 (progn
542 (when center
543 (path-translate path (p* center -1.0)))
544 (do-path (path interpolation knot)
545 (setf knot (p* knot scale-x scale-y))
546 (interpolation-scale interpolation scale-x scale-y))
547 (when center
548 (path-translate path center))
549 (when (minusp (* scale-x scale-y))
550 (path-reverse path))))
551 path)
553 (defun path-end-info (path side)
554 (when (>= (path-size path) 2)
555 (if (not side)
556 (values (aref (path-knots path) 0)
557 (interpolation-normal (aref (path-interpolations path) 1)
558 (aref (path-knots path) 0)
559 (aref (path-knots path) 1)
560 nil))
561 (let ((ks (length (path-knots path)))
562 (is (length (path-interpolations path))))
563 (values (aref (path-knots path) (1- ks))
564 (interpolation-normal (aref (path-interpolations path) (1- is))
565 (aref (path-knots path) (- is 2))
566 (aref (path-knots path) (- is 1))
567 t))))))
569 (defun path-transform-as-marker (path path-reference side &key (offset 0.0) (scale 1.0) (angle 0.0))
570 "Translate, rotate and scale PATH representing a marker such
571 that it is adapted to the PATH-REFERENCE. If SIDE is false, it is
572 placed at the start of the path, otherwise it is placed at the
573 end of the path."
574 (multiple-value-bind (knot normal) (path-end-info path-reference side)
575 (when knot
576 (path-rotate path (+ (/ pi -2) angle (point-angle normal)))
577 (path-scale path scale scale)
578 (path-translate path (p+ knot (p* normal offset)))
579 path)))
581 ;;;--[ Interpolations ]------------------------------------------------------
583 (defgeneric interpolation-segment (interpolation k1 k2 function)
584 (:documentation "Segment the path between K1 and K2 described
585 by the INTERPOLATION. Call FUNCTION for each generated point on
586 the interpolation path."))
588 (defgeneric interpolation-normal (interpolation k1 k2 side)
589 (:documentation "Compute the normal, going \"outside\" at
590 either K1 (if SIDE is false) or K2 (if SIDE is true). Return NIL
591 if the normal cannot be computed. Return a point otherwise."))
593 (defgeneric interpolation-clone (interpolation)
594 (:documentation "Duplicate INTERPOLATION."))
596 (defgeneric interpolation-reverse (interpolation)
597 (:documentation "Reverse the path described by INTERPOLATION
598 in-place."))
600 (defgeneric interpolation-reversed (interpolation)
601 (:method (interpolation)
602 (let ((cloned-interpolation (interpolation-clone interpolation)))
603 (interpolation-reversed cloned-interpolation)
604 cloned-interpolation))
605 (:documentation "Duplicate and reverse the INTERPOLATION."))
607 (defgeneric interpolation-translate (interpolation vector))
609 (defgeneric interpolation-rotate (interpolation angle))
611 (defgeneric interpolation-scale (interpolation scale-x scale-y))
613 ;;; Straight lines
615 (defun make-straight-line ()
616 :straight-line)
618 (defun straight-line-p (value)
619 (eq value :straight-line))
621 (defmethod interpolation-segment ((interpolation (eql :straight-line)) k1 k2 function)
622 (declare (ignore interpolation k1 k2 function)))
624 (defmethod interpolation-normal ((interpolation (eql :straight-line)) k1 k2 side)
625 (let* ((x1 (point-x k1))
626 (y1 (point-y k1))
627 (x2 (point-x k2))
628 (y2 (point-y k2))
629 (dx (- x2 x1))
630 (dy (- y2 y1))
631 (dist (sqrt (+ (expt dx 2) (expt dy 2)))))
632 (when (plusp dist)
633 (if side
634 (make-point (/ dx dist)
635 (/ dy dist))
636 (make-point (- (/ dx dist))
637 (- (/ dy dist)))))))
639 (defmethod interpolation-clone ((interpolation (eql :straight-line)))
640 (make-straight-line))
642 (defmethod interpolation-reverse ((interpolation (eql :straight-line)))
643 (declare (ignore interpolation)))
645 (defmethod interpolation-translate ((interpolation (eql :straight-line)) vector)
646 (declare (ignore interpolation vector)))
648 (defmethod interpolation-rotate ((interpolation (eql :straight-line)) angle)
649 (declare (ignore interpolation angle)))
651 (defmethod interpolation-scale ((interpolation (eql :straight-line)) scale-x scale-y)
652 (declare (ignore interpolation scale-x scale-y)))
654 ;;; Arc (SVG style)
656 (defclass arc ()
657 ((rx :initarg rx)
658 (ry :initarg ry)
659 (x-axis-rotation :initarg x-axis-rotation)
660 (large-arc-flag :initarg large-arc-flag) ; t = choose the longest arc, nil = choose the smallest arc
661 (sweep-flag :initarg sweep-flag))) ; t = arc on the right, nil = arc on the left
663 (defun make-arc (rx ry &key (x-axis-rotation 0.0) (large-arc-flag nil) (sweep-flag nil))
664 (make-instance 'arc
665 'rx rx
666 'ry ry
667 'x-axis-rotation x-axis-rotation
668 'large-arc-flag large-arc-flag
669 'sweep-flag sweep-flag))
671 (defun svg-arc-parameters/reverse (center rx ry rotation start-angle delta-angle)
672 "Conversion from center to endpoint parameterization of SVG arc.
674 Returns values P1, P2, LARGE-ARC-FLAG-P, SWEEP-FLAG-P."
675 (let ((p1 (point-rotate (make-point rx 0) start-angle))
676 (p2 (point-rotate (make-point rx 0) (+ start-angle delta-angle))))
677 (flet ((transform (p)
679 (point-rotate
680 (p* p 1.0 (/ rx ry))
681 rotation)
682 center)))
683 (values (transform p1) (transform p2)
684 (> (abs delta-angle) pi)
685 (plusp delta-angle)))))
687 (defun svg-arc-parameters (p1 p2 rx ry rotation large-arc-flag-p sweep-flag-p)
688 "Conversion from endpoint to center parameterization of SVG arc.
690 Returns values RC, RX, RY, START-ANGLE and DELTA-ANGLE, where RC is
691 the center of the ellipse, RX and RY are the normalized
692 radii (needed if scaling was necessary)."
693 (when (and (/= rx 0)
694 (/= ry 0))
695 ;; [SVG] "If rX or rY have negative signs, these are dropped; the
696 ;; absolute value is used instead."
697 (setf rx (abs rx)
698 ry (abs ry))
699 ;; normalize boolean value to nil/t
700 (setf large-arc-flag-p (when large-arc-flag-p t)
701 sweep-flag-p (when sweep-flag-p t))
702 ;; rp1 and rp2 are p1 and p2 into the coordinate system such
703 ;; that rotation is cancelled and ellipse ratio is 1 (a circle.)
704 (let* ((rp1 (p* (point-rotate p1 (- rotation)) 1.0 (/ rx ry)))
705 (rp2 (p* (point-rotate p2 (- rotation)) 1.0 (/ rx ry)))
706 (rm (point-middle rp1 rp2))
707 (drp1 (p- rm rp1))
708 (dist (point-norm drp1)))
709 (when (plusp dist)
710 (let ((diff-sq (- (expt rx 2) (expt dist 2)))
712 (cond
713 ((not (plusp diff-sq))
714 ;; a/ scale the arc if it is too small to touch the points
715 (setf ry (* dist (/ ry rx))
716 rx dist
717 rc rm))
719 ;; b/ otherwise compute the center of the circle
720 (let ((d (/ (sqrt diff-sq) dist)))
721 (unless (eq large-arc-flag-p sweep-flag-p)
722 (setf d (- d)))
723 (setf rc (make-point (+ (point-x rm) (* (point-y drp1) d))
724 (- (point-y rm) (* (point-x drp1) d)))))))
725 (let* ((start-angle (point-angle (p- rp1 rc)))
726 (end-angle (point-angle (p- rp2 rc)))
727 (delta-angle (- end-angle start-angle)))
728 (when (minusp delta-angle)
729 (incf delta-angle (* 2 pi)))
730 (unless sweep-flag-p
731 (decf delta-angle (* 2 pi)))
732 (values (point-rotate (p* rc 1.0 (/ ry rx)) rotation) rx ry start-angle delta-angle)))))))
734 (defmethod interpolation-segment ((interpolation arc) k1 k2 function)
735 (let ((rotation (slot-value interpolation 'x-axis-rotation)))
736 (multiple-value-bind (rc rx ry start-angle delta-angle)
737 (svg-arc-parameters k1 k2
738 (slot-value interpolation 'rx)
739 (slot-value interpolation 'ry)
740 rotation
741 (slot-value interpolation 'large-arc-flag)
742 (slot-value interpolation 'sweep-flag))
743 (when rc
744 (loop with n = (max 3 (* (max rx ry) (abs delta-angle)))
745 for i from 1 below n
746 for angle = (+ start-angle (/ (* delta-angle i) n))
747 for p = (p+ (point-rotate
749 (make-point (* rx (cos angle))
750 (* rx (sin angle)))
751 1.0 (/ ry rx))
752 rotation)
754 do (funcall function p))))))
756 (defmethod interpolation-normal ((interpolation arc) k1 k2 side)
757 (let ((rotation (slot-value interpolation 'x-axis-rotation)))
758 (multiple-value-bind (rc rx ry start-angle delta-angle)
759 (svg-arc-parameters k1 k2
760 (slot-value interpolation 'rx)
761 (slot-value interpolation 'ry)
762 rotation
763 (slot-value interpolation 'large-arc-flag)
764 (slot-value interpolation 'sweep-flag))
765 (flet ((adjust (normal)
766 (let* ((p (point-rotate (p* normal 1.0 (/ ry rx)) rotation))
767 (d (point-norm p)))
768 (when (plusp delta-angle)
769 (setf d (- d)))
770 (make-point (/ (point-x p) d) (/ (point-y p) d)))))
771 (when rc
772 (let ((end-angle (+ start-angle delta-angle)))
773 (adjust (if side
774 (make-point (sin end-angle)
775 (- (cos end-angle)))
776 (make-point (- (sin start-angle))
777 (cos start-angle))))))))))
779 (defmethod interpolation-clone ((interpolation arc))
780 (make-arc (slot-value interpolation 'rx)
781 (slot-value interpolation 'ry)
782 :x-axis-rotation (slot-value interpolation 'x-axis-rotation)
783 :large-arc-flag (slot-value interpolation 'large-arc-flag)
784 :sweep-flag (slot-value interpolation 'sweep-flag)))
786 (defmethod interpolation-reverse ((interpolation arc))
787 (setf (slot-value interpolation 'sweep-flag)
788 (not (slot-value interpolation 'sweep-flag))))
790 (defmethod interpolation-translate ((interpolation arc) vector)
791 (declare (ignore interpolation vector)))
793 (defmethod interpolation-rotate ((interpolation arc) angle)
794 (incf (slot-value interpolation 'x-axis-rotation) angle))
796 (defmethod interpolation-scale ((interpolation arc) scale-x scale-y)
797 ;; FIXME: Return :segment-me if scaling is not possible?
798 (assert (and (not (zerop scale-x))
799 (= scale-x scale-y)))
800 (with-slots (rx ry) interpolation
801 (setf rx (* rx scale-x)
802 ry (* ry scale-y))))
804 ;;; Catmull-Rom
806 (defclass catmull-rom ()
807 ((head
808 :initarg head)
809 (control-points
810 :initform (make-array 0)
811 :initarg control-points)
812 (queue
813 :initarg queue)))
815 (defun make-catmull-rom (head control-points queue)
816 (make-instance 'catmull-rom
817 'head head
818 'control-points (coerce control-points 'vector)
819 'queue queue))
821 (defmethod interpolation-segment ((interpolation catmull-rom) k1 k2 function)
822 (let* ((control-points (slot-value interpolation 'control-points))
823 (points (make-array (+ (length control-points) 4))))
824 (replace points control-points :start1 2)
825 (setf (aref points 0) (slot-value interpolation 'head)
826 (aref points 1) k1
827 (aref points (- (length points) 2)) k2
828 (aref points (- (length points) 1)) (slot-value interpolation 'queue))
829 (labels ((eval-catmull-rom (a b c d p)
830 ;; http://www.mvps.org/directx/articles/catmull/
831 (* 0.5
832 (+ (* 2 b)
833 (* (+ (- a) c) p)
834 (* (+ (* 2 a) (* -5 b) (* 4 c) (- d)) (expt p 2))
835 (* (+ (- a) (* 3 b) (* -3 c) d) (expt p 3))))))
836 (loop for s below (- (length points) 3)
837 for a = (aref points (+ s 0)) then b
838 for b = (aref points (+ s 1)) then c
839 for c = (aref points (+ s 2)) then d
840 for d = (aref points (+ s 3))
841 do (funcall function b)
842 (loop with n = 32
843 for i from 1 below n
844 for p = (/ (coerce i 'float) n)
845 for x = (eval-catmull-rom (point-x a)
846 (point-x b)
847 (point-x c)
848 (point-x d)
850 for y = (eval-catmull-rom (point-y a)
851 (point-y b)
852 (point-y c)
853 (point-y d)
855 do (funcall function (make-point x y)))
856 (funcall function c)))))
858 (defmethod interpolation-normal ((interpolation catmull-rom) k1 k2 side)
859 (with-slots (head control-points queue) interpolation
860 (let (a b)
861 (if (zerop (length control-points))
862 (if side
863 (setf a k1
864 b queue)
865 (setf a k2
866 b head))
867 (if side
868 (setf a (aref control-points (1- (length control-points)))
869 b queue)
870 (setf a (aref control-points 0)
871 b head)))
872 (let* ((x1 (point-x a))
873 (y1 (point-y a))
874 (x2 (point-x b))
875 (y2 (point-y b))
876 (dx (- x2 x1))
877 (dy (- y2 y1))
878 (dist (sqrt (+ (expt dx 2) (expt dy 2)))))
879 (when (plusp dist)
880 (make-point (/ dx dist)
881 (/ dy dist)))))))
883 (defmethod interpolation-clone ((interpolation catmull-rom))
884 (make-catmull-rom (slot-value interpolation 'head)
885 (copy-seq (slot-value interpolation 'control-points))
886 (slot-value interpolation 'queue)))
888 (defmethod interpolation-reverse ((interpolation catmull-rom))
889 (rotatef (slot-value interpolation 'head)
890 (slot-value interpolation 'queue))
891 (nreverse (slot-value interpolation 'control-points)))
893 (defmethod interpolation-translate ((interpolation catmull-rom) vector)
894 (with-slots (head control-points queue) interpolation
895 (setf head (p+ head vector)
896 queue (p+ queue vector))
897 (loop for i below (length control-points)
898 do (setf (aref control-points i) (p+ (aref control-points i) vector)))))
900 (defmethod interpolation-rotate ((interpolation catmull-rom) angle)
901 (with-slots (head control-points queue) interpolation
902 (setf head (point-rotate head angle)
903 queue (point-rotate queue angle))
904 (loop for i below (length control-points)
905 do (setf (aref control-points i) (point-rotate (aref control-points i) angle)))))
907 (defmethod interpolation-scale ((interpolation catmull-rom) scale-x scale-y)
908 (with-slots (head control-points queue) interpolation
909 (setf head (p* head scale-x scale-y)
910 queue (p* queue scale-x scale-y))
911 (loop for i below (length control-points)
912 do (setf (aref control-points i) (p* (aref control-points i)
913 scale-x scale-y)))))
915 ;;; Bezier curves
917 ;;; [http://www.fho-emden.de/~hoffmann/bezier18122002.pdf]
919 (defclass bezier ()
920 ((control-points
921 :initform (make-array 0)
922 :initarg control-points)))
924 (defun make-bezier-curve (control-points)
925 (make-instance 'bezier
926 'control-points (make-array (length control-points)
927 :initial-contents control-points)))
929 (defun split-bezier (points &optional (position 0.5))
930 "Split the Bezier curve described by POINTS at POSITION into
931 two Bezier curves of the same degree. Returns the curves as 2
932 values."
933 (let* ((size (length points))
934 (stack (make-array size))
935 (current points))
936 (setf (aref stack 0) points)
937 (loop for j from 1 below size
938 for next-size from (1- size) downto 1
939 do (let ((next (make-array next-size)))
940 (loop for i below next-size
941 for a = (aref current i)
942 for b = (aref current (1+ i))
943 do (setf (aref next i)
944 (make-point (+ (* (- 1.0 position) (point-x a))
945 (* position (point-x b)))
946 (+ (* (- 1.0 position) (point-y a))
947 (* position (point-y b))))))
948 (setf (aref stack j) next
949 current next)))
950 (let ((left (make-array (length points)))
951 (right (make-array (length points))))
952 (loop for i from 0 below size
953 for j from (1- size) downto 0
954 do (setf (aref left i) (aref (aref stack i) 0)
955 (aref right i) (aref (aref stack j) i)))
956 (values left right))))
958 (defun evaluate-bezier (points position)
959 "Evaluate the point at POSITION on the Bezier curve described
960 by POINTS."
961 (let* ((size (length points))
962 (temp (make-array (1- size))))
963 (loop for current = points then temp
964 for i from (length temp) downto 1
965 do (loop for j below i
966 for a = (aref current j)
967 for b = (aref current (1+ j))
968 do (setf (aref temp j)
969 (make-point (+ (* (- 1.0 position) (point-x a))
970 (* position (point-x b)))
971 (+ (* (- 1.0 position) (point-y a))
972 (* position (point-y b)))))))
973 (let ((p (aref temp 0)))
974 (values (point-x p) (point-y p)))))
976 (defun discrete-bezier-curve (points function
977 &key
978 (include-ends t)
979 (min-subdivide nil)
980 (max-subdivide 10)
981 (distance-tolerance *bezier-distance-tolerance*)
982 (angle-tolerance *bezier-angle-tolerance*))
983 "Subdivize Bezier curve up to certain criterions."
984 ;; FIXME: Handle cusps correctly!
985 (unless min-subdivide
986 (setf min-subdivide (floor (log (1+ (length points)) 2))))
987 (labels ((norm (a b)
988 (sqrt (+ (expt a 2) (expt b 2))))
989 (refine-bezier (points depth)
990 (let* ((a (aref points 0))
991 (b (aref points (1- (length points))))
992 (middle-straight (point-middle a b)))
993 (multiple-value-bind (bx by) (evaluate-bezier points 0.5)
994 (when (or (< depth min-subdivide)
995 (and (<= depth max-subdivide)
996 (or (> (norm (- bx (point-x middle-straight))
997 (- by (point-y middle-straight)))
998 distance-tolerance)
999 (> (abs (- (atan (- by (point-y a)) (- bx (point-x a)))
1000 (atan (- (point-y b) by) (- (point-x b) bx))))
1001 angle-tolerance))))
1002 (multiple-value-bind (a b) (split-bezier points 0.5)
1003 (refine-bezier a (1+ depth))
1004 (funcall function bx by)
1005 (refine-bezier b (1+ depth))))))))
1006 (when include-ends
1007 (let ((p (aref points 0)))
1008 (funcall function (point-x p) (point-y p))))
1009 (refine-bezier points 0)
1010 (when include-ends
1011 (let ((p (aref points (1- (length points)))))
1012 (funcall function (point-x p) (point-y p)))))
1013 (values))
1015 (defmethod interpolation-segment ((interpolation bezier) k1 k2 function)
1016 (with-slots (control-points) interpolation
1017 (let ((points (make-array (+ 2 (length control-points)))))
1018 (replace points control-points :start1 1)
1019 (setf (aref points 0) k1
1020 (aref points (1- (length points))) k2)
1021 (discrete-bezier-curve points
1022 (lambda (x y) (funcall function (make-point x y)))
1023 :include-ends nil))))
1025 (defmethod interpolation-normal ((interpolation bezier) k1 k2 side)
1026 (let ((control-points (slot-value interpolation 'control-points))
1027 a b)
1028 (if (zerop (length control-points))
1029 (if side
1030 (setf a k1
1031 b k2)
1032 (setf a k2
1033 b k1))
1034 (if side
1035 (setf a (aref control-points (1- (length control-points)))
1036 b k2)
1037 (setf a (aref control-points 0)
1038 b k1)))
1039 (let* ((x1 (point-x a))
1040 (y1 (point-y a))
1041 (x2 (point-x b))
1042 (y2 (point-y b))
1043 (dx (- x2 x1))
1044 (dy (- y2 y1))
1045 (dist (sqrt (+ (expt dx 2) (expt dy 2)))))
1046 (when (plusp dist)
1047 (make-point (/ dx dist)
1048 (/ dy dist))))))
1050 (defmethod interpolation-clone ((interpolation bezier))
1051 (let ((control-points (copy-seq (slot-value interpolation 'control-points))))
1052 (loop for i below (length control-points)
1053 do (setf (aref control-points i) (aref control-points i)))
1054 (make-bezier-curve control-points)))
1056 (defmethod interpolation-reverse ((interpolation bezier))
1057 (nreverse (slot-value interpolation 'control-points)))
1059 (defmethod interpolation-translate ((interpolation bezier) vector)
1060 (with-slots (control-points) interpolation
1061 (loop for i below (length control-points)
1062 do (setf (aref control-points i) (p+ (aref control-points i) vector)))))
1064 (defmethod interpolation-rotate ((interpolation bezier) angle)
1065 (with-slots (control-points) interpolation
1066 (loop for i below (length control-points)
1067 do (setf (aref control-points i) (point-rotate (aref control-points i) angle)))))
1069 (defmethod interpolation-scale ((interpolation bezier) scale-x scale-y)
1070 (with-slots (control-points) interpolation
1071 (loop for i below (length control-points)
1072 do (setf (aref control-points i) (p* (aref control-points i)
1073 scale-x scale-y)))))
1075 ;;;--[ Building paths ]------------------------------------------------------
1077 (defun make-discrete-path (path)
1078 "Construct a path with only straight lines."
1079 (let ((result (create-path (path-type path)))
1080 (knots (path-knots path))
1081 (interpolations (path-interpolations path)))
1082 (when (plusp (length knots))
1083 ;; nicer, but slower too.. (But not profiled. Premature optimization?)
1084 #+nil(loop with iterator = (path-iterator-segmented path)
1085 for (interpolation knot end-p) = (multiple-value-list (path-iterator-next iterator))
1086 do (path-extend result interpolation knot)
1087 until end-p)
1088 (path-reset result (aref knots 0))
1089 (loop
1090 for i below (1- (length knots))
1091 for k1 = (aref knots i)
1092 for k2 = (aref knots (1+ i))
1093 for interpolation = (aref interpolations (1+ i))
1094 do (interpolation-segment interpolation k1 k2
1095 (lambda (knot)
1096 (path-extend result
1097 (make-straight-line)
1098 knot)))
1099 do (path-extend result (make-straight-line) k2)
1100 finally (unless (eq (path-type path) :open-polyline)
1101 (interpolation-segment (aref interpolations 0) k2 (aref knots 0)
1102 (lambda (knot)
1103 (path-extend result
1104 (make-straight-line)
1105 knot))))))
1106 result))
1108 (defun make-circle-path (cx cy radius &optional (radius-y radius) (x-axis-rotation 0.0))
1109 "Construct a path to represent a circle centered at CX,CY of
1110 the specified RADIUS."
1111 ;; Note: We represent the circle with 2 arcs
1112 (let ((path (create-path :polygon)))
1113 (setf radius (abs radius)
1114 radius-y (abs radius-y))
1115 (when (= radius radius-y)
1116 (setf x-axis-rotation 0.0))
1117 (when (and (plusp radius) (plusp radius-y))
1118 (let* ((center (make-point cx cy))
1119 (p (point-rotate (make-point radius 0) x-axis-rotation))
1120 (left (p+ center p))
1121 (right (p- center p)))
1122 (path-extend path (make-arc radius radius-y :x-axis-rotation x-axis-rotation) left)
1123 (path-extend path (make-arc radius radius-y :x-axis-rotation x-axis-rotation) right)))
1124 path))
1126 (defun make-rectangle-path (x1 y1 x2 y2
1127 &key (round nil) (round-x nil) (round-y nil))
1128 ;; FIXME: Instead: center + width + height + rotation ?
1129 ;; FIXME: Round corners? (rx, ry)
1130 (when (> x1 x2)
1131 (rotatef x1 x2))
1132 (when (> y1 y2)
1133 (rotatef y1 y2))
1134 (let ((path (create-path :closed-polyline))
1135 (round-x (or round-x round))
1136 (round-y (or round-y round)))
1137 (cond
1138 ((and round-x (plusp round-x)
1139 round-y (plusp round-y))
1140 (path-reset path (make-point (+ x1 round-x) y1))
1141 (path-extend path (make-arc round-x round-y) (make-point x1 (+ y1 round-y)))
1142 (path-extend path (make-straight-line) (make-point x1 (- y2 round-y)))
1143 (path-extend path (make-arc round-x round-y) (make-point (+ x1 round-x) y2))
1144 (path-extend path (make-straight-line) (make-point (- x2 round-x) y2))
1145 (path-extend path (make-arc round-x round-y) (make-point x2 (- y2 round-y)))
1146 (path-extend path (make-straight-line) (make-point x2 (+ y1 round-y)))
1147 (path-extend path (make-arc round-x round-y) (make-point (- x2 round-x) y1)))
1149 (path-reset path (make-point x1 y1))
1150 (path-extend path (make-straight-line) (make-point x1 y2))
1151 (path-extend path (make-straight-line) (make-point x2 y2))
1152 (path-extend path (make-straight-line) (make-point x2 y1))))
1153 path))
1155 (defun make-rectangle-path/center (x y dx dy &rest args)
1156 (apply #'make-rectangle-path (- x dx) (- y dy) (+ x dx) (+ y dy) args))
1158 (defun make-regular-polygon-path (x y radius sides &optional (start-angle 0.0))
1159 (let ((path (create-path :closed-polyline)))
1160 (loop for i below sides
1161 for angle = (+ start-angle (/ (* i 2 pi) sides))
1162 do (path-extend path (make-straight-line)
1163 (make-point (+ x (* (cos angle) radius))
1164 (- y (* (sin angle) radius)))))
1165 path))
1167 (defun make-simple-path (points &optional (type :open-polyline))
1168 "Create a path with only straight line, by specifying only knots."
1169 (let ((path (create-path type)))
1170 (dolist (point points)
1171 (path-extend path (make-straight-line) point))
1172 path))
1174 ;;;--[ Transformations ]-----------------------------------------------------
1176 (defmacro define-for-multiple-paths (name-multiple name-single &optional documentation)
1177 "Define a new function named by NAME-MULTIPLE which accepts
1178 either a single path or a list of paths as input from a function
1179 named by NAME-SINGLE accepting only a single path and producing a
1180 list of paths."
1181 `(defun ,name-multiple (paths &rest args)
1182 ,@(when documentation (list documentation))
1183 (loop for path in (if (listp paths) paths (list paths))
1184 nconc (apply #',name-single path args))))
1186 ;;; Stroke
1188 (defun stroke-path/1 (path thickness
1189 &key (caps :butt) (joint :none) (inner-joint :none)
1190 assume-type)
1191 "Stroke the path."
1192 (setf thickness (abs thickness))
1193 (let ((half-thickness (/ thickness 2.0))
1194 target)
1195 ;; TARGET is the path updated by the function LINE-TO and
1196 ;; EXTEND-TO below.
1197 (labels ((filter-interpolation (interpolation)
1198 ;; We handle only straight-line and arc of circle. The
1199 ;; rest will be segmented.
1200 (not (or (straight-line-p interpolation)
1201 (and (typep interpolation 'arc)
1202 (= (slot-value interpolation 'rx)
1203 (slot-value interpolation 'ry))))))
1204 (det (a b c d)
1205 (- (* a d) (* b c)))
1206 (arc (model)
1207 "Make a new arc similar to MODEL but with a radius
1208 updated to match the stroke."
1209 (assert (= (slot-value model 'rx)
1210 (slot-value model 'ry)))
1211 (let ((shift (if (slot-value model 'sweep-flag)
1212 (- half-thickness)
1213 half-thickness)))
1214 (make-arc (+ (slot-value model 'rx) shift)
1215 (+ (slot-value model 'ry) shift)
1216 :sweep-flag (slot-value model 'sweep-flag)
1217 :large-arc-flag (slot-value model 'large-arc-flag))))
1218 (line-to (p)
1219 "Extend the path to knot P with a straight line."
1220 (path-extend target (make-straight-line) p))
1221 (extend-to (i p)
1222 "EXtend the path to knot P with the given interpolation."
1223 (path-extend target i p))
1224 (do-single (k1)
1225 "Produce the resulting path when the input path
1226 contains a single knot."
1227 (ecase caps
1228 (:butt
1229 nil)
1230 (:square
1231 (path-replace target
1232 (make-rectangle-path/center (point-x k1)
1233 (point-y k1)
1234 half-thickness
1235 half-thickness)))
1236 (:round
1237 (path-replace target
1238 (make-circle-path (point-x k1)
1239 (point-y k1)
1240 half-thickness)))))
1241 (do-first (k1 i2 k2)
1242 "Process the first interpolation."
1243 (let* ((normal (interpolation-normal i2 k1 k2 nil))
1244 (n (p* normal half-thickness))
1245 (d (point-rotate n (/ pi 2))))
1246 (ecase caps
1247 (:butt
1248 (line-to (p- k1 d)))
1249 (:square
1250 (line-to (p+ (p+ k1 d) n))
1251 (line-to (p+ (p- k1 d) n))
1252 (unless (straight-line-p i2)
1253 (line-to (p- k1 d))))
1254 (:round
1255 (extend-to (make-arc half-thickness half-thickness) (p- k1 d))))))
1256 (do-last (k1 i2 k2)
1257 "Process the last interpolation."
1258 (let* ((normal (interpolation-normal i2 k1 k2 t))
1259 (d (p* (point-rotate normal (/ pi 2)) half-thickness)))
1260 (cond
1261 ((typep i2 'arc)
1262 (extend-to (arc i2) (p+ k2 d)))
1263 ((straight-line-p i2)
1264 (unless (eq caps :square)
1265 (line-to (p+ k2 d))))
1267 (error "unexpected interpolation")))))
1268 (do-segment (k1 i2 k2 i3 k3)
1269 "Process intermediate interpolation."
1270 (let* ((normal-a (interpolation-normal i2 k1 k2 t))
1271 (normal-b (interpolation-normal i3 k2 k3 nil))
1272 (outer-p (plusp (det (point-x normal-a) (point-y normal-a)
1273 (point-x normal-b) (point-y normal-b))))
1274 (d-a (p* (point-rotate normal-a (/ pi 2)) half-thickness))
1275 (d-b (p* (point-rotate normal-b (/ pi -2)) half-thickness)))
1276 (cond
1277 ((and (not outer-p)
1278 (eq inner-joint :miter)
1279 (straight-line-p i2)
1280 (straight-line-p i3))
1281 ;; Miter inner joint between 2 straight lines
1282 (multiple-value-bind (xi yi)
1283 (line-intersection/delta
1284 (point-x (p+ k2 d-a)) (point-y (p+ k2 d-a))
1285 (point-x normal-a) (point-y normal-a)
1286 (point-x (p+ k2 d-b)) (point-y (p+ k2 d-b))
1287 (point-x normal-b) (point-y normal-b))
1288 (cond
1289 ((and xi
1290 (plusp (+ (* (- xi (point-x k1))
1291 (point-x normal-a))
1292 (* (- yi (point-y k1))
1293 (point-y normal-a))))
1294 (plusp (+ (* (- xi (point-x k3))
1295 (point-x normal-b))
1296 (* (- yi (point-y k3))
1297 (point-y normal-b)))))
1298 ;; ok, intersection point
1299 ;; is behind segments
1300 ;; ends
1301 (extend-to (make-straight-line) (make-point xi yi)))
1303 ;; revert to basic joint
1304 (line-to (p+ k2 d-a))
1305 (line-to (p+ k2 d-b))))))
1306 ((and outer-p
1307 (eq joint :miter)
1308 (straight-line-p i2)
1309 (straight-line-p i3))
1310 ;; Miter outer joint between 2 straight lines
1311 (multiple-value-bind (xi yi)
1312 (line-intersection/delta
1313 (point-x (p+ k2 d-a)) (point-y (p+ k2 d-a))
1314 (point-x normal-a) (point-y normal-a)
1315 (point-x (p+ k2 d-b)) (point-y (p+ k2 d-b))
1316 (point-x normal-b) (point-y normal-b))
1317 (let ((i (make-point xi yi)))
1318 (cond
1319 ((and xi
1320 (<= (point-distance i k2)
1321 (* half-thickness *miter-limit*)))
1322 (line-to (make-point xi yi)))
1324 ;; FIXME: Ugh. My math skill show its
1325 ;; limits. This is probably possible to
1326 ;; compute the same thing with less steps.
1327 (let* ((p (p+ k2 (point-middle d-a d-b)))
1328 (a (point-distance (p+ k2 d-a) i))
1329 (b (- (* half-thickness *miter-limit*)
1330 (point-distance k2 p)))
1331 (c (point-distance p i))
1332 (d (/ (* a b) c))
1333 (p1 (p+ (p+ k2 d-a) (p* normal-a d)))
1334 (p2 (p+ (p+ k2 d-b) (p* normal-b d))))
1335 (line-to p1)
1336 (line-to p2)))))))
1338 (extend-to (if (typep i2 'arc)
1339 (arc i2)
1340 (make-straight-line))
1341 (p+ k2 d-a))
1342 ;; joint
1343 (if outer-p
1344 (ecase joint
1345 ((:none :miter)
1346 (line-to (p+ k2 d-b)))
1347 (:round
1348 (extend-to (make-arc half-thickness half-thickness
1349 :sweep-flag nil)
1350 (p+ k2 d-b))))
1351 (ecase inner-joint
1352 ((:none :miter)
1353 (line-to (p+ k2 d-b)))
1354 (:round
1355 (extend-to (make-arc half-thickness half-thickness
1356 :sweep-flag t)
1357 (p+ k2 d-b)))))))))
1358 (do-contour-half (path new-target first-half-p)
1359 (setf target new-target)
1360 (let ((iterator (filter-distinct (path-iterator-segmented path #'filter-interpolation)
1361 t)))
1362 (flet ((next ()
1363 (path-iterator-next iterator)))
1364 (multiple-value-bind (i1 k1 e1) (next)
1365 (when k1
1366 (cond
1368 (when first-half-p
1369 (do-single k1)))
1371 ;; at least 2 knots
1372 (multiple-value-bind (i2 k2 e2) (next)
1373 (do-first k1 i2 k2)
1374 ;; rest of the path
1375 (unless e2
1376 (loop
1377 (multiple-value-bind (i3 k3 e3) (next)
1378 (do-segment k1 i2 k2 i3 k3)
1379 (shiftf i1 i2 i3)
1380 (shiftf k1 k2 k3)
1381 (when e3
1382 (return)))))
1383 (do-last k1 i2 k2)))))))))
1384 (do-contour-polygon (path new-target first-p)
1385 (setf target new-target)
1386 (let ((iterator (filter-distinct (path-iterator-segmented path #'filter-interpolation))))
1387 (flet ((next ()
1388 (path-iterator-next iterator)))
1389 (multiple-value-bind (i1 k1 e1) (next)
1390 (when k1
1391 (cond
1393 (when first-p
1394 (do-single k1)))
1396 ;; at least 2 knots
1397 (multiple-value-bind (i2 k2 e2) (next)
1398 ;; rest of the path
1399 (let (extra-iteration)
1400 (when e2
1401 (setf extra-iteration 2))
1402 (loop
1403 (multiple-value-bind (i3 k3 e3) (next)
1404 (when (and extra-iteration (zerop extra-iteration))
1405 (return))
1406 (do-segment k1 i2 k2 i3 k3)
1407 (shiftf i1 i2 i3)
1408 (shiftf k1 k2 k3)
1409 (cond
1410 (extra-iteration
1411 (decf extra-iteration))
1413 (setf extra-iteration 2)))))))))))))))
1414 (when (plusp half-thickness)
1415 (ecase (or assume-type (path-type path))
1416 (:open-polyline
1417 (let ((result (create-path :polygon)))
1418 (do-contour-half path result t)
1419 (do-contour-half (path-reversed path) result nil)
1420 (list result)))
1421 (:closed-polyline
1422 (let ((result-a (create-path :polygon))
1423 (result-b (create-path :polygon)))
1424 ;; FIXME: What happen for single knot path?
1425 (do-contour-polygon path result-a t)
1426 (do-contour-polygon (path-reversed path) result-b nil)
1427 (list result-a result-b)))
1428 (:polygon
1429 (let ((result (create-path :polygon)))
1430 (do-contour-polygon path result t)
1431 (list result))))))))
1433 (define-for-multiple-paths stroke-path stroke-path/1)
1435 ;;; Dash
1437 (defun dash-path/1 (path sizes &key (toggle-p nil) (cycle-index 0))
1438 "Dash path. If TOGGLE-P is true, segments of odd indices are
1439 kept, while if TOGGLE-P is false, segments of even indices are
1440 kept. CYCLE indicate where to cycle the SIZES once the end is
1441 reached."
1442 (assert (<= 0 cycle-index (1- (length sizes)))
1443 (cycle-index) "Invalid cycle index")
1444 (assert (loop for size across sizes never (minusp size))
1445 (sizes) "All sizes must be non-negative.")
1446 (assert (loop for size across sizes thereis (plusp size))
1447 (sizes) "At least one size must be positive.")
1448 (flet ((interpolation-filter (interpolation)
1449 (or (not (typep interpolation 'arc))
1450 (/= (slot-value interpolation 'rx)
1451 (slot-value interpolation 'ry)))))
1452 (let (result
1453 (current (create-path :open-polyline))
1454 (current-length 0.0)
1455 (toggle (not toggle-p))
1456 (index 0)
1457 (size (aref sizes 0))
1458 (iterator (path-iterator-segmented path #'interpolation-filter)))
1459 (flet ((flush ()
1460 (when toggle
1461 (push current result))
1462 (setf toggle (not toggle))
1463 (setf current (create-path :open-polyline)
1464 current-length 0.0)
1465 (incf index)
1466 (when (= index (length sizes))
1467 (setf index cycle-index))
1468 (setf size (aref sizes index)))
1469 (extend (interpolation knot length)
1470 (path-extend current interpolation knot)
1471 (incf current-length length)))
1472 (loop
1473 for previous-knot = nil then knot
1474 for stop-p = nil then end-p
1475 for (interpolation knot end-p) = (multiple-value-list (path-iterator-next iterator))
1476 if (not previous-knot)
1477 do (path-reset current knot)
1478 else
1479 do (etypecase interpolation
1480 ((eql :straight-line)
1481 (let* ((delta (p- knot previous-knot))
1482 (length (point-norm delta))
1483 (pos 0.0))
1484 (loop
1485 (let ((missing (- size current-length))
1486 (available (- length pos)))
1487 (when (> missing available)
1488 (extend (make-straight-line) knot available)
1489 (return))
1490 (incf pos missing)
1491 (let ((end (p+ previous-knot (p* delta (/ pos length)))))
1492 (extend (make-straight-line) end missing)
1493 (flush)
1494 (path-reset current end))))))
1495 (arc
1496 (with-slots (rx ry x-axis-rotation large-arc-flag sweep-flag) interpolation
1497 (assert (= rx ry))
1498 (multiple-value-bind (rc nrx nry start-angle delta-angle)
1499 (svg-arc-parameters previous-knot knot rx ry
1500 x-axis-rotation
1501 large-arc-flag
1502 sweep-flag)
1503 (let* ((length (* (abs delta-angle) nrx))
1504 (pos 0.0))
1505 (loop
1506 (let ((missing (- size current-length))
1507 (available (- length pos)))
1508 (when (> missing available)
1509 (extend (make-arc nrx nry
1510 :x-axis-rotation x-axis-rotation
1511 :large-arc-flag (>= (/ available nrx) pi)
1512 :sweep-flag sweep-flag)
1513 knot
1514 available)
1515 (return))
1516 (incf pos missing)
1517 (let ((end (p+
1518 (point-rotate (make-point nrx 0)
1519 (+ x-axis-rotation
1520 (if (plusp delta-angle)
1521 (+ start-angle (/ pos nrx))
1522 (- start-angle (/ pos nrx)))))
1523 rc)))
1524 (extend (make-arc nrx nry
1525 :x-axis-rotation x-axis-rotation
1526 :large-arc-flag (>= (/ missing nrx) pi)
1527 :sweep-flag sweep-flag)
1529 missing)
1530 (flush)
1531 (path-reset current end)))))))))
1532 until (if (eq (path-type path) :open-polyline) end-p stop-p))
1533 (flush))
1534 (nreverse result))))
1536 (define-for-multiple-paths dash-path dash-path/1)
1538 ;;; Clip path
1540 (defun clip-path/1 (path x y dx dy)
1541 (let (result
1542 (current (create-path (path-type path)))
1543 (iterator (path-iterator-segmented path)))
1544 (labels ((next ()
1545 (path-iterator-next iterator))
1546 (det (a b c d)
1547 (- (* a d) (* b c)))
1548 (inside-p (p)
1549 (plusp (det (- (point-x p) x)
1550 (- (point-y p) y)
1551 dx dy)))
1552 (clip-left (k1 k2)
1553 (let ((k1-inside-p (when (inside-p k1) t))
1554 (k2-inside-p (when (inside-p k2) t)))
1555 (when k1-inside-p
1556 (path-extend current (make-straight-line) k1))
1557 (when (not (eq k1-inside-p k2-inside-p))
1558 (multiple-value-bind (xi yi)
1559 (line-intersection/delta x y dx dy
1560 (point-x k1) (point-y k1)
1561 (- (point-x k2) (point-x k1))
1562 (- (point-y k2) (point-y k1)))
1563 (when xi
1564 (path-extend current (make-straight-line) (make-point xi yi))))))))
1565 (multiple-value-bind (i1 k1 e1) (next)
1566 (let ((first-knot k1))
1567 (when k1
1568 (cond
1570 (when (inside-p k1)
1571 (path-reset current k1)))
1573 (loop
1574 (multiple-value-bind (i2 k2 e2) (next)
1575 (clip-left k1 k2)
1576 (when e2
1577 (if (eq (path-type path) :open-polyline)
1578 (when (inside-p k2)
1579 (path-extend current (make-straight-line) k2))
1580 (clip-left k2 first-knot))
1581 (return))
1582 (setf i1 i2)
1583 (setf k1 k2)))))))))
1584 (push current result)
1585 result))
1587 (define-for-multiple-paths clip-path clip-path/1)
1589 (defun clip-path/path/1 (path limit)
1590 (let ((iterator (filter-distinct (path-iterator-segmented limit)))
1591 (result (list path)))
1592 (multiple-value-bind (i1 k1 e1) (path-iterator-next iterator)
1593 (declare (ignore i1))
1594 (when (and k1 (not e1))
1595 (let ((stop-p nil))
1596 (loop
1597 (multiple-value-bind (i2 k2 e2) (path-iterator-next iterator)
1598 (declare (ignore i2))
1599 (setq result (loop for path in result
1600 nconc (clip-path path
1601 (point-x k1) (point-y k1)
1602 (point-x (p- k2 k1)) (point-y (p- k2 k1)))))
1603 (when stop-p
1604 (return result))
1605 (when e2
1606 (setf stop-p t))
1607 (setf k1 k2))))))))
1609 (define-for-multiple-paths clip-path/path clip-path/path/1)
1612 ;;; Round path
1614 (defun round-path/1 (path &optional max-radius)
1615 (declare (ignore max-radius))
1616 (list path))
1618 (define-for-multiple-paths round-path round-path/1)