-
Notifications
You must be signed in to change notification settings - Fork 115
/
geometry.scad
2920 lines (2695 loc) · 130 KB
/
geometry.scad
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
//////////////////////////////////////////////////////////////////////
// LibFile: geometry.scad
// Perform calculations on lines, polygons, planes and circles, including
// normals, intersections of objects, distance between objects, and tangent lines.
// Throughout this library, lines can be treated as either unbounded lines, as rays with
// a single endpoint or as segments, bounded by endpoints at both ends.
// Includes:
// include <BOSL2/std.scad>
// FileGroup: Math
// FileSummary: Geometrical calculations including intersections of lines, circles and planes, circle from 3 points
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////
// Section: Lines, Rays, and Segments
// Function: is_point_on_line()
// Synopsis: Determine if a point is on a line, ray or segment.
// Topics: Geometry, Points, Segments
// See Also: is_collinear(), is_point_on_line(), point_line_distance(), line_from_points()
// Usage:
// pt = is_point_on_line(point, line, [bounded], [eps]);
// Description:
// Determine if the point is on the line segment, ray or segment defined by the two between two points.
// Returns true if yes, and false if not. If bounded is set to true it specifies a segment, with
// both lines bounded at the ends. Set bounded to `[true,false]` to get a ray. You can use
// the shorthands RAY and SEGMENT to set bounded.
// Arguments:
// point = The point to test.
// line = Array of two points defining the line, ray, or segment to test against.
// bounded = boolean or list of two booleans defining endpoint conditions for the line. If false treat the line as an unbounded line. If true treat it as a segment. If [true,false] treat as a ray, based at the first endpoint. Default: false
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function is_point_on_line(point, line, bounded=false, eps=EPSILON) =
assert(is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
assert(is_vector(point), "Point must be a vector")
assert(_valid_line(line, len(point),eps),"Given line is not valid")
_is_point_on_line(point, line, bounded,eps);
function _is_point_on_line(point, line, bounded=false, eps=EPSILON) =
let(
v1 = (line[1]-line[0]),
v0 = (point-line[0]),
t = v0*v1/(v1*v1),
bounded = force_list(bounded,2)
)
abs(cross(v0,v1))<=eps*norm(v1)
&& (!bounded[0] || t>=-eps)
&& (!bounded[1] || t<1+eps) ;
///Internal - distance from point `d` to the line passing through the origin with unit direction n
///_dist2line works for any dimension
function _dist2line(d,n) = norm(d-(d * n) * n);
///Internal
function _valid_line(line,dim,eps=EPSILON) =
is_matrix(line,2,dim)
&& norm(line[1]-line[0])>eps*max(norm(line[1]),norm(line[0]));
//Internal
function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps);
/// Internal Function: _is_at_left()
/// Usage:
/// pt = point_left_of_line2d(point, line);
/// Topics: Geometry, Points, Lines
/// Description:
/// Return true iff a 2d point is on or at left of the line defined by `line`.
/// Arguments:
/// pt = The 2d point to check position of.
/// line = Array of two 2d points forming the line segment to test against.
/// eps = Tolerance in the geometrical tests.
function _is_at_left(pt,line,eps=EPSILON) = _tri_class([pt,line[0],line[1]],eps) <= 0;
/// Internal Function: _degenerate_tri()
/// Usage:
/// degen = _degenerate_tri(triangle);
/// Topics: Geometry, Triangles
/// Description:
/// Return true for a specific kind of degeneracy: any two triangle vertices are equal
/// Arguments:
/// tri = A list of three 2d points
/// eps = Tolerance in the geometrical tests.
function _degenerate_tri(tri,eps) =
max(norm(tri[0]-tri[1]), norm(tri[1]-tri[2]), norm(tri[2]-tri[0])) < eps ;
/// Internal Function: _tri_class()
/// Usage:
/// class = _tri_class(triangle);
/// Topics: Geometry, Triangles
/// Description:
/// Return 1 if the triangle `tri` is CW.
/// Return 0 if the triangle `tri` has colinear vertices.
/// Return -1 if the triangle `tri` is CCW.
/// Arguments:
/// tri = A list of the three 2d vertices of a triangle.
/// eps = Tolerance in the geometrical tests.
function _tri_class(tri, eps=EPSILON) =
let( crx = cross(tri[1]-tri[2],tri[0]-tri[2]) )
abs( crx ) <= eps*norm(tri[1]-tri[2])*norm(tri[0]-tri[2]) ? 0 : sign( crx );
/// Internal Function: _pt_in_tri()
/// Usage:
/// class = _pt_in_tri(point, tri);
/// Topics: Geometry, Points, Triangles
/// Description:
// For CW triangles `tri` :
/// return 1 if point is inside the triangle interior.
/// return =0 if point is on the triangle border.
/// return -1 if point is outside the triangle.
/// Arguments:
/// point = The point to check position of.
/// tri = A list of the three 2d vertices of a triangle.
/// eps = Tolerance in the geometrical tests.
function _pt_in_tri(point, tri, eps=EPSILON) =
min( _tri_class([tri[0],tri[1],point],eps),
_tri_class([tri[1],tri[2],point],eps),
_tri_class([tri[2],tri[0],point],eps) );
/// Internal Function: _point_left_of_line2d()
/// Usage:
/// pt = point_left_of_line2d(point, line);
/// Topics: Geometry, Points, Lines
/// Description:
/// Return >0 if point is left of the line defined by `line`.
/// Return =0 if point is on the line.
/// Return <0 if point is right of the line.
/// Arguments:
/// point = The point to check position of.
/// line = Array of two points forming the line segment to test against.
function _point_left_of_line2d(point, line, eps=EPSILON) =
assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." )
// cross(line[0]-point, line[1]-line[0]);
_tri_class([point,line[1],line[0]],eps);
// Function: is_collinear()
// Synopsis: Determine if points are collinear.
// Topics: Geometry, Points, Collinearity
// See Also: is_collinear(), is_point_on_line(), point_line_distance(), line_from_points()
// Usage:
// bool = is_collinear(a, [b, c], [eps]);
// Description:
// Returns true if the points `a`, `b` and `c` are co-linear or if the list of points `a` is collinear.
// Arguments:
// a = First point or list of points.
// b = Second point or undef; it should be undef if `c` is undef
// c = Third point or undef.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function is_collinear(a, b, c, eps=EPSILON) =
assert( is_path([a,b,c],dim=undef)
|| ( is_undef(b) && is_undef(c) && is_path(a,dim=undef) ),
"Input should be 3 points or a list of points with same dimension.")
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
let( points = is_def(c) ? [a,b,c]: a )
len(points)<3 ? true :
_noncollinear_triple(points,error=false,eps=eps) == [];
// Function: point_line_distance()
// Synopsis: Find shortest distance from point to a line, segment or ray.
// Topics: Geometry, Points, Lines, Distance
// See Also: is_collinear(), is_point_on_line(), point_line_distance(), line_from_points()
// Usage:
// dist = point_line_distance(pt, line, [bounded]);
// Description:
// Finds the shortest distance from the point `pt` to the specified line, segment or ray.
// The bounded parameter specifies the whether the endpoints give a ray or segment.
// By default assumes an unbounded line.
// Arguments:
// pt = A point to find the distance of from the line.
// line = A list of two points defining a line.
// bounded = a boolean or list of two booleans specifiying whether each end is bounded. Default: false
// Example:
// dist1 = point_line_distance([3,8], [[-10,0], [10,0]]); // Returns: 8
// dist2 = point_line_distance([3,8], [[-10,0], [10,0]],SEGMENT); // Returns: 8
// dist3 = point_line_distance([14,3], [[-10,0], [10,0]],SEGMENT); // Returns: 5
function point_line_distance(pt, line, bounded=false) =
assert(is_bool(bounded) || is_bool_list(bounded,2), "\"bounded\" is invalid")
assert( _valid_line(line) && is_vector(pt,len(line[0])),
"Invalid line, invalid point or incompatible dimensions." )
bounded == LINE ? _dist2line(pt-line[0],unit(line[1]-line[0]))
: norm(pt-line_closest_point(line,pt,bounded));
// Function: segment_distance()
// Synopsis: Find smallest distance between two line semgnets.
// Topics: Geometry, Segments, Distance
// See Also: convex_collision(), convex_distance()
// Usage:
// dist = segment_distance(seg1, seg2, [eps]);
// Description:
// Returns the smallest distance of the points on two given line segments.
// Arguments:
// seg1 = The list of two points representing the first line segment to check the distance of.
// seg2 = The list of two points representing the second line segment to check the distance of.
// eps = tolerance for point comparisons
// Example:
// dist = segment_distance([[-14,3], [-15,9]], [[-10,0], [10,0]]); // Returns: 5
// dist2 = segment_distance([[-5,5], [5,-5]], [[-10,3], [10,-3]]); // Returns: 0
function segment_distance(seg1, seg2,eps=EPSILON) =
assert( is_matrix(concat(seg1,seg2),4), "Inputs should be two valid segments." )
convex_distance(seg1,seg2,eps);
// Function: line_normal()
// Synopsis: Return normal vector to given line.
// Topics: Geometry, Lines
// See Also: line_intersection(), line_from_points()
// Usage:
// vec = line_normal([P1,P2])
// vec = line_normal(p1,p2)
// Description:
// Returns the 2D normal vector to the given 2D line. This is otherwise known as the perpendicular vector counter-clockwise to the given ray.
// Arguments:
// p1 = First point on 2D line.
// p2 = Second point on 2D line.
// Example(2D):
// p1 = [10,10];
// p2 = [50,30];
// n = line_normal(p1,p2);
// stroke([p1,p2], endcap2="arrow2");
// color("green") stroke([p1,p1+10*n], endcap2="arrow2");
// color("blue") move_copies([p1,p2]) circle(d=2, $fn=12);
function line_normal(p1,p2) =
is_undef(p2)
? assert( len(p1)==2 && !is_undef(p1[1]) , "Invalid input." )
line_normal(p1[0],p1[1])
: assert( _valid_line([p1,p2],dim=2), "Invalid line." )
unit([p1.y-p2.y,p2.x-p1.x]);
// 2D Line intersection from two segments.
// This function returns [p,t,u] where p is the intersection point of
// the lines defined by the two segments, t is the proportional distance
// of the intersection point along s1, and u is the proportional distance
// of the intersection point along s2. The proportional values run over
// the range of 0 to 1 for each segment, so if it is in this range, then
// the intersection lies on the segment. Otherwise it lies somewhere on
// the extension of the segment. If lines are parallel or coincident then
// it returns undef.
function _general_line_intersection(s1,s2,eps=EPSILON) =
let(
denominator = cross(s1[0]-s1[1],s2[0]-s2[1])
)
approx(denominator,0,eps=eps) ? undef :
let(
t = cross(s1[0]-s2[0],s2[0]-s2[1]) / denominator,
u = cross(s1[0]-s2[0],s1[0]-s1[1]) / denominator
)
[s1[0]+t*(s1[1]-s1[0]), t, u];
// Function: line_intersection()
// Synopsis: Compute intersection of two lines, segments or rays.
// Topics: Geometry, Lines
// See Also: line_normal(), line_from_points()
// Usage:
// pt = line_intersection(line1, line2, [bounded1], [bounded2], [bounded=], [eps=]);
// Description:
// Returns the intersection point of any two 2D lines, segments or rays. Returns undef
// if they do not intersect. You specify a line by giving two distinct points on the
// line. You specify rays or segments by giving a pair of points and indicating
// bounded[0]=true to bound the line at the first point, creating rays based at l1[0] and l2[0],
// or bounded[1]=true to bound the line at the second point, creating the reverse rays bounded
// at l1[1] and l2[1]. If bounded=[true, true] then you have segments defined by their two
// endpoints. By using bounded1 and bounded2 you can mix segments, rays, and lines as needed.
// You can set the bounds parameters to true as a shorthand for [true,true] to sepcify segments.
// Arguments:
// line1 = List of two points in 2D defining the first line, segment or ray
// line2 = List of two points in 2D defining the second line, segment or ray
// bounded1 = boolean or list of two booleans defining which ends are bounded for line1. Default: [false,false]
// bounded2 = boolean or list of two booleans defining which ends are bounded for line2. Default: [false,false]
// ---
// bounded = boolean or list of two booleans defining which ends are bounded for both lines. The bounded1 and bounded2 parameters override this if both are given.
// eps = tolerance for geometric comparisons. Default: `EPSILON` (1e-9)
// Example(2D): The segments do not intersect but the lines do in this example.
// line1 = 10*[[9, 4], [5, 7]];
// line2 = 10*[[2, 3], [6, 5]];
// stroke(line1, endcaps="arrow2");
// stroke(line2, endcaps="arrow2");
// isect = line_intersection(line1, line2);
// color("red") translate(isect) circle(r=1,$fn=12);
// Example(2D): Specifying a ray and segment using the shorthand variables.
// line1 = 10*[[0, 2], [4, 7]];
// line2 = 10*[[10, 4], [3, 4]];
// stroke(line1);
// stroke(line2, endcap2="arrow2");
// isect = line_intersection(line1, line2, SEGMENT, RAY);
// color("red") translate(isect) circle(r=1,$fn=12);
// Example(2D): Here we use the same example as above, but specify two segments using the bounded argument.
// line1 = 10*[[0, 2], [4, 7]];
// line2 = 10*[[10, 4], [3, 4]];
// stroke(line1);
// stroke(line2);
// isect = line_intersection(line1, line2, bounded=true); // Returns undef
function line_intersection(line1, line2, bounded1, bounded2, bounded, eps=EPSILON) =
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
assert( _valid_line(line1,dim=2,eps=eps), "First line invalid")
assert( _valid_line(line2,dim=2,eps=eps), "Second line invalid")
assert( is_undef(bounded) || is_bool(bounded) || is_bool_list(bounded,2), "Invalid value for \"bounded\"")
assert( is_undef(bounded1) || is_bool(bounded1) || is_bool_list(bounded1,2), "Invalid value for \"bounded1\"")
assert( is_undef(bounded2) || is_bool(bounded2) || is_bool_list(bounded2,2), "Invalid value for \"bounded2\"")
let(isect = _general_line_intersection(line1,line2,eps=eps))
is_undef(isect) ? undef :
let(
bounded1 = force_list(first_defined([bounded1,bounded,false]),2),
bounded2 = force_list(first_defined([bounded2,bounded,false]),2),
good = (!bounded1[0] || isect[1]>=0-eps)
&& (!bounded1[1] || isect[1]<=1+eps)
&& (!bounded2[0] || isect[2]>=0-eps)
&& (!bounded2[1] || isect[2]<=1+eps)
)
good ? isect[0] : undef;
// Function: line_closest_point()
// Synopsis: Find point on given line, segment or ray that is closest to a given point.
// Topics: Geometry, Lines, Distance
// See Also: line_normal(), point_line_distance()
// Usage:
// pt = line_closest_point(line, pt, [bounded]);
// Description:
// Returns the point on the given line, segment or ray that is closest to the given point `pt`.
// The inputs `line` and `pt` args should be of the same dimension. The parameter bounded indicates
// whether the points of `line` should be treated as endpoints.
// Arguments:
// line = A list of two points that are on the unbounded line.
// pt = The point to find the closest point on the line to.
// bounded = boolean or list of two booleans indicating that the line is bounded at that end. Default: [false,false]
// Example(2D):
// line = [[-30,0],[30,30]];
// pt = [-32,-10];
// p2 = line_closest_point(line,pt);
// stroke(line, endcaps="arrow2");
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(2D): If the line is bounded on the left you get the endpoint instead
// line = [[-30,0],[30,30]];
// pt = [-32,-10];
// p2 = line_closest_point(line,pt,bounded=[true,false]);
// stroke(line, endcap2="arrow2");
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(2D): In this case it doesn't matter how bounded is set. Using SEGMENT is the most restrictive option.
// line = [[-30,0],[30,30]];
// pt = [-5,0];
// p2 = line_closest_point(line,pt,SEGMENT);
// stroke(line);
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(2D): The result here is the same for a line or a ray.
// line = [[-30,0],[30,30]];
// pt = [40,25];
// p2 = line_closest_point(line,pt,RAY);
// stroke(line, endcap2="arrow2");
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(2D): But with a segment we get a different result
// line = [[-30,0],[30,30]];
// pt = [40,25];
// p2 = line_closest_point(line,pt,SEGMENT);
// stroke(line);
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(2D): The shorthand RAY uses the first point as the base of the ray. But you can specify a reversed ray directly, and in this case the result is the same as the result above for the segment.
// line = [[-30,0],[30,30]];
// pt = [40,25];
// p2 = line_closest_point(line,pt,[false,true]);
// stroke(line,endcap1="arrow2");
// color("blue") translate(pt) circle(r=1,$fn=12);
// color("red") translate(p2) circle(r=1,$fn=12);
// Example(FlatSpin,VPD=200,VPT=[0,0,15]): A 3D example
// line = [[-30,-15,0],[30,15,30]];
// pt = [5,5,5];
// p2 = line_closest_point(line,pt);
// stroke(line, endcaps="arrow2");
// color("blue") translate(pt) sphere(r=1,$fn=12);
// color("red") translate(p2) sphere(r=1,$fn=12);
function line_closest_point(line, pt, bounded=false) =
assert(_valid_line(line), "Invalid line")
assert(is_vector(pt, len(line[0])), "Invalid point or incompatible dimensions.")
assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid value for \"bounded\"")
let(
bounded = force_list(bounded,2)
)
bounded==[false,false] ?
let( n = unit( line[0]- line[1]) )
line[1] + ((pt- line[1]) * n) * n
: bounded == [true,true] ?
pt + _closest_s1([line[0]-pt, line[1]-pt])[0]
:
let(
ray = bounded==[true,false] ? line : reverse(line),
seglen = norm(ray[1]-ray[0]),
segvec = (ray[1]-ray[0])/seglen,
projection = (pt-ray[0]) * segvec
)
projection<=0 ? ray[0] :
ray[0] + projection*segvec;
// Function: line_from_points()
// Synopsis: Given a list of collinear points, return the line they define.
// Topics: Geometry, Lines, Points
// Usage:
// line = line_from_points(points, [fast], [eps]);
// Description:
// Given a list of 2 or more collinear points, returns two points defining a line containing them.
// If `fast` is false and the points are coincident or non-collinear, then `undef` is returned.
// if `fast` is true, then the collinearity test is skipped and a line passing through 2 distinct arbitrary points is returned.
// Arguments:
// points = The list of points to find the line through.
// fast = If true, don't verify that all points are collinear. Default: false
// eps = How much variance is allowed in testing each point against the line. Default: `EPSILON` (1e-9)
function line_from_points(points, fast=false, eps=EPSILON) =
assert( is_path(points), "Invalid point list." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
let( pb = furthest_point(points[0],points) )
norm(points[pb]-points[0])<eps*max(norm(points[pb]),norm(points[0])) ? undef :
fast || is_collinear(points)
? [points[pb], points[0]]
: undef;
// Section: Planes
// Function: is_coplanar()
// Synopsis: Check if 3d points are coplanar and not collinear.
// Topics: Geometry, Coplanarity
// See Also: plane3pt(), plane3pt_indexed(), plane_from_normal(), plane_from_points(), plane_from_polygon()
// Usage:
// bool = is_coplanar(points,[eps]);
// Description:
// Returns true if the given 3D points are non-collinear and are on a plane.
// Arguments:
// points = The points to test.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function is_coplanar(points, eps=EPSILON) =
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
len(points)<=2 ? false
: let( ip = _noncollinear_triple(points,error=false,eps=eps) )
ip == [] ? false :
let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]) )
_pointlist_greatest_distance(points,plane) < eps;
// Function: plane3pt()
// Synopsis: Return a plane from 3 points.
// Topics: Geometry, Planes
// See Also: plane3pt(), plane3pt_indexed(), plane_from_normal(), plane_from_points(), plane_from_polygon()
// Usage:
// plane = plane3pt(p1, p2, p3);
// plane = plane3pt([p1, p2, p3]);
// Description:
// Generates the normalized cartesian equation of a plane from three 3d points.
// Returns [A,B,C,D] where Ax + By + Cz = D is the equation of a plane.
// Returns undef, if the points are collinear.
// Arguments:
// p1 = The first point on the plane.
// p2 = The second point on the plane.
// p3 = The third point on the plane.
function plane3pt(p1, p2, p3) =
is_undef(p2) && is_undef(p3) && is_path(p1,dim=3) ? plane3pt(p1[0],p1[1],p1[2])
: assert( is_path([p1,p2,p3],dim=3) && len(p1)==3,
"Invalid points or incompatible dimensions." )
let(
crx = cross(p3-p1, p2-p1),
nrm = norm(crx)
) approx(nrm,0) ? undef :
concat(crx, crx*p1)/nrm;
// Function: plane3pt_indexed()
// Synopsis: Given list of 3d points and 3 indices, return the plane they define.
// Topics: Geometry, Planes
// See Also: plane3pt(), plane3pt_indexed(), plane_from_normal(), plane_from_points(), plane_from_polygon()
// Usage:
// plane = plane3pt_indexed(points, i1, i2, i3);
// Description:
// Given a list of 3d points, and the indices of three of those points,
// generates the normalized cartesian equation of a plane that those points all
// lie on. If the points are not collinear, returns [A,B,C,D] where Ax+By+Cz=D is the equation of a plane.
// If they are collinear, returns [].
// Arguments:
// points = A list of points.
// i1 = The index into `points` of the first point on the plane.
// i2 = The index into `points` of the second point on the plane.
// i3 = The index into `points` of the third point on the plane.
function plane3pt_indexed(points, i1, i2, i3) =
is_undef(i3) && is_undef(i2) && is_vector(i1) ? plane3pt_indexed(points, i1[0], i1[1], i1[2])
:
assert( is_vector([i1,i2,i3]) && min(i1,i2,i3)>=0 && is_list(points) && max(i1,i2,i3)<len(points),
"Invalid or out of range indices." )
assert( is_path([points[i1], points[i2], points[i3]],dim=3),
"Improper points or improper dimensions." )
let(
p1 = points[i1],
p2 = points[i2],
p3 = points[i3]
) plane3pt(p1,p2,p3);
// Function: plane_from_normal()
// Synopsis: Return plane defined by normal vector and a point.
// Topics: Geometry, Planes
// See Also: plane3pt(), plane3pt_indexed(), plane_from_normal(), plane_from_points(), plane_from_polygon()
// Usage:
// plane = plane_from_normal(normal, [pt])
// Description:
// Returns a plane defined by a normal vector and a point. If you omit `pt` you will get a plane
// passing through the origin.
// Arguments:
// normal = Normal vector to the plane to find.
// pt = Point 3D on the plane to find.
// Example:
// plane_from_normal([0,0,1], [2,2,2]); // Returns the xy plane passing through the point (2,2,2)
function plane_from_normal(normal, pt=[0,0,0]) =
assert( is_matrix([normal,pt],2,3) && !approx(norm(normal),0),
"Inputs `normal` and `pt` should be 3d vectors/points and `normal` cannot be zero." )
concat(normal, normal*pt) / norm(normal);
// Eigenvalues for a 3x3 symmetrical matrix in decreasing order
// Based on: https://en.wikipedia.org/wiki/Eigenvalue_algorithm
function _eigenvals_symm_3(M) =
let( p1 = pow(M[0][1],2) + pow(M[0][2],2) + pow(M[1][2],2) )
(p1<EPSILON)
? -sort(-[ M[0][0], M[1][1], M[2][2] ]) // diagonal matrix: eigenvals in decreasing order
: let( q = (M[0][0]+M[1][1]+M[2][2])/3,
B = (M - q*ident(3)),
dB = [B[0][0], B[1][1], B[2][2]],
p2 = dB*dB + 2*p1,
p = sqrt(p2/6),
r = det3(B/p)/2,
ph = acos(constrain(r,-1,1))/3,
e1 = q + 2*p*cos(ph),
e3 = q + 2*p*cos(ph+120),
e2 = 3*q - e1 - e3 )
[ e1, e2, e3 ];
// the i-th normalized eigenvector of a 3x3 symmetrical matrix M from its eigenvalues
// using Cayley–Hamilton theorem according to:
// https://en.wikipedia.org/wiki/Eigenvalue_algorithm
function _eigenvec_symm_3(M,evals,i=0) =
let(
I = ident(3),
A = (M - evals[(i+1)%3]*I) * (M - evals[(i+2)%3]*I) ,
k = max_index( [for(i=[0:2]) norm(A[i]) ])
)
norm(A[k])<EPSILON ? I[k] : A[k]/norm(A[k]);
// finds the eigenvector corresponding to the smallest eigenvalue of the covariance matrix of a pointlist
// returns the mean of the points, the eigenvector and the greatest eigenvalue
function _covariance_evec_eval(points) =
let( pm = sum(points)/len(points), // mean point
Y = [ for(i=[0:len(points)-1]) points[i] - pm ],
M = transpose(Y)*Y , // covariance matrix
evals = _eigenvals_symm_3(M), // eigenvalues in decreasing order
evec = _eigenvec_symm_3(M,evals,i=2) )
[pm, evec, evals[0] ];
// Function: plane_from_points()
// Synopsis: Return plane defined by a set of coplanar 3d points, with arbitrary normal direction.
// Topics: Geometry, Planes, Points
// See Also: plane3pt(), plane3pt_indexed(), plane_from_normal(), plane_from_points(), plane_from_polygon()
// Usage:
// plane = plane_from_points(points, [fast], [eps]);
// Description:
// Given a list of 3 or more coplanar 3D points, returns the coefficients of the normalized cartesian equation of a plane,
// that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane and norm([A,B,C])=1.
// If `fast` is false and the points in the list are collinear or not coplanar, then `undef` is returned.
// If `fast` is true, the polygon coplanarity check is skipped and a best fitting plane is returned.
// The direction of the plane's normal is arbitrary and is not determined by the point order, unlike {{plane_from_polygon()}}.
// This function is faster than {{plane_from_polygon()}}.
// Arguments:
// points = The list of points to find the plane of.
// fast = If true, don't verify the point coplanarity. Default: false
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// Example(3D):
// points = rot(45, v=[-0.3,1,0], p=path3d(random_points(25,2,scale=55,seed=47), 70));
// plane = plane_from_points(points);
// #move_copies(points)sphere(d=3);
// cp = mean(points);
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(50);
function plane_from_points(points, fast=false, eps=EPSILON) =
assert( is_path(points,dim=3), "Improper 3d point list." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
len(points) == 3
? plane3pt(points[0],points[1],points[2])
: let(
covmix = _covariance_evec_eval(points),
pm = covmix[0],
evec = covmix[1],
eval0 = covmix[2],
plane = [ each evec, pm*evec]
)
!fast && _pointlist_greatest_distance(points,plane)>eps*eval0 ? undef :
plane ;
// Function: plane_from_polygon()
// Synopsis: Given a 3d planar polygon, returns directed plane.
// Topics: Geometry, Planes, Polygons
// See Also: plane3pt(), plane3pt_indexed(), plane_from_normal(), plane_from_points(), plane_from_polygon()
// Usage:
// plane = plane_from_polygon(points, [fast], [eps]);
// Description:
// Given a 3D planar polygon, returns the normalized cartesian equation of its plane.
// Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane where norm([A,B,C])=1.
// If not all the points in the polygon are coplanar, then [] is returned.
// If `fast` is false and the points in the list are collinear or not coplanar, then `undef` is returned.
// if `fast` is true, then the coplanarity test is skipped and a plane passing through 3 non-collinear arbitrary points is returned.
// The normal direction is determined by the order of the points and the right hand rule. This is slower than {{plane_from_points()}},
// which returns an arbitrary normal.
// Arguments:
// poly = The planar 3D polygon to find the plane of.
// fast = If true, doesn't verify that all points in the polygon are coplanar. Default: false
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// Example(3D):
// xyzpath = rot(45, v=[0,1,0], p=path3d(star(n=5,step=2,d=100), 70));
// plane = plane_from_polygon(xyzpath);
// #stroke(xyzpath,closed=true,width=3);
// cp = centroid(xyzpath);
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(45);
function plane_from_polygon(poly, fast=false, eps=EPSILON) =
assert( is_path(poly,dim=3), "Invalid polygon." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
let(
poly_normal = polygon_normal(poly)
)
is_undef(poly_normal) ? undef :
let(
plane = plane_from_normal(poly_normal, poly[0])
)
fast? plane: are_points_on_plane(poly, plane, eps=eps)? plane: undef;
// Function: plane_normal()
// Synopsis: Returns the normal vector to a plane.
// Topics: Geometry, Planes
// See Also: plane3pt(), plane3pt_indexed(), plane_from_normal(), plane_from_points(), plane_from_polygon(), plane_normal(), plane_offset()
// Usage:
// vec = plane_normal(plane);
// Description:
// Returns the unit length normal vector for the given plane.
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
function plane_normal(plane) =
assert( _valid_plane(plane), "Invalid input plane." )
unit([plane.x, plane.y, plane.z]);
// Function: plane_offset()
// Synopsis: Returns the signed offset of the plane from the origin.
// Topics: Geometry, Planes
// See Also: plane3pt(), plane3pt_indexed(), plane_from_normal(), plane_from_points(), plane_from_polygon(), plane_normal(), plane_offset()
// Usage:
// d = plane_offset(plane);
// Description:
// Returns coeficient D of the normalized plane equation `Ax+By+Cz=D`, or the scalar offset of the plane from the origin.
// This value may be negative.
// The absolute value of this coefficient is the distance of the plane from the origin.
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
function plane_offset(plane) =
assert( _valid_plane(plane), "Invalid input plane." )
plane[3]/norm([plane.x, plane.y, plane.z]);
// Returns [POINT, U] if line intersects plane at one point, where U is zero at line[0] and 1 at line[1]
// Returns [LINE, undef] if the line is on the plane.
// Returns undef if line is parallel to, but not on the given plane.
function _general_plane_line_intersection(plane, line, eps=EPSILON) =
let(
a = plane*[each line[0],-1], // evaluation of the plane expression at line[0]
b = plane*[each(line[1]-line[0]),0] // difference between the plane expression evaluation at line[1] and at line[0]
)
approx(b,0,eps) // is (line[1]-line[0]) "parallel" to the plane ?
? approx(a,0,eps) // is line[0] on the plane ?
? [line,undef] // line is on the plane
: undef // line is parallel but not on the plane
: [ line[0]-a/b*(line[1]-line[0]), -a/b ];
/// Internal Function: normalize_plane()
/// Usage:
/// nplane = normalize_plane(plane);
/// Topics: Geometry, Planes
/// Description:
/// Returns a new representation [A,B,C,D] of `plane` where norm([A,B,C]) is equal to one.
function _normalize_plane(plane) =
assert( _valid_plane(plane), str("Invalid plane. ",plane ) )
plane/norm(point3d(plane));
// Function: plane_line_intersection()
// Synopsis: Returns the intersection of a plane and 3d line, segment or ray.
// Topics: Geometry, Planes, Lines, Intersection
// See Also: plane3pt(), plane_from_normal(), plane_from_points(), plane_from_polygon(), line_intersection()
// Usage:
// pt = plane_line_intersection(plane, line, [bounded], [eps]);
// Description:
// Takes a line, and a plane [A,B,C,D] where the equation of that plane is `Ax+By+Cz=D`.
// If `line` intersects `plane` at one point, then that intersection point is returned.
// If `line` lies on `plane`, then the original given `line` is returned.
// If `line` is parallel to, but not on `plane`, then undef is returned.
// Arguments:
// plane = The [A,B,C,D] values for the equation of the plane.
// line = A list of two distinct 3D points that are on the line.
// bounded = If false, the line is considered unbounded. If true, it is treated as a bounded line segment. If given as `[true, false]` or `[false, true]`, the boundedness of the points are specified individually, allowing the line to be treated as a half-bounded ray. Default: false (unbounded)
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) =
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
assert(_valid_plane(plane,eps=eps) && _valid_line(line,dim=3,eps=eps), "Invalid plane and/or 3d line.")
assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition.")
let(
bounded = is_list(bounded)? bounded : [bounded, bounded],
res = _general_plane_line_intersection(plane, line, eps=eps)
) is_undef(res) ? undef :
is_undef(res[1]) ? res[0] :
bounded[0] && res[1]<0 ? undef :
bounded[1] && res[1]>1 ? undef :
res[0];
// Function: plane_intersection()
// Synopsis: Returns the intersection of two or three planes.
// Topics: Geometry, Planes, Intersection
// See Also: plane3pt(), plane_from_normal(), plane_from_points(), plane_from_polygon(), line_intersection()
// Usage:
// line = plane_intersection(plane1, plane2)
// pt = plane_intersection(plane1, plane2, plane3)
// Description:
// Compute the point which is the intersection of the three planes, or the line intersection of two planes.
// If you give three planes the intersection is returned as a point. If you give two planes the intersection
// is returned as a list of two points on the line of intersection. If any two input planes are parallel
// or coincident then returns undef.
// Arguments:
// plane1 = The [A,B,C,D] coefficients for the first plane equation `Ax+By+Cz=D`.
// plane2 = The [A,B,C,D] coefficients for the second plane equation `Ax+By+Cz=D`.
// plane3 = The [A,B,C,D] coefficients for the third plane equation `Ax+By+Cz=D`.
function plane_intersection(plane1,plane2,plane3) =
assert( _valid_plane(plane1) && _valid_plane(plane2) && (is_undef(plane3) ||_valid_plane(plane3)),
"The input must be 2 or 3 planes." )
is_def(plane3)
? let(
matrix = [for(p=[plane1,plane2,plane3]) point3d(p)],
rhs = [for(p=[plane1,plane2,plane3]) p[3]]
)
linear_solve(matrix,rhs)
: let( normal = cross(plane_normal(plane1), plane_normal(plane2)) )
approx(norm(normal),0) ? undef :
let(
matrix = [for(p=[plane1,plane2]) point3d(p)],
rhs = [plane1[3], plane2[3]],
point = linear_solve(matrix,rhs)
)
point==[]? undef:
[point, point+normal];
// Function: plane_line_angle()
// Synopsis: Returns the angle between a plane and a 3d line.
// Topics: Geometry, Planes, Lines, Angle
// See Also: plane3pt(), plane_from_normal(), plane_from_points(), plane_from_polygon(), plane_intersection(), line_intersection(), vector_angle()
// Usage:
// angle = plane_line_angle(plane,line);
// Description:
// Compute the angle between a plane [A, B, C, D] and a 3d line, specified as a pair of 3d points [p1,p2].
// The resulting angle is signed, with the sign positive if the vector p2-p1 lies above the plane, on
// the same side of the plane as the plane's normal vector.
function plane_line_angle(plane, line) =
assert( _valid_plane(plane), "Invalid plane." )
assert( _valid_line(line,dim=3), "Invalid 3d line." )
let(
linedir = unit(line[1]-line[0]),
normal = plane_normal(plane),
sin_angle = linedir*normal,
cos_angle = norm(cross(linedir,normal))
) atan2(sin_angle,cos_angle);
// Function: plane_closest_point()
// Synopsis: Returns the orthogonal projection of points onto a plane.
// Topics: Geometry, Planes, Projection
// See Also: plane3pt(), line_closest_point(), point_plane_distance()
// Usage:
// pts = plane_closest_point(plane, points);
// Description:
// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or
// 3d points, return the closest 3D orthogonal projection of the points on the plane.
// In other words, for every point given, returns the closest point to it on the plane.
// If points is a single point then returns a single point result.
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
// points = List of points to project
// Example(FlatSpin,VPD=500,VPT=[2,20,10]):
// points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36))));
// plane = plane_from_normal([1,0,1]);
// proj = plane_closest_point(plane,points);
// color("red") move_copies(points) sphere(d=4,$fn=12);
// color("blue") move_copies(proj) sphere(d=4,$fn=12);
// move(centroid(proj)) {
// rot(from=UP,to=plane_normal(plane)) {
// anchor_arrow(50);
// %cube([120,150,0.1],center=true);
// }
// }
function plane_closest_point(plane, points) =
is_vector(points,3) ? plane_closest_point(plane,[points])[0] :
assert( _valid_plane(plane), "Invalid plane." )
assert( is_matrix(points,undef,3), "Must supply 3D points.")
let(
plane = _normalize_plane(plane),
n = point3d(plane)
)
[for(pi=points) pi - (pi*n - plane[3])*n];
// Function: point_plane_distance()
// Synopsis: Determine distance between a point and plane.
// Topics: Geometry, Planes, Distance
// See Also: plane3pt(), line_closest_point(), plane_closest_point()
// Usage:
// dist = point_plane_distance(plane, point)
// Description:
// Given a plane as [A,B,C,D] where the cartesian equation for that plane
// is Ax+By+Cz=D, determines how far from that plane the given point is.
// The returned distance will be positive if the point is above the
// plane, meaning on the side where the plane normal points.
// If the point is below the plane, then the distance returned
// will be negative. The normal of the plane is [A,B,C].
// Arguments:
// plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane.
// point = The distance evaluation point.
function point_plane_distance(plane, point) =
assert( _valid_plane(plane), "Invalid input plane." )
assert( is_vector(point,3), "The point should be a 3D point." )
let( plane = _normalize_plane(plane) )
point3d(plane)* point - plane[3];
// the maximum distance from points to the plane
function _pointlist_greatest_distance(points,plane) =
let(
normal = [plane[0],plane[1],plane[2]],
pt_nrm = points*normal
)
max( max(pt_nrm) - plane[3], -min(pt_nrm) + plane[3]) / norm(normal);
// Function: are_points_on_plane()
// Synopsis: Determine if all of the listed points are on a plane.
// Topics: Geometry, Planes, Points
// See Also: plane3pt(), line_closest_point(), plane_closest_point(), is_coplanar()
// Usage:
// bool = are_points_on_plane(points, plane, [eps]);
// Description:
// Returns true if the given 3D points are on the given plane.
// Arguments:
// plane = The plane to test the points on.
// points = The list of 3D points to test.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function are_points_on_plane(points, plane, eps=EPSILON) =
assert( _valid_plane(plane), "Invalid plane." )
assert( is_matrix(points,undef,3) && len(points)>0, "Invalid pointlist." ) // using is_matrix it accepts len(points)==1
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
_pointlist_greatest_distance(points,plane) < eps;
/// Internal Function: is_point_above_plane()
/// Usage:
/// bool = _is_point_above_plane(plane, point);
/// Topics: Geometry, Planes
/// Description:
/// Given a plane as [A,B,C,D] where the cartesian equation for that plane
/// is Ax+By+Cz=D, determines if the given 3D point is on the side of that
/// plane that the normal points towards. The normal of the plane is the
/// same as [A,B,C].
/// Arguments:
/// plane = The [A,B,C,D] coefficients for the first plane equation `Ax+By+Cz=D`.
/// point = The 3D point to test.
function _is_point_above_plane(plane, point) =
point_plane_distance(plane, point) > EPSILON;
// Section: Circle Calculations
// Function: circle_line_intersection()
// Synopsis: Find the intersection points between a 2d circle and a line, ray or segment.
// Topics: Geometry, Circles, Lines, Intersection
// See Also: circle_line_intersection(), circle_circle_intersection(), circle_2tangents(), circle_3points(), circle_point_tangents(), circle_circle_tangents()
// Usage:
// pts = circle_line_intersection(r|d=, cp, line, [bounded], [eps=]);
// Description:
// Find intersection points between a 2D circle and a line, ray or segment specified by two points.
// By default the line is unbounded. Returns the list of zero or more intersection points.
// Arguments:
// r = Radius of circle
// cp = Center of circle
// line = Two points defining the line
// bounded = False for unbounded line, true for a segment, or a vector [false,true] or [true,false] to specify a ray with the first or second end unbounded. Default: false
// ---
// d = Diameter of circle
// eps = Epsilon used for identifying the case with one solution. Default: `1e-9`
// Example(2D): Standard intersection returns two points.
// line = [[-15,2], [15,7]];
// cp = [1,2]; r = 10;
// translate(cp) circle(r=r);
// color("black") stroke(line, endcaps="arrow2", width=0.5);
// isects = circle_line_intersection(r=r, cp=cp, line=line);
// color("red") move_copies(isects) circle(d=1);
// Example(2D): Tangent intersection returns one point.
// line = [[-10,12], [10,12]];
// cp = [1,2]; r = 10;
// translate(cp) circle(r=r);
// color("black") stroke(line, endcaps="arrow2", width=0.5);
// isects = circle_line_intersection(r=r, cp=cp, line=line);
// color("#f44") move_copies(isects) circle(d=1);
// Example(2D): A bounded ray might only intersect in one direction.
// line = [[-5,2], [5,7]];
// extended = [line[0], line[0]+22*unit(line[1]-line[0])];
// cp = [1,2]; r = 10;
// translate(cp) circle(r=r);
// color("gray") dashed_stroke(extended, width=0.2);
// color("black") stroke(line, endcap2="arrow2", width=0.5);
// isects = circle_line_intersection(r=r, cp=cp, line=line, bounded=[true,false]);
// color("#f44") move_copies(isects) circle(d=1);
// Example(2D): If they don't intersect at all, then an empty list is returned.
// line = [[-12,12], [12,8]];
// cp = [-5,-2]; r = 10;
// translate(cp) circle(r=r);
// color("black") stroke(line, endcaps="arrow2", width=0.5);
// isects = circle_line_intersection(r=r, cp=cp, line=line);
// color("#f44") move_copies(isects) circle(d=1);
function circle_line_intersection(r, cp, line, bounded=false, d, eps=EPSILON) =
assert(_valid_line(line,2), "Invalid 2d line.")
assert(is_vector(cp,2), "Circle center must be a 2-vector")
_circle_or_sphere_line_intersection(r, cp, line, bounded, d, eps);
function _circle_or_sphere_line_intersection(r, cp, line, bounded=false, d, eps=EPSILON) =
let(r=get_radius(r=r,d=d,dflt=undef))
assert(is_num(r) && r>0, "Radius must be positive")
assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition")
let(
bounded = force_list(bounded,2),
closest = line_closest_point(line,cp),
d = norm(closest-cp)
)
d > r ? [] :
let(
isect = approx(d,r,eps) ? [closest] :
let( offset = sqrt(r*r-d*d),
uvec=unit(line[1]-line[0])
) [closest-offset*uvec, closest+offset*uvec]
)
[for(p=isect)
if ((!bounded[0] || (p-line[0])*(line[1]-line[0])>=0)
&& (!bounded[1] || (p-line[1])*(line[0]-line[1])>=0)) p];
// Function: circle_circle_intersection()
// Synopsis: Find the intersection points of two 2d circles.
// Topics: Geometry, Circles
// See Also: circle_line_intersection(), circle_circle_intersection(), circle_2tangents(), circle_3points(), circle_point_tangents(), circle_circle_tangents()
// Usage:
// pts = circle_circle_intersection(r1|d1=, cp1, r2|d2=, cp2, [eps]);
// Description:
// Compute the intersection points of two circles. Returns a list of the intersection points, which
// will contain two points in the general case, one point for tangent circles, or will be empty
// if the circles do not intersect.
// Arguments:
// r1 = Radius of the first circle.
// cp1 = Centerpoint of the first circle.
// r2 = Radius of the second circle.
// cp2 = Centerpoint of the second circle.
// eps = Tolerance for detecting tangent circles. Default: EPSILON