-
Notifications
You must be signed in to change notification settings - Fork 6
/
edit-shape.lisp
230 lines (201 loc) · 7.8 KB
/
edit-shape.lisp
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
(in-package #:sdf/base)
;;; experimental alternative to SHAPE class that supports in-place
;;; editing. Currently only intended for implementing a few functions
;;; that are easier that way, but might eventually replace SHAPE.
(defclass es-contour-node ()
;; contours are stored as doubly linked list,
;; alternating vertex and edge nodes
((prev :initarg :prev :accessor eprev)
(next :initarg :next :accessor enext)))
(defclass es-contour-vertex (es-contour-node)
((point :initarg :point :reader point)))
(defclass es-contour-edge (es-contour-node)
;; not sure if we need a common base class for edges or not?
())
(defclass es-contour-segment (es-contour-edge)
;; just a line segment between adjacent points
())
(defclass es-contour-bezier2 (es-contour-edge)
;; curve adds a control point between the adjacent points
((control-point :initarg :control-point :reader control-point :initform nil)))
(defun enl (n)
(flet ((p? (p)
(cond ((eql p n) :<@>)
((eql p :removed) :removed)
((typep p 'es-contour-vertex) (nl (point p)))
(t :???))))
(etypecase n
(null nil)
(es-contour-vertex
(list :v (nl (point n))))
(es-contour-bezier2
(list :b (p? (eprev n))
(when (control-point n) (nl (control-point n)))
(p? (enext n))))
(es-contour-segment
(list :s (p? (eprev n)) (p? (enext n)))))))
(defmethod initialize-instance :after ((n es-contour-node) &key)
;; if only 1 of next is bound, set other to same thing. if neither
;; is bound, set both to node
;; todo: decide if setting only 1 should be an error instead, or if
;; we should initialize to NIL when not in a list
(unless (slot-boundp n 'next)
(setf (enext n) (if (slot-boundp n 'prev) (eprev n) n)))
(unless (slot-boundp n 'prev)
(setf (eprev n) (enext n))))
(defun add-after (node new)
;; if new is multiple nodes, return the other end, so calling
;; add-after on result will add after all of NEW
(let ((r (eprev new)))
(when node
(psetf (eprev new) node
(enext node) new
(enext (eprev new)) (enext node)
(eprev (enext node)) (eprev new)))
r))
(defun add-before (node new)
(let ((r (enext new)))
(when node
(psetf (enext new) node
(eprev node) new
(eprev (enext new)) (eprev node)
(enext (eprev node)) (enext new)))
r))
(defun add-after-* (node &rest new)
(loop for i in new
do (setf node (add-after node i))))
(defclass edit-shape ()
((contours :initarg :contours
:initform nil ;; not sure if this is list or vector yet,
;; will need to add/remove contours
:accessor contours)))
(defun make-edit-contour (shape contour)
(let ((new nil))
(loop for n = contour then nn
for nn = (next shape n)
for end = (eql nn contour)
do (etypecase n
(point
(setf new
(add-after new
(make-instance 'es-contour-vertex
:point n))))
(segment
(setf new
(add-after new
(make-instance 'es-contour-segment))))
(bezier2
(setf new
(add-after new
(make-instance 'es-contour-bezier2
:control-point (b2-c1 n))))))
until end)
new))
(defun degenerate-edit-contour (c)
(or
;; not a contour
(not c)
;; just a point
(eql c (enext c))
;; same start and end points
(eql (enext c) (eprev c))))
(defun %delete-node (ecn)
;; remove ecn from doubly linked list, and return previous node
(when ecn
(check-type ecn es-contour-node)
(if (eql ecn (eprev ecn))
nil ;; removed last node
(let ((r (eprev ecn)))
(psetf (enext (eprev ecn)) (enext ecn)
(eprev (enext ecn)) (eprev ecn)
;; not sure if we should try to leave removed node in
;; a valid state so it can be reinserted, or
;; invalidate it to catch problems where we kept
;; reference to it
;; (eprev ecn) ecn
;; (enext ecn) ecn
;; for now making it invalid
(eprev ecn) :removed
(enext ecn) :removed)
r))))
(defun collapse-edge (edge)
(check-type edge es-contour-edge)
;; for now just using average of end points, but might be slightly
;; better to point on curve at t=0.5 for beziers?
(unless (degenerate-edit-contour edge)
(let ((x1 (p-rx (point (eprev edge))))
(y1 (p-ry (point (eprev edge))))
(x2 (p-rx (point (enext edge))))
(y2 (p-ry (point (enext edge))))
(tmp (enext edge)))
(assert (typep tmp 'es-contour-vertex))
(setf tmp (%delete-node tmp))
(assert (eql tmp edge))
(setf tmp (%delete-node tmp))
(assert (typep tmp 'es-contour-vertex))
(setf tmp (%delete-node tmp))
(setf edge
(add-after tmp
(make-instance 'es-contour-vertex
:point (make-point (/ (+ x1 x2) 2)
(/ (+ y1 y2) 2))))))))
(defun map-modifying-contour (contour fun)
;; iterate over doubly linked list contour, calling FUN with each
;; node. If FUN returns something other than NODE, continue iteration
;; with the returned node, otherwise with NEXT node.
;; not sure if this should be allowed to visit nodes multiple times,
;; or if it can stop at first already visited node without using
;; some extra ram to track visited nodes.
;; Might be worth visiting multiple times if for example FUN wants to
;; match patterns that extend over multiple nodes, and might match
;; at a visited node after modifications to a subsequent node.
;; Detecting earliest stop without storing all visited nodes might
;; be hard if FUN tries to delete more than just the current node,
;; so for now just storing a visited hash...
(when contour
(let ((visited (make-hash-table)))
(loop with n = contour
for n2 = (funcall fun n)
do (setf (gethash n visited) t)
(if (eql n n2)
(setf n (enext n))
(setf n n2))
while n
until (and (gethash n visited)
;; stop when current and next node have been
;; visited, so FUN can backtrack by 1 if needed
(gethash (enext n) visited))
finally (return n)))))
(defun map-contour (contour fun)
(map-modifying-contour contour (lambda (a) (funcall fun a) a)))
(defun %print-contour (start &key max)
(let ((c 0)
(m2 (when max (ceiling max 2))))
(sdf/base::map-modifying-contour
start (lambda (n)
(incf c)
(when (or (not max) (< c m2))
(format t " ~s~%" (sdf/base::enl n)))
n))
(when (and m2 (>= c m2))
(let ((c2 (- c m2))
(s2 start))
(when (> c2 m2)
(format t " ... skipped ~s ...~%" (- c2 m2)))
(setf c2 (min m2 c2))
(loop repeat c2 do (setf s2 (eprev s2)))
(loop repeat c2
do (format t " ~s~%" (sdf/base::enl s2))
(setf s2 (enext s2))
until (eql s2 start))))))
(defun %print-contours (contours)
(loop for c in contours
for i from 0
do (format t " ~s = ~%" i)
(%print-contour c)))
(defun %reverse-contour (contour)
(loop for s = contour then n
for n = (enext contour) then (enext n)
do (rotatef (eprev s) (enext s))
until (eql n contour))
contour)