1 ;;;; cl-vectors -- Rasterizer and paths manipulation library
2 ;;;; Copyright (C) 2007 Frédéric Jolliton <frederic@jolliton.com>
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.
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.
17 ;;;; 2007-02-20: first release
19 #+nil
(error "This file assume that #+NIL is never defined.")
21 (in-package #:net.tuxee.paths
)
23 (defvar *bezier-distance-tolerance
* 0.5
24 "The default distance tolerance used when rendering Bezier
27 (defvar *bezier-angle-tolerance
* 0.05
28 "The default angle tolerance (in radian) used when rendering
31 (defvar *arc-length-tolerance
* 1.0
32 "The maximum length of segment describing an arc.")
34 (defvar *miter-limit
* 4.0
35 "Miter limit before reverting to bevel joint. Must be >=1.0.")
37 ;;;--[ Math utilities ]------------------------------------------------------
39 ;;; http://mathworld.wolfram.com/Line-LineIntersection.html
40 (defun line-intersection (x1 y1 x2 y2
42 "Compute the intersection between 2 lines (x1,y1)-(x2,y2)
43 and (x3,y3)-(x4,y4). Return the coordinates of the intersection
44 points as 2 values. If the 2 lines are colinears, return NIL."
48 (let* ((dx1 (- x2 x1
))
52 (d (det dx2 dy2 dx1 dy1
)))
54 (let ((a (det x1 y1 x2 y2
))
55 (b (det x3 y3 x4 y4
)))
56 (values (/ (det a dx1 b dx2
) d
)
57 (/ (det a dy1 b dy2
) d
)))))))
59 (defun line-intersection/delta
(x1 y1 dx1 dy1
61 "Compute the intersection between the line by (x1,y1) and
62 direction (dx1,dy1) and the line by (x2,y2) and
63 direction (dx2,dy2). Return the coordinates of the intersection
64 points as 2 values. If the 2 lines are colinears, return NIL."
68 (let ((d (det dx2 dy2 dx1 dy1
)))
70 (let ((a (det x1 y1
(+ x1 dx1
) (+ y1 dy1
)))
71 (b (det x2 y2
(+ x2 dx2
) (+ y2 dy2
))))
72 (values (/ (det a dx1 b dx2
) d
)
73 (/ (det a dy1 b dy2
) d
)))))))
75 (defun normalize (x y
&optional
(length 1.0))
76 "Normalize the vector (X,Y) such that its length is LENGTH (or
77 1.0 if unspecified.) Return the component of the resulting vector
78 as 2 values. Return NIL if the input vector had a null length."
81 (let ((norm (/ (sqrt (+ (* x x
) (* y y
))) length
)))
83 (values (/ x norm
) (/ y norm
))))))
85 (defun line-normal (x1 y1 x2 y2
)
86 "Normalize the vector (X2-X1,Y2-Y1). See NORMALIZE."
87 (normalize (- x2 x1
) (- y2 y1
)))
89 ;;;--[ Points ]--------------------------------------------------------------
91 ;;; Points are supposed to be immutable
93 (declaim (inline make-point point-x point-y
))
94 (defun make-point (x y
) (cons x y
))
95 (defun point-x (point) (car point
))
96 (defun point-y (point) (cdr point
))
98 ;;; Utility functions for points
101 (make-point (+ (point-x p1
) (point-x p2
))
102 (+ (point-y p1
) (point-y p2
))))
105 (make-point (- (point-x p1
) (point-x p2
))
106 (- (point-y p1
) (point-y p2
))))
108 (defun p* (point scale
&optional
(scale-y scale
))
109 (make-point (* (point-x point
) scale
)
110 (* (point-y point
) scale-y
)))
112 (defun point-rotate (point angle
)
113 "Rotate POINT by ANGLE radian around the origin."
114 (let ((x (point-x point
))
116 (make-point (- (* x
(cos angle
)) (* y
(sin angle
)))
117 (+ (* y
(cos angle
)) (* x
(sin angle
))))))
119 (defun point-angle (point)
120 "Compute the angle of POINT relatively to the X axis."
121 (atan (point-y point
) (point-x point
)))
123 (defun point-norm (point)
124 "Compute the distance of POINT from origin."
125 (sqrt (+ (expt (point-x point
) 2)
126 (expt (point-y point
) 2))))
128 ;; (point-norm (p- p2 p1))
129 (defun point-distance (p1 p2
)
130 "Compute the distance between P1 and P2."
131 (sqrt (+ (expt (- (point-x p2
) (point-x p1
)) 2)
132 (expt (- (point-y p2
) (point-y p1
)) 2))))
134 ;; (p* (p+ p1 p2) 0.5)
135 (defun point-middle (p1 p2
)
136 "Compute the point between P1 and P2."
137 (make-point (/ (+ (point-x p1
) (point-x p2
)) 2.0)
138 (/ (+ (point-y p1
) (point-y p2
)) 2.0)))
140 ;;;--[ Paths ]---------------------------------------------------------------
143 (type :open-polyline
:type
(member :open-polyline
:closed-polyline
:polygon
))
144 (orientation :unknown
:type
(member :unknown
:cw
:ccw
))
145 (knots (make-array 0 :adjustable t
:fill-pointer
0))
146 (interpolations (make-array 0 :adjustable t
:fill-pointer
0)))
148 (defun create-path (type)
149 "Create a new path of the given type. The type must be one of
150 the following keyword:
152 :open-polyline -- An open polyline path,
153 :closed-polyline -- A closed polyline path,
154 :polygon -- Like :closed-polyline, but implicitly filled."
155 (assert (member type
'(:open-polyline
:closed-polyline
:polygon
)))
156 (make-path :type type
))
158 (defun path-clear (path)
159 "Clear the path such that it is empty."
160 (setf (path-orientation path
) :unknown
161 (fill-pointer (path-knots path
)) 0
162 (fill-pointer (path-interpolations path
)) 0))
164 (defun path-reset (path knot
)
165 "Reset the path such that it is a single knot."
167 (vector-push-extend knot
(path-knots path
))
168 (vector-push-extend (make-straight-line) (path-interpolations path
)))
170 (defun path-extend (path interpolation knot
)
171 "Extend the path to KNOT, with INTERPOLATION."
172 (vector-push-extend interpolation
(path-interpolations path
))
173 (vector-push-extend knot
(path-knots path
))
174 ;; Extending the path can change how the orientation is
176 (setf (path-orientation path
) :unknown
))
178 (defun path-concatenate (path interpolation other-path
)
179 "Append OTHER-PATH to PATH, joined by INTERPOLATION."
180 (let ((interpolations (path-interpolations other-path
))
181 (knots (path-knots other-path
)))
182 (loop for i below
(length knots
)
184 (interpolation-clone (if (and (zerop i
) interpolation
)
186 (aref interpolations i
)))
189 (defun path-replace (path other-path
)
190 "Replace PATH with contents of OTHER-PATH."
192 (path-concatenate path nil other-path
))
194 (defun path-size (path)
195 "Return the number of knots on the path."
196 (length (path-knots path
)))
198 (defun path-last-knot (path)
199 "Return the last knot of the path. Return NIL if the path is
201 (let ((knots (path-knots path
)))
202 (when (plusp (length knots
))
203 (aref knots
(1- (length knots
))))))
205 (defun path-guess-orientation (path)
206 "Guess the orientation of the path.
208 This is implemented loosely because we don't take care about
209 interpolations. We only consider a polygon described by the
210 knots. However, it should work..
212 Update path orientation flag, and returns either :CW or :CCW."
213 (let ((knots (path-knots path
)))
214 (let ((loose-area (loop for last-knot-index
= (1- (length knots
)) then knot-index
215 for knot-index below
(length knots
)
216 sum
(- (* (point-x (aref knots last-knot-index
))
217 (point-y (aref knots knot-index
)))
218 (* (point-x (aref knots knot-index
))
219 (point-y (aref knots last-knot-index
)))))))
220 (setf (path-orientation path
) (if (plusp loose-area
) :ccw
:cw
)))))
222 (defun path-orient (path orientation
&optional other-paths
)
223 "Orient the path in the given orientation.
225 If OTHER-PATHS is specified, then the paths are reversed
226 inconditionnaly if PATH is also reversed."
227 (assert (member orientation
'(:cw
:ccw
)) (orientation) "Expected either :CW or :CCW")
228 (when (eq (path-orientation path
) :unknown
)
229 (path-guess-orientation path
))
230 (unless (eq (path-orientation path
) orientation
)
232 (map nil
#'path-reverse other-paths
))
237 (defgeneric path-iterator-reset
(iterator)
238 (:documentation
"Reset the iterator before the first knot."))
240 (defgeneric path-iterator-next
(iterator)
241 (:documentation
"Move the iterator to the next knot, and return
242 3 values: INTERPOLATION, KNOT and END-P. INTERPOLATION is the
243 interpolation between the previous knot and the current one. For
244 the first iteration, INTERPOLATION is usually the implicit
245 straight line between the last knot and the first knot. KNOT and
246 INTERPOLATION are null if the path is empty. END-P is true if the
247 knot is the last on the path or if the path is empty."))
249 (defun path-from-iterator (iterator type
)
250 "Construct a new path from the given iterator."
251 (let ((path (create-path type
)))
253 (multiple-value-bind (iterator knot end-p
) (path-iterator-next iterator
)
254 (path-extend path iterator knot
)
260 (defstruct path-iterator-state
263 (defun path-iterator (path)
264 (make-path-iterator-state :path path
:index nil
))
266 (defmethod path-iterator-reset ((iterator path-iterator-state
))
267 (setf (path-iterator-state-index iterator
) nil
))
269 (defmethod path-iterator-next ((iterator path-iterator-state
))
270 (let* ((index (path-iterator-state-index iterator
))
271 (path (path-iterator-state-path iterator
))
272 (knots (path-knots path
))
273 (interpolations (path-interpolations path
)))
275 ((zerop (length knots
))
278 ;; Update index to the next place
280 (setf (path-iterator-state-index iterator
)
281 (if (null index
) 0 (mod (1+ index
) (length knots
)))))
282 (values (aref interpolations index
)
284 (= index
(1- (length knots
))))))))
286 ;;; Segmented iterator
288 ;;; This iterator iterate over segmented interpolation, if the
289 ;;; interpolation is matched by the predicate. This is useful for
290 ;;; algorithms that doesn't handle certain type of interpolations.
291 ;;; The predicate could test the type, but also certain type of
292 ;;; interpolation (such as arc of circle vs arc of ellipse, or degree
293 ;;; of the Bezier curves.)
295 ;;; Note: I use PI prefix instead of PATH-ITERATOR to shorten names.
297 (defstruct pi-segmented-state
298 path index predicate end-p queue
)
300 (defun path-iterator-segmented (path &optional
(predicate (constantly t
)))
301 (make-pi-segmented-state :path path
:index nil
303 :end-p nil
:queue nil
))
305 (defmethod path-iterator-reset ((iterator pi-segmented-state
))
306 (setf (pi-segmented-state-index iterator
) nil
307 (pi-segmented-state-queue iterator
) nil
))
309 (defmethod path-iterator-next ((iterator pi-segmented-state
))
310 (flet ((update-queue (interpolation k1 k2 last-p
)
312 (interpolation-segment interpolation k1 k2
(lambda (p) (push p new-queue
)))
314 (setf (pi-segmented-state-end-p iterator
) last-p
315 (pi-segmented-state-queue iterator
) (nreverse new-queue
))))
317 (let* ((knot (pop (pi-segmented-state-queue iterator
)))
318 (end-p (and (pi-segmented-state-end-p iterator
)
319 (null (pi-segmented-state-queue iterator
)))))
320 (values (make-straight-line) knot
(when end-p t
)))))
322 ((pi-segmented-state-queue iterator
)
323 ;; Queue is not empty, process it first.
326 ;; Either refill the queue, or return the next straight line
327 ;; from the sub iterator.
328 (let* ((index (pi-segmented-state-index iterator
))
329 (path (pi-segmented-state-path iterator
))
330 (knots (path-knots path
))
331 (interpolations (path-interpolations path
)))
333 ((zerop (length knots
))
337 ;; Update index to the next place
339 (setf (pi-segmented-state-index iterator
)
340 (if (null index
) 0 (mod (1+ index
) (length knots
)))))
341 (let ((interpolation (aref interpolations index
))
342 (knot (aref knots index
))
343 (end-p (= index
(1- (length knots
)))))
344 ;; Check if we have to segment the next interpolation
345 (if (funcall (pi-segmented-state-predicate iterator
)
347 (let ((previous-index (mod (1- index
) (length knots
))))
348 (update-queue interpolation
349 (aref knots previous-index
)
352 (values interpolation knot end-p
))))))))))
356 ;;; This iterator filter out identical knots. That is, the knots with
357 ;;; the same positions, with any interpolation. (All interpolations
358 ;;; currently implemented are empty when knot around them are not
361 ;;; When cyclic-p is true, the first knot of the iterator is the first
362 ;;; knot distinct from the first knot of the reference iterator.
364 ;;; When cyclic-p is false, the first knot of the iterator if the
365 ;;; first knot of the reference iterator, and if the path ends with a
366 ;;; knot which is not distinct from the first, it is kept.
368 (defclass filter-distinct-state
()
369 ((iterator :initarg
:iterator
)
370 (cyclic-p :initarg
:cyclic-p
)
371 (fixed :initarg
:fixed
)
372 (next :initarg
:next
)
375 (defun filter-distinct (iterator &optional
(preserve-cyclic-end-p nil
))
376 (make-instance 'filter-distinct-state
378 :cyclic-p
(not preserve-cyclic-end-p
)
382 (defmethod path-iterator-reset ((iterator filter-distinct-state
))
383 (with-slots ((sub iterator
) next next-is-end-p
) iterator
384 (path-iterator-reset sub
)
388 (defmethod path-iterator-next ((iterator filter-distinct-state
))
389 (with-slots ((sub iterator
) cyclic-p fixed next next-is-end-p
) iterator
391 ;; constant result cached
392 (return-from path-iterator-next
(values-list fixed
)))
393 (labels ((get-next ()
394 "Get the next knot information as a list (not as
396 (multiple-value-list (path-iterator-next sub
)))
398 "Test if A and B have distinct knots."
399 (not (zerop (point-distance (second a
) (second b
)))))
400 (move-to-next (previous loop-p
)
401 "Move iterator to find a knot distinct from the
402 PREVIOUS. Also indicate if the resulting knot is
403 the first of the sub iterator, and if end of path
404 was encountered. This is needed to compute the
405 effective END-P flag for the resulting iterator."
407 with first-p
= (third previous
)
408 with end-encountered-p
= (third previous
)
409 for current
= (get-next)
410 until
(or (distinct-p previous current
)
411 (and (not loop-p
) first-p
))
412 do
(setf first-p
(third current
))
414 do
(setf end-encountered-p t
)
415 finally
(return (values current first-p end-encountered-p
)))))
418 ;; First time we iterate.
419 (setf next-is-end-p nil
)
420 (let ((first (get-next)))
422 ((or (not (second first
))
424 ;; It was an empty path or a single knot path. Cache it
425 ;; and returns it for each further iterations.
429 (multiple-value-bind (first-in-cycle first-p end-p
) (move-to-next first nil
)
430 (declare (ignore first-p
))
433 (setf (third first
) t
437 (setf next first-in-cycle
)))))
439 (setf next first
)))))
441 ;; We copy NEXT because we need to modify RESULT, and since
442 ;; NEXT is kept for the next iteration, we take care of not
444 (setf result
(copy-seq next
)
445 (third result
) next-is-end-p
)
446 (multiple-value-bind (current first-p end-encountered-p
) (move-to-next next cyclic-p
)
451 (setf next-is-end-p first-p
)
452 (when (and end-encountered-p
(not first-p
))
453 (setf (third result
) t
)))
455 (setf (third result
) end-encountered-p
)))))
456 (values-list result
)))))
460 (defun path-clone (path)
461 (let ((new-interpolations (copy-seq (path-interpolations path
))))
462 (loop for i below
(length new-interpolations
)
463 do
(setf (aref new-interpolations i
)
464 (interpolation-clone (aref new-interpolations i
))))
465 (let ((new-path (create-path (path-type path
))))
466 (setf (path-knots new-path
) (copy-seq (path-knots path
))
467 (path-interpolations new-path
) new-interpolations
468 (path-orientation new-path
) (path-orientation path
))
471 (defun path-reverse (path)
472 ;; reverse the order of knots
473 (setf (path-knots path
) (nreverse (path-knots path
)))
474 ;; reverse the order of interpolations 1..n (not the first one,
475 ;; which is the implicit straight line.)
476 (loop with interpolations
= (path-interpolations path
)
477 with length
= (length interpolations
)
478 for i from
1 upto
(floor (1- length
) 2)
479 do
(rotatef (aref interpolations i
)
480 (aref interpolations
(- length i
))))
481 ;; reverse each interpolation
482 (loop for interpolation across
(path-interpolations path
)
483 do
(interpolation-reverse interpolation
))
484 (unless (eq (path-orientation path
) :unknown
)
485 (setf (path-orientation path
) (ecase (path-orientation path
)
490 (defun path-reversed (path)
491 (let ((new-path (path-clone path
)))
492 (path-reverse new-path
)
495 (defmacro do-path
((path interpolation knot
) &body body
)
496 (let ((path-sym (gensym))
498 (interpolations (gensym))
500 `(symbol-macrolet ((,interpolation
(aref ,interpolations
,index
))
501 (,knot
(aref ,knots
,index
)))
503 with
,path-sym
= ,path
504 with
,knots
= (path-knots ,path-sym
)
505 with
,interpolations
= (path-interpolations ,path-sym
)
506 for
,index below
(length ,knots
)
507 do
(progn ,@body
)))))
509 (defun path-translate (path vector
)
510 "Translate the whole path accordingly to VECTOR."
511 (unless (and (zerop (point-x vector
))
512 (zerop (point-y vector
)))
513 (do-path (path interpolation knot
)
514 (setf knot
(p+ knot vector
))
515 (interpolation-translate interpolation vector
)))
518 (defun path-rotate (path angle
&optional center
)
519 "Rotate the whole path by ANGLE radian around CENTER (which is
520 the origin if unspecified.)"
521 (unless (zerop angle
)
523 (path-translate path
(p* center -
1.0)))
524 (do-path (path interpolation knot
)
525 (setf knot
(point-rotate knot angle
))
526 (interpolation-rotate interpolation angle
))
528 (path-translate path center
)))
531 (defun path-scale (path scale-x scale-y
&optional center
)
532 "Scale the whole path by (SCALE-X,SCALE-Y) from CENTER (which
533 is the origin if unspecified.) Warning: not all interpolations
534 support non uniform scaling (when scale-x /= scale-y)."
536 (path-translate path
(p* center -
1.0)))
537 (do-path (path interpolation knot
)
538 (setf knot
(p* knot scale-x scale-y
))
539 (interpolation-scale interpolation scale-x scale-y
))
541 (path-translate path center
))
542 (when (minusp (* scale-x scale-y
))
546 ;;;--[ Interpolations ]------------------------------------------------------
548 (defgeneric interpolation-segment
(interpolation k1 k2 function
)
549 (:documentation
"Segment the path between K1 and K2 described
550 by the INTERPOLATION. Call FUNCTION for each generated point on
551 the interpolation path."))
553 (defgeneric interpolation-normal
(interpolation k1 k2 side
)
554 (:documentation
"Compute the normal, going \"outside\" at
555 either K1 (if SIDE is false) or K2 (if SIDE is true). Return NIL
556 if the normal cannot be computed. Return a point otherwise."))
558 (defgeneric interpolation-clone
(interpolation)
559 (:documentation
"Duplicate INTERPOLATION."))
561 (defgeneric interpolation-reverse
(interpolation)
562 (:documentation
"Reverse the path described by INTERPOLATION
565 (defgeneric interpolation-reversed
(interpolation)
566 (:method
(interpolation)
567 (let ((cloned-interpolation (interpolation-clone interpolation
)))
568 (interpolation-reversed cloned-interpolation
)
569 cloned-interpolation
))
570 (:documentation
"Duplicate and reverse the INTERPOLATION."))
572 (defgeneric interpolation-translate
(interpolation vector
))
574 (defgeneric interpolation-rotate
(interpolation angle
))
576 (defgeneric interpolation-scale
(interpolation scale-x scale-y
))
580 (defun make-straight-line ()
583 (defun straight-line-p (value)
584 (eq value
:straight-line
))
586 (defmethod interpolation-segment ((interpolation (eql :straight-line
)) k1 k2 function
)
587 (declare (ignore interpolation k1 k2 function
)))
589 (defmethod interpolation-normal ((interpolation (eql :straight-line
)) k1 k2 side
)
590 (let* ((x1 (point-x k1
))
596 (dist (sqrt (+ (expt dx
2) (expt dy
2)))))
599 (make-point (/ dx dist
)
601 (make-point (- (/ dx dist
))
604 (defmethod interpolation-clone ((interpolation (eql :straight-line
)))
605 (make-straight-line))
607 (defmethod interpolation-reverse ((interpolation (eql :straight-line
)))
608 (declare (ignore interpolation
)))
610 (defmethod interpolation-translate ((interpolation (eql :straight-line
)) vector
)
611 (declare (ignore interpolation vector
)))
613 (defmethod interpolation-rotate ((interpolation (eql :straight-line
)) angle
)
614 (declare (ignore interpolation angle
)))
616 (defmethod interpolation-scale ((interpolation (eql :straight-line
)) scale-x scale-y
)
617 (declare (ignore interpolation scale-x scale-y
)))
624 (x-axis-rotation :initarg x-axis-rotation
)
625 (large-arc-flag :initarg large-arc-flag
) ; t = choose the longest arc, nil = choose the smallest arc
626 (sweep-flag :initarg sweep-flag
))) ; t = arc on the right, nil = arc on the left
628 (defun make-arc (rx ry
&key
(x-axis-rotation 0.0) (large-arc-flag nil
) (sweep-flag nil
))
632 'x-axis-rotation x-axis-rotation
633 'large-arc-flag large-arc-flag
634 'sweep-flag sweep-flag
))
636 (defun svg-arc-parameters/reverse
(center rx ry rotation start-angle delta-angle
)
637 "Conversion from center to endpoint parameterization of SVG arc.
639 Returns values P1, P2, LARGE-ARC-FLAG-P, SWEEP-FLAG-P."
640 (let ((p1 (point-rotate (make-point rx
0) start-angle
))
641 (p2 (point-rotate (make-point rx
0) (+ start-angle delta-angle
))))
642 (flet ((transform (p)
648 (values (transform p1
) (transform p2
)
649 (> (abs delta-angle
) pi
)
650 (plusp delta-angle
)))))
652 (defun svg-arc-parameters (p1 p2 rx ry rotation large-arc-flag-p sweep-flag-p
)
653 "Conversion from endpoint to center parameterization of SVG arc.
655 Returns values RC, RX, RY, START-ANGLE and DELTA-ANGLE, where RC is
656 the center of the ellipse, RX and RY are the normalized
657 radii (needed if scaling was necessary)."
660 ;; [SVG] "If rX or rY have negative signs, these are dropped; the
661 ;; absolute value is used instead."
664 ;; normalize boolean value to nil/t
665 (setf large-arc-flag-p
(when large-arc-flag-p t
)
666 sweep-flag-p
(when sweep-flag-p t
))
667 ;; rp1 and rp2 are p1 and p2 into the coordinate system such
668 ;; that rotation is cancelled and ellipse ratio is 1 (a circle.)
669 (let* ((rp1 (p* (point-rotate p1
(- rotation
)) 1.0 (/ rx ry
)))
670 (rp2 (p* (point-rotate p2
(- rotation
)) 1.0 (/ rx ry
)))
671 (rm (point-middle rp1 rp2
))
673 (dist (point-norm drp1
)))
675 (let ((diff-sq (- (expt rx
2) (expt dist
2)))
678 ((not (plusp diff-sq
))
679 ;; a/ scale the arc if it is too small to touch the points
680 (setf ry
(* dist
(/ ry rx
))
684 ;; b/ otherwise compute the center of the circle
685 (let ((d (/ (sqrt diff-sq
) dist
)))
686 (unless (eq large-arc-flag-p sweep-flag-p
)
688 (setf rc
(make-point (+ (point-x rm
) (* (point-y drp1
) d
))
689 (- (point-y rm
) (* (point-x drp1
) d
)))))))
690 (let* ((start-angle (point-angle (p- rp1 rc
)))
691 (end-angle (point-angle (p- rp2 rc
)))
692 (delta-angle (- end-angle start-angle
)))
693 (when (minusp delta-angle
)
694 (incf delta-angle
(* 2 pi
)))
696 (decf delta-angle
(* 2 pi
)))
697 (values (point-rotate (p* rc
1.0 (/ ry rx
)) rotation
) rx ry start-angle delta-angle
)))))))
699 (defmethod interpolation-segment ((interpolation arc
) k1 k2 function
)
700 (let ((rotation (slot-value interpolation
'x-axis-rotation
)))
701 (multiple-value-bind (rc rx ry start-angle delta-angle
)
702 (svg-arc-parameters k1 k2
703 (slot-value interpolation
'rx
)
704 (slot-value interpolation
'ry
)
706 (slot-value interpolation
'large-arc-flag
)
707 (slot-value interpolation
'sweep-flag
))
709 (loop with n
= (max 3 (* (max rx ry
) (abs delta-angle
)))
711 for angle
= (+ start-angle
(/ (* delta-angle i
) n
))
712 for p
= (p+ (point-rotate
714 (make-point (* rx
(cos angle
))
719 do
(funcall function p
))))))
721 (defmethod interpolation-normal ((interpolation arc
) k1 k2 side
)
722 (let ((rotation (slot-value interpolation
'x-axis-rotation
)))
723 (multiple-value-bind (rc rx ry start-angle delta-angle
)
724 (svg-arc-parameters k1 k2
725 (slot-value interpolation
'rx
)
726 (slot-value interpolation
'ry
)
728 (slot-value interpolation
'large-arc-flag
)
729 (slot-value interpolation
'sweep-flag
))
730 (flet ((adjust (normal)
731 (let* ((p (point-rotate (p* normal
1.0 (/ ry rx
)) rotation
))
733 (when (plusp delta-angle
)
735 (make-point (/ (point-x p
) d
) (/ (point-y p
) d
)))))
737 (let ((end-angle (+ start-angle delta-angle
)))
739 (make-point (sin end-angle
)
741 (make-point (- (sin start-angle
))
742 (cos start-angle
))))))))))
744 (defmethod interpolation-clone ((interpolation arc
))
745 (make-arc (slot-value interpolation
'rx
)
746 (slot-value interpolation
'ry
)
747 :x-axis-rotation
(slot-value interpolation
'x-axis-rotation
)
748 :large-arc-flag
(slot-value interpolation
'large-arc-flag
)
749 :sweep-flag
(slot-value interpolation
'sweep-flag
)))
751 (defmethod interpolation-reverse ((interpolation arc
))
752 (setf (slot-value interpolation
'sweep-flag
)
753 (not (slot-value interpolation
'sweep-flag
))))
755 (defmethod interpolation-translate ((interpolation arc
) vector
)
756 (declare (ignore interpolation vector
)))
758 (defmethod interpolation-rotate ((interpolation arc
) angle
)
759 (incf (slot-value interpolation
'x-axis-rotation
) angle
))
761 (defmethod interpolation-scale ((interpolation arc
) scale-x scale-y
)
762 ;; FIXME: Return :segment-me if scaling is not possible?
763 (assert (and (not (zerop scale-x
))
764 (= scale-x scale-y
)))
765 (with-slots (rx ry
) interpolation
766 (setf rx
(* rx scale-x
)
771 (defclass catmull-rom
()
775 :initform
(make-array 0)
776 :initarg control-points
)
780 (defun make-catmull-rom (head control-points queue
)
781 (make-instance 'catmull-rom
783 'control-points
(coerce control-points
'vector
)
786 (defmethod interpolation-segment ((interpolation catmull-rom
) k1 k2 function
)
787 (let* ((control-points (slot-value interpolation
'control-points
))
788 (points (make-array (+ (length control-points
) 4))))
789 (replace points control-points
:start1
2)
790 (setf (aref points
0) (slot-value interpolation
'head
)
792 (aref points
(- (length points
) 2)) k2
793 (aref points
(- (length points
) 1)) (slot-value interpolation
'queue
))
794 (labels ((eval-catmull-rom (a b c d p
)
795 ;; http://www.mvps.org/directx/articles/catmull/
799 (* (+ (* 2 a
) (* -
5 b
) (* 4 c
) (- d
)) (expt p
2))
800 (* (+ (- a
) (* 3 b
) (* -
3 c
) d
) (expt p
3))))))
801 (loop for s below
(- (length points
) 3)
802 for a
= (aref points
(+ s
0)) then b
803 for b
= (aref points
(+ s
1)) then c
804 for c
= (aref points
(+ s
2)) then d
805 for d
= (aref points
(+ s
3))
806 do
(funcall function b
)
809 for p
= (/ (coerce i
'float
) n
)
810 for x
= (eval-catmull-rom (point-x a
)
815 for y
= (eval-catmull-rom (point-y a
)
820 do
(funcall function
(make-point x y
)))
821 (funcall function c
)))))
823 (defmethod interpolation-normal ((interpolation catmull-rom
) k1 k2 side
)
824 (with-slots (head control-points queue
) interpolation
826 (if (zerop (length control-points
))
833 (setf a
(aref control-points
(1- (length control-points
)))
835 (setf a
(aref control-points
0)
837 (let* ((x1 (point-x a
))
843 (dist (sqrt (+ (expt dx
2) (expt dy
2)))))
845 (make-point (/ dx dist
)
848 (defmethod interpolation-clone ((interpolation catmull-rom
))
849 (make-catmull-rom (slot-value interpolation
'head
)
850 (copy-seq (slot-value interpolation
'control-points
))
851 (slot-value interpolation
'queue
)))
853 (defmethod interpolation-reverse ((interpolation catmull-rom
))
854 (rotatef (slot-value interpolation
'head
)
855 (slot-value interpolation
'queue
))
856 (nreverse (slot-value interpolation
'control-points
)))
858 (defmethod interpolation-translate ((interpolation catmull-rom
) vector
)
859 (with-slots (head control-points queue
) interpolation
860 (setf head
(p+ head vector
)
861 queue
(p+ queue vector
))
862 (loop for i below
(length control-points
)
863 do
(setf (aref control-points i
) (p+ (aref control-points i
) vector
)))))
865 (defmethod interpolation-rotate ((interpolation catmull-rom
) angle
)
866 (with-slots (head control-points queue
) interpolation
867 (setf head
(point-rotate head angle
)
868 queue
(point-rotate queue angle
))
869 (loop for i below
(length control-points
)
870 do
(setf (aref control-points i
) (point-rotate (aref control-points i
) angle
)))))
872 (defmethod interpolation-scale ((interpolation catmull-rom
) scale-x scale-y
)
873 (with-slots (head control-points queue
) interpolation
874 (setf head
(p* head scale-x scale-y
)
875 queue
(p* queue scale-x scale-y
))
876 (loop for i below
(length control-points
)
877 do
(setf (aref control-points i
) (p* (aref control-points i
)
882 ;;; [http://www.fho-emden.de/~hoffmann/bezier18122002.pdf]
886 :initform
(make-array 0)
887 :initarg control-points
)))
889 (defun make-bezier-curve (control-points)
890 (make-instance 'bezier
891 'control-points
(make-array (length control-points
)
892 :initial-contents control-points
)))
894 (defun split-bezier (points &optional
(position 0.5))
895 "Split the Bezier curve described by POINTS at POSITION into
896 two Bezier curves of the same degree. Returns the curves as 2
898 (let* ((size (length points
))
899 (stack (make-array size
))
901 (setf (aref stack
0) points
)
902 (loop for j from
1 below size
903 for next-size from
(1- size
) downto
1
904 do
(let ((next (make-array next-size
)))
905 (loop for i below next-size
906 for a
= (aref current i
)
907 for b
= (aref current
(1+ i
))
908 do
(setf (aref next i
)
909 (make-point (+ (* (- 1.0 position
) (point-x a
))
910 (* position
(point-x b
)))
911 (+ (* (- 1.0 position
) (point-y a
))
912 (* position
(point-y b
))))))
913 (setf (aref stack j
) next
915 (let ((left (make-array (length points
)))
916 (right (make-array (length points
))))
917 (loop for i from
0 below size
918 for j from
(1- size
) downto
0
919 do
(setf (aref left i
) (aref (aref stack i
) 0)
920 (aref right i
) (aref (aref stack j
) i
)))
921 (values left right
))))
923 (defun evaluate-bezier (points position
)
924 "Evaluate the point at POSITION on the Bezier curve described
926 (let* ((size (length points
))
927 (temp (make-array (1- size
))))
928 (loop for current
= points then temp
929 for i from
(length temp
) downto
1
930 do
(loop for j below i
931 for a
= (aref current j
)
932 for b
= (aref current
(1+ j
))
933 do
(setf (aref temp j
)
934 (make-point (+ (* (- 1.0 position
) (point-x a
))
935 (* position
(point-x b
)))
936 (+ (* (- 1.0 position
) (point-y a
))
937 (* position
(point-y b
)))))))
938 (let ((p (aref temp
0)))
939 (values (point-x p
) (point-y p
)))))
941 (defun discrete-bezier-curve (points function
946 (distance-tolerance *bezier-distance-tolerance
*)
947 (angle-tolerance *bezier-angle-tolerance
*))
948 "Subdivize Bezier curve up to certain criterions."
949 ;; FIXME: Handle cusps correctly!
950 (unless min-subdivide
951 (setf min-subdivide
(floor (log (1+ (length points
)) 2))))
953 (sqrt (+ (expt a
2) (expt b
2))))
954 (refine-bezier (points depth
)
955 (let* ((a (aref points
0))
956 (b (aref points
(1- (length points
))))
957 (middle-straight (point-middle a b
)))
958 (multiple-value-bind (bx by
) (evaluate-bezier points
0.5)
959 (when (or (< depth min-subdivide
)
960 (and (<= depth max-subdivide
)
961 (or (> (norm (- bx
(point-x middle-straight
))
962 (- by
(point-y middle-straight
)))
964 (> (abs (- (atan (- by
(point-y a
)) (- bx
(point-x a
)))
965 (atan (- (point-y b
) by
) (- (point-x b
) bx
))))
967 (multiple-value-bind (a b
) (split-bezier points
0.5)
968 (refine-bezier a
(1+ depth
))
969 (funcall function bx by
)
970 (refine-bezier b
(1+ depth
))))))))
972 (let ((p (aref points
0)))
973 (funcall function
(point-x p
) (point-y p
))))
974 (refine-bezier points
0)
976 (let ((p (aref points
(1- (length points
)))))
977 (funcall function
(point-x p
) (point-y p
)))))
980 (defmethod interpolation-segment ((interpolation bezier
) k1 k2 function
)
981 (with-slots (control-points) interpolation
982 (let ((points (make-array (+ 2 (length control-points
)))))
983 (replace points control-points
:start1
1)
984 (setf (aref points
0) k1
985 (aref points
(1- (length points
))) k2
)
986 (discrete-bezier-curve points
987 (lambda (x y
) (funcall function
(make-point x y
)))
988 :include-ends nil
))))
990 (defmethod interpolation-normal ((interpolation bezier
) k1 k2 side
)
991 (let ((control-points (slot-value interpolation
'control-points
))
993 (if (zerop (length control-points
))
1000 (setf a
(aref control-points
(1- (length control-points
)))
1002 (setf a
(aref control-points
0)
1004 (let* ((x1 (point-x a
))
1010 (dist (sqrt (+ (expt dx
2) (expt dy
2)))))
1012 (make-point (/ dx dist
)
1015 (defmethod interpolation-clone ((interpolation bezier
))
1016 (let ((control-points (copy-seq (slot-value interpolation
'control-points
))))
1017 (loop for i below
(length control-points
)
1018 do
(setf (aref control-points i
) (aref control-points i
)))
1019 (make-bezier-curve control-points
)))
1021 (defmethod interpolation-reverse ((interpolation bezier
))
1022 (nreverse (slot-value interpolation
'control-points
)))
1024 (defmethod interpolation-translate ((interpolation bezier
) vector
)
1025 (with-slots (control-points) interpolation
1026 (loop for i below
(length control-points
)
1027 do
(setf (aref control-points i
) (p+ (aref control-points i
) vector
)))))
1029 (defmethod interpolation-rotate ((interpolation bezier
) angle
)
1030 (with-slots (control-points) interpolation
1031 (loop for i below
(length control-points
)
1032 do
(setf (aref control-points i
) (point-rotate (aref control-points i
) angle
)))))
1034 (defmethod interpolation-scale ((interpolation bezier
) scale-x scale-y
)
1035 (with-slots (control-points) interpolation
1036 (loop for i below
(length control-points
)
1037 do
(setf (aref control-points i
) (p* (aref control-points i
)
1038 scale-x scale-y
)))))
1040 ;;;--[ Building paths ]------------------------------------------------------
1042 (defun make-discrete-path (path)
1043 "Construct a path with only straight lines."
1044 (let ((result (create-path (path-type path
)))
1045 (knots (path-knots path
))
1046 (interpolations (path-interpolations path
)))
1047 (when (plusp (length knots
))
1048 ;; nicer, but slower too.. (But not profiled. Premature optimization?)
1049 #+nil
(loop with iterator
= (path-iterator-segmented path
)
1050 for
(interpolation knot end-p
) = (multiple-value-list (path-iterator-next iterator
))
1051 do
(path-extend result interpolation knot
)
1053 (path-reset result
(aref knots
0))
1055 for i below
(1- (length knots
))
1056 for k1
= (aref knots i
)
1057 for k2
= (aref knots
(1+ i
))
1058 for interpolation
= (aref interpolations
(1+ i
))
1059 do
(interpolation-segment interpolation k1 k2
1062 (make-straight-line)
1064 do
(path-extend result
(make-straight-line) k2
)
1065 finally
(unless (eq (path-type path
) :open-polyline
)
1066 (interpolation-segment (aref interpolations
0) k2
(aref knots
0)
1069 (make-straight-line)
1073 (defun make-circle-path (cx cy radius
&optional
(radius-y radius
) (x-axis-rotation 0.0))
1074 "Construct a path to represent a circle centered at CX,CY of
1075 the specified RADIUS."
1076 ;; Note: We represent the circle with 2 arcs
1077 (let ((path (create-path :polygon
)))
1078 (setf radius
(abs radius
)
1079 radius-y
(abs radius-y
))
1080 (when (= radius radius-y
)
1081 (setf x-axis-rotation
0.0))
1082 (when (and (plusp radius
) (plusp radius-y
))
1083 (let* ((center (make-point cx cy
))
1084 (p (point-rotate (make-point radius
0) x-axis-rotation
))
1085 (left (p+ center p
))
1086 (right (p- center p
)))
1087 (path-extend path
(make-arc radius radius-y
:x-axis-rotation x-axis-rotation
) left
)
1088 (path-extend path
(make-arc radius radius-y
:x-axis-rotation x-axis-rotation
) right
)))
1091 (defun make-rectangle-path (x1 y1 x2 y2
1092 &key
(round nil
) (round-x nil
) (round-y nil
))
1093 ;; FIXME: Instead: center + width + height + rotation ?
1094 ;; FIXME: Round corners? (rx, ry)
1099 (let ((path (create-path :closed-polyline
))
1100 (round-x (or round-x round
))
1101 (round-y (or round-y round
)))
1103 ((and round-x
(plusp round-x
)
1104 round-y
(plusp round-y
))
1105 (path-reset path
(make-point (+ x1 round-x
) y1
))
1106 (path-extend path
(make-arc round-x round-y
) (make-point x1
(+ y1 round-y
)))
1107 (path-extend path
(make-straight-line) (make-point x1
(- y2 round-y
)))
1108 (path-extend path
(make-arc round-x round-y
) (make-point (+ x1 round-x
) y2
))
1109 (path-extend path
(make-straight-line) (make-point (- x2 round-x
) y2
))
1110 (path-extend path
(make-arc round-x round-y
) (make-point x2
(- y2 round-y
)))
1111 (path-extend path
(make-straight-line) (make-point x2
(+ y1 round-y
)))
1112 (path-extend path
(make-arc round-x round-y
) (make-point (- x2 round-x
) y1
)))
1114 (path-reset path
(make-point x1 y1
))
1115 (path-extend path
(make-straight-line) (make-point x1 y2
))
1116 (path-extend path
(make-straight-line) (make-point x2 y2
))
1117 (path-extend path
(make-straight-line) (make-point x2 y1
))))
1120 (defun make-rectangle-path/center
(x y dx dy
&rest args
)
1121 (apply #'make-rectangle-path
(- x dx
) (- y dy
) (+ x dx
) (+ y dy
) args
))
1123 (defun make-regular-polygon-path (x y radius sides
&optional
(start-angle 0.0))
1124 (let ((path (create-path :closed-polyline
)))
1125 (loop for i below sides
1126 for angle
= (+ start-angle
(/ (* i
2 pi
) sides
))
1127 do
(path-extend path
(make-straight-line)
1128 (make-point (+ x
(* (cos angle
) radius
))
1129 (- y
(* (sin angle
) radius
)))))
1132 (defun make-simple-path (points &optional
(type :open-polyline
))
1133 "Create a path with only straight line, by specifying only knots."
1134 (let ((path (create-path type
)))
1135 (dolist (point points
)
1136 (path-extend path
(make-straight-line) point
))
1139 ;;;--[ Transformations ]-----------------------------------------------------
1141 (defmacro define-for-multiple-paths
(name-multiple name-single
&optional documentation
)
1142 "Define a new function named by NAME-MULTIPLE which accepts
1143 either a single path or a list of paths as input from a function
1144 named by NAME-SINGLE accepting only a single path and producing a
1146 `(defun ,name-multiple
(paths &rest args
)
1147 ,@(when documentation
(list documentation
))
1148 (loop for path in
(if (listp paths
) paths
(list paths
))
1149 nconc
(apply #',name-single path args
))))
1153 (defun stroke-path/1 (path thickness
1154 &key
(caps :butt
) (joint :none
) (inner-joint :none
)
1157 (setf thickness
(abs thickness
))
1158 (let ((half-thickness (/ thickness
2.0))
1160 ;; TARGET is the path updated by the function LINE-TO and
1162 (labels ((filter-interpolation (interpolation)
1163 ;; We handle only straight-line and arc of circle. The
1164 ;; rest will be segmented.
1165 (not (or (straight-line-p interpolation
)
1166 (and (typep interpolation
'arc
)
1167 (= (slot-value interpolation
'rx
)
1168 (slot-value interpolation
'ry
))))))
1170 (- (* a d
) (* b c
)))
1172 "Make a new arc similar to MODEL but with a radius
1173 updated to match the stroke."
1174 (assert (= (slot-value model
'rx
)
1175 (slot-value model
'ry
)))
1176 (let ((shift (if (slot-value model
'sweep-flag
)
1179 (make-arc (+ (slot-value model
'rx
) shift
)
1180 (+ (slot-value model
'ry
) shift
)
1181 :sweep-flag
(slot-value model
'sweep-flag
)
1182 :large-arc-flag
(slot-value model
'large-arc-flag
))))
1184 "Extend the path to knot P with a straight line."
1185 (path-extend target
(make-straight-line) p
))
1187 "EXtend the path to knot P with the given interpolation."
1188 (path-extend target i p
))
1190 "Produce the resulting path when the input path
1191 contains a single knot."
1196 (path-replace target
1197 (make-rectangle-path/center
(point-x k1
)
1202 (path-replace target
1203 (make-circle-path (point-x k1
)
1206 (do-first (k1 i2 k2
)
1207 "Process the first interpolation."
1208 (let* ((normal (interpolation-normal i2 k1 k2 nil
))
1209 (n (p* normal half-thickness
))
1210 (d (point-rotate n
(/ pi
2))))
1213 (line-to (p- k1 d
)))
1215 (line-to (p+ (p+ k1 d
) n
))
1216 (line-to (p+ (p- k1 d
) n
))
1217 (unless (straight-line-p i2
)
1218 (line-to (p- k1 d
))))
1220 (extend-to (make-arc half-thickness half-thickness
) (p- k1 d
))))))
1222 "Process the last interpolation."
1223 (let* ((normal (interpolation-normal i2 k1 k2 t
))
1224 (d (p* (point-rotate normal
(/ pi
2)) half-thickness
)))
1227 (extend-to (arc i2
) (p+ k2 d
)))
1228 ((straight-line-p i2
)
1229 (unless (eq caps
:square
)
1230 (line-to (p+ k2 d
))))
1232 (error "unexpected interpolation")))))
1233 (do-segment (k1 i2 k2 i3 k3
)
1234 "Process intermediate interpolation."
1235 (let* ((normal-a (interpolation-normal i2 k1 k2 t
))
1236 (normal-b (interpolation-normal i3 k2 k3 nil
))
1237 (outer-p (plusp (det (point-x normal-a
) (point-y normal-a
)
1238 (point-x normal-b
) (point-y normal-b
))))
1239 (d-a (p* (point-rotate normal-a
(/ pi
2)) half-thickness
))
1240 (d-b (p* (point-rotate normal-b
(/ pi -
2)) half-thickness
)))
1243 (eq inner-joint
:miter
)
1244 (straight-line-p i2
)
1245 (straight-line-p i3
))
1246 ;; Miter inner joint between 2 straight lines
1247 (multiple-value-bind (xi yi
)
1248 (line-intersection/delta
1249 (point-x (p+ k2 d-a
)) (point-y (p+ k2 d-a
))
1250 (point-x normal-a
) (point-y normal-a
)
1251 (point-x (p+ k2 d-b
)) (point-y (p+ k2 d-b
))
1252 (point-x normal-b
) (point-y normal-b
))
1255 (plusp (+ (* (- xi
(point-x k1
))
1257 (* (- yi
(point-y k1
))
1258 (point-y normal-a
))))
1259 (plusp (+ (* (- xi
(point-x k3
))
1261 (* (- yi
(point-y k3
))
1262 (point-y normal-b
)))))
1263 ;; ok, intersection point
1264 ;; is behind segments
1266 (extend-to (make-straight-line) (make-point xi yi
)))
1268 ;; revert to basic joint
1269 (line-to (p+ k2 d-a
))
1270 (line-to (p+ k2 d-b
))))))
1273 (straight-line-p i2
)
1274 (straight-line-p i3
))
1275 ;; Miter outer joint between 2 straight lines
1276 (multiple-value-bind (xi yi
)
1277 (line-intersection/delta
1278 (point-x (p+ k2 d-a
)) (point-y (p+ k2 d-a
))
1279 (point-x normal-a
) (point-y normal-a
)
1280 (point-x (p+ k2 d-b
)) (point-y (p+ k2 d-b
))
1281 (point-x normal-b
) (point-y normal-b
))
1282 (let ((i (make-point xi yi
)))
1285 (<= (point-distance i k2
)
1286 (* half-thickness
*miter-limit
*)))
1287 (line-to (make-point xi yi
)))
1289 ;; FIXME: Ugh. My math skill show its
1290 ;; limits. This is probably possible to
1291 ;; compute the same thing with less steps.
1292 (let* ((p (p+ k2
(point-middle d-a d-b
)))
1293 (a (point-distance (p+ k2 d-a
) i
))
1294 (b (- (* half-thickness
*miter-limit
*)
1295 (point-distance k2 p
)))
1296 (c (point-distance p i
))
1298 (p1 (p+ (p+ k2 d-a
) (p* normal-a d
)))
1299 (p2 (p+ (p+ k2 d-b
) (p* normal-b d
))))
1303 (extend-to (if (typep i2
'arc
)
1305 (make-straight-line))
1311 (line-to (p+ k2 d-b
)))
1313 (extend-to (make-arc half-thickness half-thickness
1318 (line-to (p+ k2 d-b
)))
1320 (extend-to (make-arc half-thickness half-thickness
1323 (do-contour-half (path new-target first-half-p
)
1324 (setf target new-target
)
1325 (let ((iterator (filter-distinct (path-iterator-segmented path
#'filter-interpolation
)
1328 (path-iterator-next iterator
)))
1329 (multiple-value-bind (i1 k1 e1
) (next)
1337 (multiple-value-bind (i2 k2 e2
) (next)
1342 (multiple-value-bind (i3 k3 e3
) (next)
1343 (do-segment k1 i2 k2 i3 k3
)
1348 (do-last k1 i2 k2
)))))))))
1349 (do-contour-polygon (path new-target first-p
)
1350 (setf target new-target
)
1351 (let ((iterator (filter-distinct (path-iterator-segmented path
#'filter-interpolation
))))
1353 (path-iterator-next iterator
)))
1354 (multiple-value-bind (i1 k1 e1
) (next)
1362 (multiple-value-bind (i2 k2 e2
) (next)
1364 (let (extra-iteration)
1366 (setf extra-iteration
2))
1368 (multiple-value-bind (i3 k3 e3
) (next)
1369 (when (and extra-iteration
(zerop extra-iteration
))
1371 (do-segment k1 i2 k2 i3 k3
)
1376 (decf extra-iteration
))
1378 (setf extra-iteration
2)))))))))))))))
1379 (when (plusp half-thickness
)
1380 (ecase (or assume-type
(path-type path
))
1382 (let ((result (create-path :polygon
)))
1383 (do-contour-half path result t
)
1384 (do-contour-half (path-reversed path
) result nil
)
1387 (let ((result-a (create-path :polygon
))
1388 (result-b (create-path :polygon
)))
1389 ;; FIXME: What happen for single knot path?
1390 (do-contour-polygon path result-a t
)
1391 (do-contour-polygon (path-reversed path
) result-b nil
)
1392 (list result-a result-b
)))
1394 (let ((result (create-path :polygon
)))
1395 (do-contour-polygon path result t
)
1396 (list result
))))))))
1398 (define-for-multiple-paths stroke-path stroke-path
/1)
1402 (defun dash-path/1 (path sizes
&key
(toggle-p nil
) (cycle-index 0))
1403 "Dash path. If TOGGLE-P is true, segments of odd indices are
1404 kept, while if TOGGLE-P is false, segments of even indices are
1405 kept. CYCLE indicate where to cycle the SIZES once the end is
1407 (assert (<= 0 cycle-index
(1- (length sizes
)))
1408 (cycle-index) "Invalid cycle index")
1409 (assert (loop for size across sizes never
(minusp size
))
1410 (sizes) "All sizes must be non-negative.")
1411 (assert (loop for size across sizes thereis
(plusp size
))
1412 (sizes) "At least one size must be positive.")
1413 (flet ((interpolation-filter (interpolation)
1414 (or (not (typep interpolation
'arc
))
1415 (/= (slot-value interpolation
'rx
)
1416 (slot-value interpolation
'ry
)))))
1418 (current (create-path :open-polyline
))
1419 (current-length 0.0)
1420 (toggle (not toggle-p
))
1422 (size (aref sizes
0))
1423 (iterator (path-iterator-segmented path
#'interpolation-filter
)))
1426 (push current result
))
1427 (setf toggle
(not toggle
))
1428 (setf current
(create-path :open-polyline
)
1431 (when (= index
(length sizes
))
1432 (setf index cycle-index
))
1433 (setf size
(aref sizes index
)))
1434 (extend (interpolation knot length
)
1435 (path-extend current interpolation knot
)
1436 (incf current-length length
)))
1438 for previous-knot
= nil then knot
1439 for stop-p
= nil then end-p
1440 for
(interpolation knot end-p
) = (multiple-value-list (path-iterator-next iterator
))
1441 if
(not previous-knot
)
1442 do
(path-reset current knot
)
1444 do
(etypecase interpolation
1445 ((eql :straight-line
)
1446 (let* ((delta (p- knot previous-knot
))
1447 (length (point-norm delta
))
1450 (let ((missing (- size current-length
))
1451 (available (- length pos
)))
1452 (when (> missing available
)
1453 (extend (make-straight-line) knot available
)
1456 (let ((end (p+ previous-knot
(p* delta
(/ pos length
)))))
1457 (extend (make-straight-line) end missing
)
1459 (path-reset current end
))))))
1461 (with-slots (rx ry x-axis-rotation large-arc-flag sweep-flag
) interpolation
1463 (multiple-value-bind (rc nrx nry start-angle delta-angle
)
1464 (svg-arc-parameters previous-knot knot rx ry
1468 (let* ((length (* (abs delta-angle
) nrx
))
1471 (let ((missing (- size current-length
))
1472 (available (- length pos
)))
1473 (when (> missing available
)
1474 (extend (make-arc nrx nry
1475 :x-axis-rotation x-axis-rotation
1476 :large-arc-flag
(>= (/ available nrx
) pi
)
1477 :sweep-flag sweep-flag
)
1483 (point-rotate (make-point nrx
0)
1485 (if (plusp delta-angle
)
1486 (+ start-angle
(/ pos nrx
))
1487 (- start-angle
(/ pos nrx
)))))
1489 (extend (make-arc nrx nry
1490 :x-axis-rotation x-axis-rotation
1491 :large-arc-flag
(>= (/ missing nrx
) pi
)
1492 :sweep-flag sweep-flag
)
1496 (path-reset current end
)))))))))
1497 until
(if (eq (path-type path
) :open-polyline
) end-p stop-p
))
1499 (nreverse result
))))
1501 (define-for-multiple-paths dash-path dash-path
/1)
1505 (defun clip-path/1 (path x y dx dy
)
1507 (current (create-path (path-type path
)))
1508 (iterator (path-iterator-segmented path
)))
1510 (path-iterator-next iterator
))
1512 (- (* a d
) (* b c
)))
1514 (plusp (det (- (point-x p
) x
)
1518 (let ((k1-inside-p (when (inside-p k1
) t
))
1519 (k2-inside-p (when (inside-p k2
) t
)))
1521 (path-extend current
(make-straight-line) k1
))
1522 (when (not (eq k1-inside-p k2-inside-p
))
1523 (multiple-value-bind (xi yi
)
1524 (line-intersection/delta x y dx dy
1525 (point-x k1
) (point-y k1
)
1526 (- (point-x k2
) (point-x k1
))
1527 (- (point-y k2
) (point-y k1
)))
1529 (path-extend current
(make-straight-line) (make-point xi yi
))))))))
1530 (multiple-value-bind (i1 k1 e1
) (next)
1531 (let ((first-knot k1
))
1536 (path-reset current k1
)))
1539 (multiple-value-bind (i2 k2 e2
) (next)
1542 (if (eq (path-type path
) :open-polyline
)
1544 (path-extend current
(make-straight-line) k2
))
1545 (clip-left k2 first-knot
))
1548 (setf k1 k2
)))))))))
1549 (push current result
)
1552 (define-for-multiple-paths clip-path clip-path
/1)
1554 (defun clip-path/path
/1 (path limit
)
1555 (let ((iterator (filter-distinct (path-iterator-segmented limit
)))
1556 (result (list path
)))
1557 (multiple-value-bind (i1 k1 e1
) (path-iterator-next iterator
)
1558 (declare (ignore i1
))
1559 (when (and k1
(not e1
))
1562 (multiple-value-bind (i2 k2 e2
) (path-iterator-next iterator
)
1563 (declare (ignore i2
))
1564 (setq result
(loop for path in result
1565 nconc
(clip-path path
1566 (point-x k1
) (point-y k1
)
1567 (point-x (p- k2 k1
)) (point-y (p- k2 k1
)))))
1574 (define-for-multiple-paths clip-path
/path clip-path
/path
/1)
1579 (defun round-path/1 (path &optional max-radius
)
1580 (declare (ignore max-radius
))
1583 (define-for-multiple-paths round-path round-path
/1)