forked from racket/racket
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclip.rkt
More file actions
159 lines (137 loc) · 7.54 KB
/
clip.rkt
File metadata and controls
159 lines (137 loc) · 7.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#lang racket/base
;; Small library for clipping points, rectangles, lines and polygons against axial planes.
(require racket/match racket/list
"../common/utils.rkt")
(provide point-in-bounds? clip-line clip-lines clip-polygon)
;; ===================================================================================================
;; Point clipping
(define (point-in-bounds? v x-min x-max y-min y-max)
(match-define (vector x y) v)
(and (<= x-min x x-max) (<= y-min y y-max)))
;; ===================================================================================================
;; Line clipping
(define (clip-line-x start-in-bounds? x x1 y1 x2 y2)
(let-map
(x x1 y1 x2 y2) inexact->exact
(define t (/ (- x x1) (- x2 x1)))
(cond [start-in-bounds? (values x1 y1 x (+ y1 (* t (- y2 y1))))]
[else (values x (+ y1 (* t (- y2 y1))) x2 y2)])))
(define (clip-line-y start-in-bounds? y x1 y1 x2 y2)
(let-map
(y x1 y1 x2 y2) inexact->exact
(define t (/ (- y y1) (- y2 y1)))
(cond [start-in-bounds? (values x1 y1 (+ x1 (* t (- x2 x1))) y)]
[else (values (+ x1 (* t (- x2 x1))) y x2 y2)])))
(define (clip-line-x-min x-min x1 y1 x2 y2)
(cond [(and (x1 . >= . x-min) (x2 . < . x-min)) (clip-line-x #t x-min x1 y1 x2 y2)]
[(and (x2 . >= . x-min) (x1 . < . x-min)) (clip-line-x #f x-min x1 y1 x2 y2)]
[else (values x1 y1 x2 y2)]))
(define (clip-line-x-max x-max x1 y1 x2 y2)
(cond [(and (x1 . <= . x-max) (x2 . > . x-max)) (clip-line-x #t x-max x1 y1 x2 y2)]
[(and (x2 . <= . x-max) (x1 . > . x-max)) (clip-line-x #f x-max x1 y1 x2 y2)]
[else (values x1 y1 x2 y2)]))
(define (clip-line-y-min y-min x1 y1 x2 y2)
(cond [(and (y1 . >= . y-min) (y2 . < . y-min)) (clip-line-y #t y-min x1 y1 x2 y2)]
[(and (y2 . >= . y-min) (y1 . < . y-min)) (clip-line-y #f y-min x1 y1 x2 y2)]
[else (values x1 y1 x2 y2)]))
(define (clip-line-y-max y-max x1 y1 x2 y2)
(cond [(and (y1 . <= . y-max) (y2 . > . y-max)) (clip-line-y #t y-max x1 y1 x2 y2)]
[(and (y2 . <= . y-max) (y1 . > . y-max)) (clip-line-y #f y-max x1 y1 x2 y2)]
[else (values x1 y1 x2 y2)]))
(define (clip-line v1 v2 x-min x-max y-min y-max)
(let/ec return
(match-define (vector x1 y1) v1)
(match-define (vector x2 y2) v2)
;; early accept: both endpoints in bounds
(when (and (<= x-min x1 x-max) (<= y-min y1 y-max)
(<= x-min x2 x-max) (<= y-min y2 y-max))
(return v1 v2))
;; early reject: both endpoints on the outside of the same plane
(when (or (and (x1 . < . x-min) (x2 . < . x-min)) (and (x1 . > . x-max) (x2 . > . x-max))
(and (y1 . < . y-min) (y2 . < . y-min)) (and (y1 . > . y-max) (y2 . > . y-max)))
(return #f #f))
(let*-values ([(x1 y1 x2 y2) (clip-line-x-min x-min x1 y1 x2 y2)]
[(x1 y1 x2 y2) (clip-line-x-max x-max x1 y1 x2 y2)]
[(x1 y1 x2 y2) (clip-line-y-min y-min x1 y1 x2 y2)]
[(x1 y1 x2 y2) (clip-line-y-max y-max x1 y1 x2 y2)])
(values (vector x1 y1) (vector x2 y2)))))
;; ===================================================================================================
;; Polygon clipping
(define-syntax-rule (make-clip-polygon in-bounds? clip-line)
(λ (val xs ys)
(cond [(empty? xs) (values empty empty)]
[else
(for/fold ([res-xs empty] [res-ys empty]) ([x1 (in-list (cons (last xs) xs))]
[x2 (in-list xs)]
[y1 (in-list (cons (last ys) ys))]
[y2 (in-list ys)])
(define v1-in-bounds? (in-bounds? x1 y1 val))
(define v2-in-bounds? (in-bounds? x2 y2 val))
(cond [(and v1-in-bounds? v2-in-bounds?) (values (cons x2 res-xs)
(cons y2 res-ys))]
[(and (not v1-in-bounds?) (not v2-in-bounds?)) (values res-xs res-ys)]
[else (let-values ([(x1 y1 x2 y2) (clip-line v1-in-bounds? val x1 y1 x2 y2)])
(cond [v2-in-bounds? (values (list* x2 x1 res-xs)
(list* y2 y1 res-ys))]
[else (values (cons x2 res-xs) (cons y2 res-ys))]))]))])))
(define-syntax-rule (x-min-in-bounds? x y x-min) (x . >= . x-min))
(define-syntax-rule (x-max-in-bounds? x y x-max) (x . <= . x-max))
(define-syntax-rule (y-min-in-bounds? x y y-min) (y . >= . y-min))
(define-syntax-rule (y-max-in-bounds? x y y-max) (y . <= . y-max))
(define clip-polygon-x-min (make-clip-polygon x-min-in-bounds? clip-line-x))
(define clip-polygon-x-max (make-clip-polygon x-max-in-bounds? clip-line-x))
(define clip-polygon-y-min (make-clip-polygon y-min-in-bounds? clip-line-y))
(define clip-polygon-y-max (make-clip-polygon y-max-in-bounds? clip-line-y))
(define (clip-polygon vs x-min x-max y-min y-max)
(let/ec return
;; early reject: no polygon
(when (empty? vs) (return empty))
(match-define (list (vector xs ys) ...) vs)
;; early accept: all endpoints in bounds
(when (and (andmap (λ (x) (<= x-min x x-max)) xs)
(andmap (λ (y) (<= y-min y y-max)) ys))
(return vs))
;; early reject: all endpoints on the outside of the same plane
(when (or (andmap (λ (x) (x . < . x-min)) xs) (andmap (λ (x) (x . > . x-max)) xs)
(andmap (λ (y) (y . < . y-min)) ys) (andmap (λ (y) (y . > . y-max)) ys))
(return empty))
(let*-values ([(xs ys) (clip-polygon-x-min x-min xs ys)]
[(xs ys) (clip-polygon-x-max x-max xs ys)]
[(xs ys) (clip-polygon-y-min y-min xs ys)]
[(xs ys) (clip-polygon-y-max y-max xs ys)])
(map vector xs ys))))
;; ===================================================================================================
;; Lines clipping
(define (join-lines lines)
(let loop ([lines lines] [current-line empty])
(cond [(empty? lines) (list (reverse current-line))]
[(empty? current-line) (loop (rest lines) (reverse (first lines)))]
[else
(match-define v (first current-line))
(match-define (list v1 v2) (first lines))
(cond [(equal? v v1) (loop (rest lines) (cons v2 current-line))]
[else (cons (reverse current-line) (loop lines empty))])])))
#;
(join-lines
'((#(1 2) #(3 4))
(#(3 4) #(5 6))
(#(5 7) #(7 8))
(#(7 8) #(9 10))))
(define (clip-lines vs x-min x-max y-min y-max)
(let/ec return
;; early reject: no lines
(when (empty? vs) (return empty))
(match-define (list (vector xs ys) ...) vs)
;; early accept: all endpoints in bounds
(when (and (andmap (λ (x) (<= x-min x x-max)) xs) (andmap (λ (y) (<= y-min y y-max)) ys))
(return (list vs)))
;; early reject: all endpoints on the outside of the same plane
(when (or (andmap (λ (x) (x . < . x-min)) xs) (andmap (λ (x) (x . > . x-max)) xs)
(andmap (λ (y) (y . < . y-min)) ys) (andmap (λ (y) (y . > . y-max)) ys))
(return empty))
(join-lines
(reverse
(for/fold ([res empty]) ([v1 (in-list vs)] [v2 (in-list (rest vs))])
(let-values ([(v1 v2) (clip-line v1 v2 x-min x-max y-min y-max)])
(cond [(and v1 v2) (cons (list v1 v2) res)]
[else res])))))))