1+ # import " /src/cetz.typ"
2+
13// / Clip line-strip in rect
24// /
35// / - points (array): Array of vectors representing a line-strip
1113 let (min-y , max-y ) = (calc . min (low . at (1 ), high . at (1 )),
1214 calc . max (low . at (1 ), high . at (1 )))
1315
14- let in-rect (pt ) = {
15- return (pt . at (0 ) >= min-x and pt . at (0 ) <= max-x and
16- pt . at (1 ) >= min-y and pt . at (1 ) <= max-y )
17- }
18-
19- let interpolated-end (a , b ) = {
20- if in-rect (a ) and in-rect (b ) {
21- return b
22- }
23-
24- let (x1 , y1 , .. ) = a
25- let (x2 , y2 , .. ) = b
26-
27- if x2 - x1 == 0 {
28- return (x2 , calc . min (max-y , calc . max (y2 , min-y )))
29- }
30-
31- if y2 - y1 == 0 {
32- return (calc . min (max-x , calc . max (x2 , min-x )), y2 )
33- }
34-
35- let m = (y2 - y1 ) / (x2 - x1 )
36- let n = y2 - m * x2
37-
38- let x = x2
39- let y = y2
40-
41- y = calc . min (max-y , calc . max (y , min-y ))
42- x = (y - n ) / m
43-
44- x = calc . min (max-x , calc . max (x , min-x ))
45- y = m * x + n
46-
47- return (x , y )
16+ let in-rect ((x , y )) = {
17+ return (x >= min-x and x <= max-x and
18+ y >= min-y and y <= max-y )
4819 }
4920
50- // Append path to paths and return paths
51- //
52- // If path starts or ends with a vector of another part, merge those
53- // paths instead appending path as a new path.
54- let append-path (paths , path ) = {
55- if path . len () <= 1 {
56- return paths
57- }
58-
59- let cmp (a , b ) = {
60- return a . map (calc . round . with (digits : 8 )) == b . map (calc . round . with (digits : 8 ))
61- }
21+ let edges = (
22+ ((min-x , min-y ), (min-x , max-y )),
23+ ((max-x , min-y ), (max-x , max-y )),
24+ ((min-x , min-y ), (max-x , min-y )),
25+ ((min-x , max-y ), (max-x , max-y )),
26+ )
6227
63- let added = false
64- for i in range (0 , paths . len ()) {
65- let p = paths . at (i )
66- if cmp (p . first (), path . last ()) {
67- paths . at (i ) = path + p
68- added = true
69- } else if cmp (p . first (), path . first ()) {
70- paths . at (i ) = path . rev () + p
71- added = true
72- } else if cmp (p . last (), path . first ()) {
73- paths . at (i ) = p + path
74- added = true
75- } else if cmp (p . last (), path . last ()) {
76- paths . at (i ) = p + path . rev ()
77- added = true
28+ let interpolated-end (a , b ) = {
29+ for (edge-a , edge-b ) in edges {
30+ let pt = cetz . intersection . line-line (a , b , edge-a , edge-b )
31+ if pt != none {
32+ return pt
7833 }
79- if added { break }
8034 }
81-
82- if not added {
83- paths . push (path )
84- }
85- return paths
8635 }
8736
88- let clamped-pt (pt ) = {
89- return (calc . max (min-x , calc . min (pt . at (0 ), max-x )),
90- calc . max (min-y , calc . min (pt . at (1 ), max-y )))
91- }
9237
93- let paths = ()
38+ // Find lines crossing the rect bounds
39+ // by storing all crossings as tuples (<index>, <goes-inside>, <point-on-border>)
40+ let crossings = ()
9441
95- let path = ()
96- let prev = points . at (0 )
97- let was-inside = in-rect (prev )
42+ // Push a pseudo entry for the last point, if it is insides the bounds.
43+ let was-inside = in-rect (points . at (0 ))
9844 if was-inside {
99- path . push (prev )
100- } else if fill {
101- path . push (clamped-pt (prev ))
45+ crossings . push ((0 , true , points . first ()))
10246 }
10347
48+ // Find crossings and compute interseciton points.
10449 for i in range (1 , points . len ()) {
105- let prev = points . at (i - 1 )
106- let pt = points . at (i )
107-
108- let is-inside = in-rect (pt )
109-
110- let (x1 , y1 ) = prev
111- let (x2 , y2 ) = pt
112-
113- // Ignore lines if both ends are outsides the x-window and on the
114- // same side.
115- if (x1 < min-x and x2 < min-x ) or (x1 > max-x and x2 > max-x ) {
116- if fill {
117- let clamped = clamped-pt (pt )
118- if path . last () != clamped {
119- path . push (clamped )
120- }
121- }
122- was-inside = false
123- continue
50+ let current-inside = in-rect (points . at (i ))
51+ if current-inside != was-inside {
52+ crossings . push ((
53+ i ,
54+ current-inside ,
55+ interpolated-end (points . at (i - 1 ), points . at (i ))))
56+ was-inside = current-inside
12457 }
125-
126- if is-inside {
127- if was-inside {
128- path . push (pt )
129- } else {
130- path . push (interpolated-end (pt , prev ))
131- path . push (pt )
132- }
133- } else {
134- if was-inside {
135- path . push (interpolated-end (prev , pt ))
136- } else {
137- let (a , b ) = (interpolated-end (pt , prev ),
138- interpolated-end (prev , pt ))
139- if in-rect (a ) and in-rect (b ) {
140- path . push (a )
141- path . push (b )
142- } else if fill {
143- let clamped = clamped-pt (pt )
144- if path . last () != clamped {
145- path . push (clamped )
146- }
147- }
148- }
149-
150- if path . len () > 0 and not fill {
151- paths = append-path (paths , path )
152- path = ()
153- }
154- }
155-
156- was-inside = is-inside
15758 }
15859
159- // Append clamped last point if filling
160- if fill and not in-rect (points . last ()) {
161- path . push (clamped-pt ( points . last ()))
60+ // Push a pseudo entry for the last point, if it is insides the bounds.
61+ if in-rect (points . last ()) and crossings . last () . at ( 1 ) {
62+ crossings . push (( points . len () - 1 , false , points . last ()))
16263 }
16364
164- if path . len () > 1 {
165- paths = append-path (paths , path )
65+ // Generate paths
66+ let paths = ()
67+ for i in range (1 , crossings . len ()) {
68+ let (a-index , a-dir , a-pt ) = crossings . at (i - 1 )
69+ let (b-index , b-dir , b-pt ) = crossings . at (i )
70+
71+ if a-dir {
72+ let path = points . slice (a-index , b-index )
73+ path . insert (0 , a-pt )
74+ path . push (b-pt )
75+
76+ // Insert the last end point to connect
77+ // to a filled area.
78+ if fill and paths . len () > 0 {
79+ path . insert (0 , paths . last (). last ())
80+ }
81+
82+ paths . push (path )
83+ }
16684 }
16785
16886 return paths
18199// / - low (vector): Lower clip-window coordinate
182100// / - high (vector): Upper clip-window coordinate
183101// / -> array List of fill paths
184- # let compute-fill-paths = clipped-paths-rect . with (fill : true )
102+ # let compute-fill-paths = clipped-paths-rect . with (fill : true )
0 commit comments