-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathmesh.lisp
1999 lines (1767 loc) · 78.2 KB
/
mesh.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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; -*- Mode:Lisp; Package:Weyli; Base:10; Lowercase:T; Syntax:Common-Lisp -*-
;;; ===========================================================================
;;; Flat Meshing
;;; ===========================================================================
;;; (c) Copyright 1995 Cornell University
;;; mesh.lisp,v 1.11 1995/06/09 14:21:32 chew Exp
;; Everything needed for flat meshing.
(in-package :weyli)
;;; DELETE (make::adjust-version-numbers Weyl "1.11")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Control Variables.
(defvar *delaunay* T) ; Each new triangle is checked.
(defvar *cross-edges* nil) ; Can't cross constraint edges.
(defvar *mesh* nil) ; Holds the current mesh.
(defvar *space* nil) ; Holds the current mesh space.
(defvar *too-close-factor* 0.75) ; Affects what gets deleted when edges
; are split. 1.0 deletes too many.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Classes.
(defclass has-home-space ()
((home :initarg :home :reader home-of)))
;; The home-space acts as the parameter space.
(defclass curved-simplex (has-home-space simplex) ())
(defgeneric home-of (simplex)
(:documentation
"The home of a noncurved simplex is determined by (the first of)
its vertices.")
(:method ((simplex simplex)) (domain-of (first (vertices-of simplex)))))
(defclass triangulation (simplicial-complex)
(;; Most recent triangle inserted; used for beginning searches.
(most-recent :initform nil :accessor %most-recent)))
(defclass c-triangulation (triangulation)
(;; Holds the constraint edges. (The main tool to query this
;; structure is Constraint.)
(constraints :initform (make-instance 'simplicial-complex)
:reader %constraints-of)))
(defclass CDT (c-triangulation) ())
(defclass named-simplicial-complex (simplicial-complex)
(;; Holds names.
(name-table :initform (make-hash-table) :reader %name-table-of)
(default-name :initform nil :accessor %default-name-of)))
(defclass mesh (cdt named-simplicial-complex has-home-space)
(;; Triangles waiting to be improved during Refine-Mesh.
(pending-list :initform nil :accessor %pending-list-of)))
(defmethod initialize-instance :after ((mesh mesh) &rest ignore)
(declare (ignore ignore))
(with-slots (constraints) mesh
(setf constraints (make-instance 'named-simplicial-complex))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Maintenance of Names.
(defgeneric name (simplex mesh)
(:documentation
"The purpose of this function is unknown."))
(defmethod name ((simplex simplex) (nsc named-simplicial-complex))
(gethash (id-number-of simplex) (%name-table-of nsc)))
(defmethod name ((simplex simplex) (mesh mesh))
(case (dimension-of simplex)
((0 1) (name simplex (%constraints-of mesh)))
(2 (call-next-method))
(otherwise (error "Illegal use of NAME. ~s" simplex))))
(defgeneric %set-name (simplex mesh name)
(:documentation
"The purpose of this function is unkown."))
(defmethod %set-name ((simplex simplex) (nsc named-simplicial-complex) name)
(if name
(setf (gethash (id-number-of simplex) (%name-table-of nsc)) name)
(remhash (id-number-of simplex) (%name-table-of nsc)))
name)
(defmethod %set-name ((simplex simplex) (mesh mesh) name)
(case (dimension-of simplex)
((0 1) (%set-name simplex (%constraints-of mesh) name))
(2 (call-next-method))
(otherwise (error "Illegal use of (SETF NAME). ~s" simplex))))
(defsetf name %set-name)
(defgeneric insert (simplex nsc &key name &allow-other-keys)
(:documentation
"The purpose of this function is unknown."))
(defmethod insert ((simplex simplex) (nsc named-simplicial-complex)
&key (name (%default-name-of nsc))
&allow-other-keys)
(setf (name simplex nsc) name)
(call-next-method))
(defgeneric delete-maximal-cell (simplex nsc)
(:documentation
"The purpose of this function is unknown."))
(defmethod delete-maximal-cell ((simplex simplex)
(nsc named-simplicial-complex))
(setf (name simplex nsc) nil)
(call-next-method))
(defgeneric all-names (nsc)
(:documentation
"The purpose of this function is unknown."))
(defmethod all-names ((nsc named-simplicial-complex))
(let ((names nil))
(maphash #'(lambda (ignore name) (declare (ignore ignore))
(pushnew name names))
(%name-table-of nsc))
names))
(defmethod all-names ((sc simplicial-complex))
nil)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utilities.
;; Rotate the given list to bring the specified position to the front
;; (items are numbered 0, 1, 2,...). If no position is specified
;; then one item is rotated (the second item is then on the front).
(defun rotate-list (list &optional (position 1))
(append (nthcdr position list)
(butlast list (- (length list) position))))
;; Rotate a list to bring the leftmost occurrence of the specified
;; member to the front. The test is eql unless altered using the
;; keyword :test. Results are undefined if the given item is not a
;; member of the given list.
(defun member-rotate (item list &key (test #'eql) (key #'identity))
(rotate-list list (position item list :test test :key key)))
;; Convert a vector into a lisp complex number.
(defgeneric complexer (vector)
(:documentation
"The purpose of this function is unknown."))
(defmethod complexer ((vector vector-space-element))
(unless (= 2 (dimension-of (domain-of vector)))
(error "Wrong length vector for conversion to complex number. ~s" vector))
(cl:complex (convert-to-lisp-number (ref vector 0))
(convert-to-lisp-number (ref vector 1))))
(defgeneric coordinate-list (vector)
(:documentation
"The purpose of this function is unknown."))
(defmethod coordinate-list ((vector vector-space-element))
(loop for n below (dimension-of (domain-of vector))
collect (convert-to-lisp-number (ref vector n))))
(defun sqr (item)
(* item item))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Queue (a list implementation of a standard queue).
;; FIXTHIS: All this queue stuff should be somewhere else. There are
;; also implementations (with consistent interface) for stack,
;; priority-queue, and random-queue.
(defclass queue ()
((front :initarg :front :accessor front :reader %contents)
;; The last item in the queue.
(back :initarg :back :accessor back)))
(defun make-queue (&key (initial-contents nil))
(setf initial-contents (copy-list initial-contents))
(make-instance 'queue :front initial-contents
:back (last initial-contents)))
(defgeneric clearq (queue)
(:documentation
"The purpose of this function is unknown."))
(defmethod clearq ((queue queue))
(setf (front queue) nil)
(setf (back queue) nil))
(defgeneric insertq (item queue)
(:documentation
"The purpose of this function is unknown."))
(defmethod insertq (item (queue queue))
(with-slots (front back) queue
(cond (front ; Nonempty queue.
(setf (rest back) (list item))
(setf back (rest back)))
(t ; Empty queue.
(setf front (list item))
(setf back front)))))
;; If :delete is null then the item is not removed from the queue.
(defgeneric getq (queue &key delete)
(:documentation
"The purpose of this function is unknown."))
(defmethod getq ((queue queue) &key (delete t))
(with-slots (front) queue
(let ((item (first front)))
(if delete (setf front (rest front)))
item)))
(defgeneric emptyq? (queue)
(:documentation
"The purpose of this function is unknown."))
(defmethod emptyq? ((queue queue))
(not (front queue)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Geometry.
;; Given 3 complex numbers forming a triangle, return the complex
;; number that is the circumcenter. Works with lisp complex numbers,
;; since the weyl versions have some problems.
(defun complex-circumcenter (a b c)
;; Map a and b to 0 and 1.
(let* ((bb (cl:- b a))
(cc (cl:/ (cl:- c a) bb))
(cx (cl:realpart cc))
(cy (cl:imagpart cc))
w)
(when (cl:= 0 cy) (error "Flat triangle. ~s ~s ~s" a b c))
(setf w (cl:complex 0.5 (cl:/ (cl:+ (cl:* cx cx) (cl:* cy cy) (cl:- cx))
(cl:* 2.0 cy))))
;; Map back.
(cl:+ (cl:* w bb) a)))
;; Determine the center of a circle described by two points on the
;; circle and a radius. The two points and the returned center are
;; all complex numbers. There can be zero, one, or two solutions to
;; this problem. If there is no solution then a warning is printed
;; and the midpoint of the two vertices is returned. A radius of 0
;; is taken as a special case and no warning is issued. If there are
;; two solutions then normally the solution to the left of the line
;; from a to b is returned. The other solution is returned if the
;; given radius is negative. Uses Lisp complex numbers.
(defun circle-center (a b radius)
;; Based on using complex arithmetic to map 0,2 to a,b. c1 and c2
;; represent the center in various coordinate frames.
(let* ((bb (cl:* 0.5 (cl:- b a)))
(rad (cl:/ radius (cl:abs bb)))
(cy (cl:sqrt (cl:- 1 (cl:* rad rad))))
(c1 (cl:+ 1 cy))
;; Check for bad radius and alternate solution.
(c2 (cond
((cl:/= 0 (cl:realpart cy))
(unless (cl:= 0 radius)
(warn "Radius too small; half circle assumed. ~s ~s" a b))
1.0)
((cl:plusp radius) c1)
(t (cl:conjugate c1)))))
;; Switch back to original reference frame.
(cl:+ a (cl:* bb c2))))
(defgeneric make-mean-point (points &key mean-space point-space)
(:documentation
"The purpose of this function is unknown."))
;; Make a point that is the mean of the given points. The mean-space
;; is the domain in which the mean is calculated; the point-space is
;; the domain in which the resulting point resides. The two spaces
;; are presumably related via coerce.
(defmethod make-mean-point ((points list) &key
(mean-space (domain-of (first points)))
(point-space (domain-of (first points))))
(let* ((vectors (mapcar #'(lambda (p) (coerce p mean-space)) points))
(mean (/ (apply #'%plus vectors) (float (length points))))
(point (make-point point-space (coerce mean point-space))))
(unless (eql point-space mean-space)
(setf (coerce point mean-space) mean))
point))
(defconstant %deg-over-rad (cl:/ 180.0 cl:pi))
;; Compute the angle between two vectors.
(defgeneric angle (vertex triangle &rest args &key space)
(:documentation
"The purpose of this function is unknown."))
(defmethod angle ((a vector-space-element) (b vector-space-element)
&key (radians nil) (degrees (not radians)) &allow-other-keys)
;; The realpart is needed because we sometimes get a small
;; imaginary component due to numerical error.
(let ((angle (cl:realpart (cl:acos (convert-to-lisp-number
(/ (dot-product a b)
(sqrt (* (dot-product a a)
(dot-product b b)))))))))
(if degrees (cl:* %deg-over-rad angle)
angle)))
;; Create a parameter-space. This is used mainly to create simplices
;; that are curved in the target-space. The map should take a
;; vector-space-element and produce an element of the target-space.
;;
;; If an inverse-map is given then that is used build the
;; correspondence between the parameter-space and the target-space
;; (but inverse-maps are usually unavailable). The inverse-map
;; should take an element of the target-space and produce a lisp
;; vector. The dimension of the parameter-space must be specified
;; when an inverse-map is used.
;;
;; If no inverse-map is available then the spaces are tied together
;; via the correspondence between the parameter-vectors and the
;; target-points (i.e., the result will be that the target-points can
;; be coerced into the parameter-space where they have the
;; coordinates given by the parameter vectors). The
;; parameter-vectors must be lisp vectors. [At some point, we may
;; want to check that the map actually takes the parameter-vectors
;; into the target-points.]
;;
(defgeneric make-parameter-space (map-function target-space &key parameter-vectors
target-points dimension inverse-map)
(:documentation
"The purpose of this function is unknown."))
(defmethod make-parameter-space ((map function) (target-space vector-space)
&key parameter-vectors target-points
dimension inverse-map)
(unless dimension
(if parameter-vectors (setf dimension (length (first parameter-vectors)))
(error "Cannot determine dimension in Make-Parameter-Space.")))
(let ((parameter-space (make-euclidean-space dimension))) ; A new space.
(make-homomorphism parameter-space map target-space)
;; Establish the inverse correspondence between the spaces.
(if inverse-map
(make-homomorphism target-space
#'(lambda (e)
(make-element parameter-space
(funcall inverse-map e)))
parameter-space)
(mapcar #'(lambda (vector point)
(setf (coerce point parameter-space)
(make-element parameter-space vector)))
parameter-vectors target-points))
parameter-space))
;; Split the given simplex along the given face using the given
;; splitting-point. The default splitting-point is the mean-point of
;; the face. Results are undefined if the given face does not
;; correspond to part of the simplex. Note that the calculations for
;; the splitting-point and the creation of new simplices takes place
;; in the simplex's home coordinate system.
(defgeneric split (simplex where &rest args)
(:documentation
"The purpose of this function is unknown."))
(defmethod split ((simplex simplex) (where (eql nil)) &key
(face (vertices-of simplex))
(splitting-point
(make-mean-point face :mean-space (home-of simplex))))
(loop with points = (vertices-of simplex)
with home = (home-of simplex)
for v in face
for new-set = (subst splitting-point v points)
collect (make-instance (class-of simplex)
:vertices new-set :home home)))
;; Split a simplex within a simplicial-complex.
(defmethod split ((simplex simplex) (where simplicial-complex) &rest args)
(loop with simplices = (apply #'split simplex nil args)
initially (delete-maximal-cell simplex where)
for s in simplices do
(insert s where)
finally (return simplices)))
;; Splitting a list splits each thing in the list.
(defmethod split ((things list) where &rest ignore)
(declare (ignore ignore))
(loop for thing in things
append (split thing where)))
;; Size of the given simplex. We use the length of the longest side.
(defgeneric simplex-size (simplex &optional space)
(:documentation
"The purpose of this function is unknown."))
(defmethod simplex-size ((simplex simplex) &optional (space (home-of simplex)))
(loop with points = (vertices-of simplex)
repeat (1- (length points))
maximize (loop with p = (first points)
for other in (rest points)
maximize (convert-to-lisp-number
(distance p other :space space)))
do (setf points (rest points))))
;; Returns :left, :right, or :on depending on position of third
;; vertex in relation to ray from first to second vertex. Some
;; effort has been taken to ensure that this operation is safe in the
;; sense that the answer is the same even when the three vertices are
;; given in a different order. Without this caution, you can run
;; into problems (due to numerical error); for instance, a vertex
;; that looks as if it's on the line between two triangles can be
;; claimed to be :outside of each of them.
(defgeneric bend (space &rest points)
(:documentation
"The purpose of this function is unknown."))
(defmethod bend ((space vector-space) &rest three-points)
(unless (= 3 (length three-points))
(error "Improper number of arguments to Bend; need space then 3 points."))
(let* ((ordered (sort (copy-list three-points) #'cl:< :key #'id-number-of))
(a (coerce (first ordered) space))
(b (coerce (second ordered) space))
(c (coerce (third ordered) space))
(det (* (sign-of-permutation ordered three-points)
(+ (* (- (ref b 1) (ref a 1)) (- (ref c 0) (ref b 0)))
(* (- (ref a 0) (ref b 0)) (- (ref c 1) (ref b 1)))))))
(cond ((> 0 det) :left)
((< 0 det) :right)
(t :on))))
(defgeneric distance (vector1 vector2 &rest ignore)
(:documentation
"The purpose of this function is unknown."))
(defmethod distance ((vectora vector-space-element)
(vectorb vector-space-element) &rest ignore)
(declare (ignore ignore))
(let ((diff (- vectorb vectora)))
(sqrt (dot-product diff diff))))
(defmethod distance ((pointa point) (pointb point) &key (space nil))
(unless space
(error "Must specify space for Distance between points."))
(call-next-method (coerce pointa space) (coerce pointb space)))
(defmethod distance ((lista list) (listb list) &rest ignore)
(declare (ignore ignore))
(unless (= (length lista) (length listb))
(error "Cannot comput distance between lists of different lengths. ~s ~s"
lista listb))
(loop for a in lista
for b in listb
sum (sqr (- b a)) into sum-of-squares
finally (return (sqrt sum-of-squares))))
;; Returns T iff the given edges cross each other. Returns true even
;; when one vertex of one edge is :on the other edge.
(defgeneric edges-cross? (space edge1 edge2)
(:documentation
"The purpose of this function is unknown."))
(defmethod edges-cross? ((space vector-space) (edge-a list) (edge-b list))
(and (not (eql (bend space (first edge-a) (second edge-a) (first edge-b))
(bend space (first edge-a) (second edge-a) (second edge-b))))
(not (eql (bend space (first edge-b) (second edge-b) (first edge-a))
(bend space (first edge-b) (second edge-b) (second edge-a))))
))
;; A bounding-box is a pair (low high) of coordinate lists. For
;; instance, 2D points would produce: ((low-x low-y) (high-x
;; high-y)). Useful mostly for graphics. Also used for mesh
;; initialization.
(defgeneric bounding-box (point space)
(:documentation
"The purpose of this function is unknown."))
(defmethod bounding-box ((point point) (space vector-space))
(let ((c (coordinate-list (coerce point space))))
(list c c)))
(defmethod bounding-box ((list list) (space vector-space))
(if (= 1 (length list))
(bounding-box (first list) space)
(let ((a (bounding-box (first list) space))
(b (bounding-box (rest list) space)))
(list (mapcar #'cl:min (first a) (first b))
(mapcar #'cl:max (second a) (second b))))))
(defmethod bounding-box ((simplex simplex) (space vector-space))
(bounding-box (vertices-of simplex) space))
(defmethod bounding-box ((sc simplicial-complex) (space vector-space))
(let (old new)
(map-over-cells (cell) sc
(setf new (bounding-box cell space))
(unless old (setf old new))
(setf old (list (mapcar #'cl:min (first new) (first old))
(mapcar #'cl:max (second new) (second old)))))
old))
;; Report the measure of the given simplex in the given space.
;; FIXTHIS: This should be a general measure function based on
;; determinants so that it works in any dimension.
(defgeneric measure (simplex space)
(:documentation
"The purpose of this function is unknown."))
(defmethod measure ((simplex simplex) (space vector-space))
(let ((vertices (vertices-of simplex)))
(case (dimension-of simplex)
(0 0)
(1 (distance (first vertices) (second vertices) :space space))
(2 (let* ((a (complexer (coerce (first vertices) space)))
(b (complexer (coerce (second vertices) space)))
(c (complexer (coerce (third vertices) space)))
(bb (cl:- b a))
(cc (cl:/ (cl:- c a) bb)))
(cl:* 0.5 (cl:imagpart cc) bb (cl:conjugate bb))))
(otherwise (error "Measure not yet implemented for higher dimensions."))
)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Segments.
;; Given endpoints of an arc together with other information about
;; the arc's shape, return a segment corresponding to the arc. The
;; arc can go :thru a given vertex, can have a given :radius, or can
;; have a given :center vertex. A radius that is too small (e.g.,
;; zero) creates a half circle. Weird things can happen if the given
;; center is impossible for the given endpoints or if more than one
;; of these options is given. Direction can be specified, either :cw
;; or :ccw. If direction is not specified then :ccw is the default.
;; Uses Lisp complex numbers.
(defgeneric arc (point1 point2 space &key thru radius center clockwise cw
counterclockwise ccw direction)
(:documentation
"The purpose of this function is unknown."))
(defmethod arc ((apoint point) (bpoint point) (space vector-space) &key
thru radius center
clockwise (cw clockwise)
(counterclockwise (not cw)) (ccw counterclockwise)
(direction (if ccw :ccw :cw)))
;; Convert to complex. A and b are endpoints of the arc; c is the center.
(let* ((a (complexer (coerce apoint space)))
(b (complexer (coerce bpoint space)))
(c (cond
;; If center was given, we do a simple conversion.
(center (complexer (coerce center space)))
;; If thru was given, we use the circumcenter.
(thru (complex-circumcenter a b (complexer (coerce thru space))))
;; If radius was given, we compute the center.
(radius (circle-center a b (convert-to-lisp-number radius)))))
theta-a theta-b generator)
;; Swap the endpoints if not doing :ccw.
(when (not (eql :ccw direction))
(rotatef a b) (rotatef apoint bpoint))
;; Determine angles and radius (the existing radius value could
;; be nil, too small, or negative).
(setf theta-a (cl:phase (cl:- a c)))
(setf theta-b (cl:phase (cl:- b c)))
(setf radius (cl:abs (cl:- a c)))
;; Make sure direction is right.
(unless (cl:< theta-a theta-b)
(setf theta-b (cl:+ theta-b (cl:* 2 cl:pi))))
;; Create the parametric function.
(setf generator #'(lambda (theta)
(let* ((ltheta (convert-to-lisp-number theta))
(d (cl:complex (cl:cos ltheta) (cl:sin ltheta)))
(transformed (cl:+ c (cl:* radius d))))
(make-element space (cl:realpart transformed)
(cl:imagpart transformed)))))
;; Create the segment.
(make-instance 'curved-simplex :vertices (list apoint bpoint)
:home (make-parameter-space
#'(lambda (v) (funcall generator (ref v 0)))
space
:parameter-vectors (list (vector theta-a)
(vector theta-b))
:target-points (list apoint bpoint)))))
;; Note that the generator here take a number (the parameter) and
;; returns an element of the space.
(defgeneric make-curved-segment (space param1 endpoint1 param2 endpoint2 generator)
(:documentation
"The purpose of this function is unknown."))
(defmethod make-curved-segment ((space vector-space)
(a-param-value number) (a-endpoint point)
(b-param-value number) (b-endpoint point)
(generator function))
(make-instance 'curved-simplex :vertices (list a-endpoint b-endpoint)
:home (make-parameter-space
#'(lambda (v) (funcall generator (ref v 0)))
space
:parameter-vectors (list (vector a-param-value)
(vector b-param-value))
:target-points (list a-endpoint b-endpoint))))
;; Return the endpoint common to two segments.
(defun common-endpoint (segment-a segment-b)
(first (intersection (vertices-of segment-a) (vertices-of segment-b))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Triangles.
;; Report whether the given point is :inside, :outside, or :on the
;; given triangle. Works regardless of whether triangle is clockwise
;; or counterclockwise. Returns multiple values -- if the vertex is
;; :on the triangle then an appropriate side or vertex is also
;; returned.
(defgeneric point-vs-triangle (vertex triangle &key space)
(:documentation
"The purpose of this function is unknown."))
(defmethod point-vs-triangle ((vertex point) (triangle simplex)
&key (space (home-of triangle)))
(unless (triangle? triangle)
(error "Point-vs-Triangle only works on triangles. ~s" triangle))
(loop with on-side = nil
with vertices = (vertices-of triangle)
for va in vertices
for vb in (rotate-list vertices)
for bend = (bend space va vb vertex)
collect bend into bends
do (if (eql bend :on) (setf on-side (list va vb)))
finally
(cond
((and (member :left bends) (member :right bends)) (return :outside))
(on-side (return (values :on (cond
((0? (distance (first on-side) vertex
:space space))
(first on-side))
((0? (distance (second on-side) vertex
:space space))
(second on-side))
(t on-side)))))
(T (return :inside)))))
;; Return the (counterclockwise) oriented side opposite the given
;; vertex.
(defgeneric ccw-side (vertex triangle)
(:documentation
"The purpose of this function is unknown."))
(defmethod ccw-side ((vertex point) (triangle simplex))
(let ((side (opposite vertex triangle)))
(if (eql :left (apply #'bend (home-of triangle) vertex side))
side (rotate-list side))))
;; Given a set of adjacency triples of the form (left-ptr vertex
;; right-ptr), return a list of triangles created by these triples.
;; When triangles are created, the points are in counterclockwise
;; order. If a poison-vertex is given then no triangle that contains
;; that vertex or pair of triangles that cover that vertex will be
;; allowed until the very last triangle created. This function takes
;; linear time: Whenever a triangle is created, two new vertices must
;; be inspected (they are pushed onto triples). The sum
;; 3*(triangles-to-do) + |triples| always decreases.
(defun triangulate-triples (triples poison-vertex space triangle-class
&rest args)
(loop with left and right and triangle and relation
;; T when we need to watch for pair of covering triangles.
with on-flag = nil
with triangle-list ;; What we return.
with triangles-to-do = (- (length triples) 2)
;; Choose the next vertex.
while (and (> triangles-to-do 0) triples)
for triple = (pop triples)
for v = (second triple)
;; Use it if... it hasn't been done (once underway, a
;; triple can appear more than once), it has a left
;; neighbor, it has a right neighbor, and the triangle bends
;; the right way.
do (when (and v
(first triple) (setq left (second (first triple)))
(third triple) (setq right (second (third triple)))
(eql :left (bend space left right v)))
(setf triangle (apply #'make-instance triangle-class
:vertices (list left right v)
:home space args))
;; Check for the poison-vertex. Automatically ok if
;; there's no poison vertex or we're on the last
;; triangle. Also, ok if this is first triangle that
;; poison-vertex is :on.
(setf relation (if poison-vertex
(point-vs-triangle
poison-vertex triangle :space space)))
(when (or (null poison-vertex) (= triangles-to-do 1)
(eql :outside relation)
(and (eql :on relation) (not on-flag) (setq on-flag T)))
;; Save the triangle, cancel v, and update the
;; neighboring vertices. The neighboring vertices
;; should be rechecked (put back on the list).
(decf triangles-to-do) (push triangle triangle-list)
(setf (second triple) nil)
(setf (third (first triple)) (third triple))
(setf (first (third triple)) (first triple))
(push (first triple) triples)
(push (third triple) triples)))
finally (return triangle-list))) ; Return the list of triangles.
;; Given a list of vertices that describe a star-shaped polygon in
;; counterclockwise order and a star-source vertex, return a set of
;; triangles that fills the star-shaped polygon. Note that the star
;; source is not used as a vertex in the triangulation; it's needed
;; to make the triangulation algorithm efficient. Planned use:
;; retriangulate a hole in a triangulation after a single vertex has
;; been eliminated.
(defgeneric star-triangulate (star-shape star-source space triangle-class &rest args)
(:documentation
"The purpose of this method is unknown."))
(defmethod star-triangulate ((star-shape list) (star-source point)
(space vector-space) triangle-class
&rest args)
;; We convert the vertex list into a list of adjacency triples of
;; the form (left v right).
(let ((triples (mapcar #'(lambda (v) (list nil v nil)) star-shape)))
(mapc #'(lambda (pred current succ)
(setf (third current) pred) (setf (first current) succ))
triples (rotate-list triples) (rotate-list triples 2))
;; Pass the triples to our triangulator.
(apply #'triangulate-triples
triples star-source space triangle-class args)))
;; Given a list of vertices that describe a flat polygon in
;; counterclockwise order, return a set of triangles that fills the
;; flat polygon. The polygon must be illuminated by the base and all
;; vertices must be to one side of the base. The base is determined
;; by the first and last vertices given. Planned use: Triangulate
;; the hole that appears when a constraint side is forced through a
;; triangulation. There is one hole on each side of such a
;; constraint side.
(defgeneric flat-triangulate (flat-polygon space triangle-class &rest args)
(:documentation
"The purpose of this function is unknown."))
(defmethod flat-triangulate ((flat-polygon list) (space vector-space)
triangle-class &rest args)
;; We convert the vertex list into a list of adjacency triples of
;; the form (left v right)
(let ((triples (mapcar #'(lambda (v) (list nil v nil)) flat-polygon)))
(mapc #'(lambda (pred current succ)
(setf (third current) pred) (setf (first current) succ))
triples (rest triples) (rest (rest triples)))
;; Pass the triples to our triangulator.
(apply #'triangulate-triples triples nil space triangle-class args)))
;; Return the circumcenter of the given triangle.
(defgeneric circumcenter (triangle &key space)
(:documentation
"The purpose of this function is unknown."))
(defmethod circumcenter ((triangle simplex) &key (space (home-of triangle)))
(unless (triangle? triangle)
(error "Circumcenter is implemented only for triangles. ~s" triangle))
(let ((center (apply #'complex-circumcenter
(mapcar #'(lambda (v) (complexer (coerce v space)))
(vertices-of triangle)))))
(make-point space (cl:realpart center) (cl:imagpart center))))
(defgeneric circumradius (triangle &key space)
(:documentation
"The purpose of this method is unknown."))
(defmethod circumradius ((triangle simplex) &key (space (home-of triangle)))
(distance (first (vertices-of triangle))
(circumcenter triangle :space space) :space space))
;; Return the triangle's angle at the given vertex.
(defmethod angle ((vertex point) (triangle simplex)
&rest args &key (space (home-of triangle)))
(unless (triangle? triangle)
(error "Function Angle is implemented only for triangles. ~s" triangle))
(let ((others (remove vertex (vertices-of triangle)))
(v (coerce vertex space)))
(apply #'angle (- (coerce (first others) space) v)
(- (coerce (second others) space) v) args)))
(defgeneric angles (triangle &key space)
(:documentation
"The purpose of this function is unknown."))
(defmethod angles ((triangle simplex) &key (space (home-of triangle)))
(loop for v in (vertices-of triangle)
collect (angle v triangle :space space)))
(defgeneric vertices-sorted-by-angle (triangle &key space)
(:documentation
"The purpose of this function is unknown."))
(defmethod vertices-sorted-by-angle ((triangle simplex) &key
(space (home-of triangle)))
(loop with (a b c) = (mapcar #'(lambda (v) (coerce v space))
(vertices-of triangle))
repeat 3
for diff = (- c b)
for size = (convert-to-lisp-number (dot-product diff diff))
collect (cons size a) into pairs
do (rotatef a b c)
finally (return (mapcar #'rest (sort pairs #'cl:< :key #'first)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Triangulations.
(defmethod insert ((triangle simplex) (triangulation triangulation)
&rest ignore)
(declare (ignore ignore))
(unless (= 2 (dimension-of triangle))
(error "Only triangles can be INSERTed into a triangulation. ~s" triangle))
(setf (%most-recent triangulation) triangle)
(call-next-method))
;; Return a list of all triangles adjacent to the given vertices.
(defgeneric neighbors (vertices triangulation)
(:documentation
"The purpose of this function is unknown."))
(defmethod neighbors ((vertices list) (triangulation triangulation))
(case (length vertices)
(1 (cofacets (cofacets (get-cell vertices triangulation) triangulation)
triangulation))
(2 (cofacets (get-cell vertices triangulation) triangulation))))
;; Report the neighbor of the given triangle across the given side.
(defgeneric neighbor (triangle side triangulation)
(:documentation
"The purpose of this function is unknown."))
(defmethod neighbor ((triangle simplex) (side list)
(triangulation triangulation))
(find triangle (cofacets (get-cell side triangulation) triangulation)
:test-not #'eql))
;; Return a list of all the triangles within a given region. The
;; region's boundaries are defined by the function Neighbor. This
;; method is not terribly efficient, but it probably won't be done
;; all that often.
(defgeneric neighborhood (start triangulation)
(:documentation
"The purpose of this function is unknown."))
(defmethod neighborhood ((start simplex) (triangulation triangulation))
(unless (triangle? start)
(error "Neighborhood must start at a triangle. ~s" start))
(loop with stack = (list start)
with mark-list = (list start)
while stack ; Process triangles until we run out.
for triangle = (pop stack)
collect triangle
;; Check each neighbor; if it's not on the stack then put it
;; there and mark it.
do (loop for side in (facets triangle triangulation)
for neighbor = (neighbor triangle (vertices-of side)
triangulation)
if (and neighbor (not (member neighbor mark-list)))
do (push neighbor stack)
(push neighbor mark-list))))
;; Return a triangle base (a list of two points) that is between the
;; given vertex and the corresponding triangle apex. Triangle
;; orientation does not matter (aside from not :flat).
(defgeneric near-base (triangle vertex &key space)
(:documentation
"The purpose of this function is unknown."))
(defmethod near-base ((triangle simplex) (vertex point)
&key (space (home-of triangle)))
(unless (triangle? triangle)
(error "Near-Base works only on triangles. ~s" triangle))
(loop with (a b c) = (vertices-of triangle)
with direction = (bend space a b c)
repeat 3
if (eql direction (bend space vertex c b))
return (list b c)
do (rotatef a b c)))
;; Given a triangle and a destination (a vertex), travel from the
;; triangle to the destination. We return the triangle that contains
;; the destination if one exists. Otherwise, we return multiple
;; values: nil and the side where we fell off the triangulation.
;; Travel is not necessarily along a straight line, although each
;; triangle we visit is closer than the previous one.
(defgeneric directed-locate (triangle destination triangulation &key space)
(:documentation
"The purpose of this function is unknown."))
(defmethod directed-locate ((triangle simplex) (destination point)
(triangulation triangulation)
&key (space (home-of triangle)))
(unless (triangle? triangle)
(error "Directed-Locate must start at a triangle. ~s" triangle))
;; Find a side of the triangle that points toward the destination.
(loop for side = (near-base triangle destination :space space)
;; If no side found then we're done.
do (unless side (return triangle))
;; Update triangle. If no triangle, we've fallen off.
(setf triangle (neighbor triangle side triangulation))
(unless triangle (return (values nil side)))))
;; Find the triangle that contains the given vertex.
(defgeneric locate (vertex triangulation)
(:documentation
"The purpose of this method is unknown."))
(defmethod locate ((vertex point) (triangulation triangulation))
;; Check first to see if it's already present in the triangulation...
(or (first (neighbors (list vertex) triangulation))
;; Then try a directed search...
(let ((start (%most-recent triangulation)))
(if (maximal-cell? start triangulation)
(directed-locate start vertex triangulation)))
;; Finally, try looking at everything.
(catch 'found
(map-over-maximal-cells (triangle) triangulation
(if (not (eql :outside (point-vs-triangle vertex triangle)))
(throw 'found triangle)))
nil)))
;; An inefficient way to look at all triangles in a triangulation.
;; Not used elsewhere, but handy for debugging.
(defgeneric triangles (triangulation)
(:documentation
"The purpose of this function is unknown."))
(defmethod triangles ((triangulation triangulation))
(let ((triangles nil))
(map-over-maximal-cells (triangle) triangulation
(push triangle triangles))
triangles))
;; Report all vertices adjacent to given vertex (not necessarily in order).
(defgeneric adj-vertices (vertex triangulation)
(:documentation
"The purpose of this function is unknown."))
(defmethod adj-vertices ((vertex point) (triangulation triangulation))
(loop for edge in (cofacets (get-cell (list vertex) triangulation)
triangulation)
for vertices = (vertices-of edge)
collect (if (eql vertex (first vertices))
(second vertices)
(first vertices))))
;; Returns T iff the given edge can be flipped without producing an
;; inverted triangle.
(defgeneric flip-ok? (edge triangulation)
(:documentation
"The purpose of this function is unknown."))
(defmethod flip-ok? ((edge list) (triangulation triangulation))
(let* ((triangles (neighbors edge triangulation))
(apexes (mapcar #'(lambda (tri) (first (opposite edge tri)))
triangles))
(space (home-of (first triangles))))
(and (= 2 (length triangles))
(eql (apply #'bend space (first edge) apexes)
(apply #'bend space (second edge) (reverse apexes))))))
;; Check if Delaunay property holds for the given edge. The Delaunay
;; property here is that the opposite angles sum to less than or
;; equal to 180 degrees. There are lots of choices on how to
;; implement the Delaunay property (e.g., look at the circles, choose
;; the smallest angle sum, etc.). This one has the advantage that it
;; also works for curved surface Delaunay triangulations. At some
;; point, it may be desirable to switch to a faster method.
(defgeneric delaunay? (edge triangulation &key space)
(:documentation
"The purpose of this function is unknown."))
(defmethod delaunay? ((edge list) (triangulation triangulation) &key space)
(let* ((triangles (neighbors edge triangulation))
(t1 (first triangles))
(t2 (second triangles)))
(unless space (setf space (home-of t1)))
(or (< (length triangles) 2)
(<= (+ (angle (first (opposite edge t1)) t1 :space space)
(angle (first (opposite edge t2)) t2 :space space))
180.0))))
;; Flip the given side of the triangulation. In other words, the
;; side implicitly defines a quadrilateral; replace the side with the
;; other diagonal of the quadrilateral. This function will go ahead
;; and Flip even when inverted triangles are produced. To avoid
;; this, check beforehand using Flip-OK?.
(defgeneric flip (side triangulation)
(:documentation
"The purpose of this function is unknown."))
(defmethod flip ((side list) (triangulation triangulation))
(let* ((triangles (neighbors side triangulation))
(vert (first (opposite side (second triangles)))))
(delete-maximal-cell (second triangles) triangulation)
(split (first triangles) triangulation
:face side :splitting-point vert)))
;; Split the given edge of the triangulation. Expect strange results
;; if the splitting-vertex is not on or near the edge.