-
Notifications
You must be signed in to change notification settings - Fork 5
/
jump_and_march.jl
1251 lines (1137 loc) · 73.1 KB
/
jump_and_march.jl
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
"""
default_num_samples(n) -> Integer
Given a number of points `n`, returns `∛n` rounded up to the nearest integer. This is the default number of samples used in the jump-and-march algorithm.
"""
default_num_samples(num_points::I) where {I} = ceil(I, cbrt(num_points))
"""
compare_distance(current_dist, current_idx, pts, i, qx, qy) -> (Number, Vertex)
Computes the minimum of the distance between the `i`th point of `pts` and `(qx, qy)` and `current_dist`.
# Arguments
- `current_dist`: The current value for the distance to the point `(qx, qy)`.
- `current_idx`: The point of `pts` corresponding to the distance `current_dist`.
- `pts`: The point set.
- `i`: The vertex to compare with `current_idx`.
- `qx`: The x-coordinate of the query point.
- `qy`: The y-coordinate of the query point.
# Outputs
- `current_dist`: The minimum of the distance between the `i`th point of `pts` and `(qx, qy)` and `current_dist`.
- `current_idx`: The point of `pts` corresponding to the distance `current_dist`, which will be either `i` or `current_idx`.
"""
function compare_distance(current_dist, current_idx::I, pts, i, qx, qy) where {I}
p = get_point(pts, i)
px, py = _getxy(p)
_dist = dist_sqr((px, py), (qx, qy))
if _dist < current_dist
current_dist = _dist
current_idx = i
end
return current_dist, I(current_idx)
end
@doc """
select_initial_point(tri::Triangulation, q; kwargs...) -> Vertex
Selects the initial point for [`jump_and_march`](@ref) to start from.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `q`: The query point. Can be either a point or a vertex - if it is a vertex, the corresponding point `get_point(tri, q)` will be used.
# Keyword Arguments
- `point_indices=each_solid_vertex(tri)`: The indices to sample from.
- `m=default_num_samples(num_vertices(point_indices))`: The number of samples to take. Replacement is not used, so there may be duplicates.
- `try_points=()`: A list of points to try in addition to those randomly sampled.
- `allow_boundary_points=!is_disjoint(tri)`: Whether to allow boundary points to be selected.
- `rng=Random.default_rng()`: The random number generator to use.
# Outputs
- `i`: The index of the point closest to `q` out of those queried.
"""
select_initial_point
function select_initial_point(tri::Triangulation, q;
point_indices=each_solid_vertex(tri),
m=default_num_samples(num_vertices(point_indices)),
try_points=(),
allow_boundary_points=!is_disjoint(tri),
rng::AbstractRNG=Random.default_rng())
F = number_type(tri)
I = integer_type(tri)
current_dist = typemax(F)
current_idx = I(first(point_indices))
qx, qy = _getxy(q)
for _ in 1:m # Not using replacement, but probability of duplicates is approximately 0.5num_solid_vertices(tri)^(-1/3)
i = I(rand(rng, point_indices))
(is_ghost_vertex(i) || (!allow_boundary_points && is_boundary_node(tri, i)[1])) && continue
current_dist, current_idx = compare_distance(current_dist, current_idx, tri, i, qx, qy)
end
for i in try_points
if i ≠ ∅
current_dist, current_idx = compare_distance(current_dist, current_idx, tri, i, qx, qy)
end
end
return I(current_idx)
end
function select_initial_point(tri::Triangulation, q::Integer;
point_indices=each_solid_vertex(tri),
m=default_num_samples(num_vertices(tri)),
allow_boundary_points=!is_disjoint(tri),
try_points=(),
rng::AbstractRNG=Random.default_rng())
return select_initial_point(tri, get_point(tri, q); point_indices, m, try_points, allow_boundary_points, rng)
end
"""
select_random_edge(tri::Triangulation, edges, rng::AbstractRNG=Random.default_rng()) -> (Vertex, Vertex, Point, Point)
Selects a random edge from the set of edges `edges`.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `edges`: The set of edges to sample from.
- `rng::AbstractRNG=Random.default_rng()`: The random number generator to use.
# Outputs
- `i`: The initial vertex of the edge.
- `j`: The terminal vertex of the edge.
- `pᵢ`: The point corresponding to `i`.
- `pⱼ`: The point corresponding to `j`.
"""
function select_random_edge(tri::Triangulation, edges, rng::AbstractRNG=Random.default_rng())
edge = random_edge(rng, edges)
i, j = edge_vertices(edge)
pᵢ, pⱼ = get_point(tri, i, j)
return i, j, pᵢ, pⱼ
end
"""
prepare_initial_edge(tri::Triangulation, edges, p, q, rng::AbstractRNG=Random.default_rng()) -> (Vertex, Vertex, Point, Point, Certificate, Certificate)
Selects a random edge from the set of edges `edges` and computes the certificates for the points corresponding to the initial and terminal vertices of the edge.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `edges`: The set of edges to sample from.
- `p`: The initial point.
- `q`: The query point.
- `rng::AbstractRNG=Random.default_rng()`: The random number generator to use.
# Outputs
- `i`: The initial vertex of the edge.
- `j`: The terminal vertex of the edge.
- `pᵢ`: The point corresponding to `i`.
- `pⱼ`: The point corresponding to `j`.
- `line_cert_i`: The [`Certificate`](@ref) for `pᵢ`'s position relative to the oriented line `pq`.
- `line_cert_j`: The [`Certificate`](@ref) for `pⱼ`'s position relative to the oriented line `pq`.
"""
function prepare_initial_edge(tri::Triangulation, edges, p, q, rng::AbstractRNG=Random.default_rng())
i, j, pᵢ, pⱼ = select_random_edge(tri, edges, rng)
line_cert_i = point_position_relative_to_line(p, q, pᵢ)
line_cert_j = point_position_relative_to_line(p, q, pⱼ)
return i, j, pᵢ, pⱼ, line_cert_i, line_cert_j
end
"""
select_initial_triangle_interior_vertex(tri::Triangulation, k, q, store_history=Val(false), history=nothing, rng::AbstractRNG=Random.default_rng()) -> (Point, Vertex, Vertex, Point, Point)
Selects the initial triangle for [`jump_and_march`](@ref) to start from, for the case where `k` is an interior vertex.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `k`: The vertex to start from.
- `q`: The query point.
- `store_history=Val(false)`: Whether to store the history of the algorithm.
- `history=nothing`: The history of the algorithm. If `store_history`, then this should be a [`PointLocationHistory`](@ref) object.
- `rng::AbstractRNG=Random.default_rng()`: The random number generator to use.
# Outputs
- `p`: The point corresponding to `k`.
- `i`: The initial vertex of the triangle.
- `j`: The terminal vertex of the triangle.
- `pᵢ`: The point corresponding to `i`.
- `pⱼ`: The point corresponding to `j`.
!!! warning "Non-convex geometries"
This function assumes that the geometry is convex. To deal with this, when an infinite loop is detected
we return `∅` for both `i` and `j`, and then let [`jump_and_march`](@ref) handle how to
correct the algorithm from there.
# Extended help
This part of the algorithm works by rotating around the vertex `k`, looking for a triangle whose edges adjoining `k`
are to the left and to the right of `k`. By choosing the initial edge at random via [`prepare_initial_edge`](@ref),
and computing the position of `q` relative to this initial edge, the rotation will be either clockwise or counter-clockwise,
and the triangle is then found using either `select_initial_triangle_clockwise` or `select_initial_triangle_counterclockwise`,
respectively.
In case the initial edge is collinear with the line `pq`, where `p = get_point(tri, q)`, then `fix_initial_collinear_edge_for_interior_vertex` to find a
non-collinear edge resample more edges from [`prepare_initial_edge`](@ref) if necessary.
"""
function select_initial_triangle_interior_vertex(tri::Triangulation, k, q, store_history::F=Val(false), history=nothing, rng::AbstractRNG=Random.default_rng()) where {F}
p = get_point(tri, k)
## Select the initial edge to rotate about
neighbouring_edges = get_adjacent2vertex(tri, k)
i, j, pᵢ, pⱼ, line_cert_i, line_cert_j = prepare_initial_edge(tri, neighbouring_edges, p, q, rng)
p == q && return p, j, i, pⱼ, pᵢ
## Take care for collinear edges
return_flag, p, i, j, pᵢ, pⱼ, line_cert_i, line_cert_j = fix_initial_collinear_edge_for_interior_vertex(tri, k, q, store_history, history, rng, p, neighbouring_edges, i, j, pᵢ, pⱼ, line_cert_i, line_cert_j)
return_flag && return p, i, j, pᵢ, pⱼ
## Now rotate around to find a triangle that the line pq intersects through
if is_left(line_cert_j)
i, j, pᵢ, pⱼ = select_initial_triangle_clockwise(tri, p, q, pᵢ, pⱼ, i, j, k, store_history, history)
else
i, j, pᵢ, pⱼ = select_initial_triangle_counterclockwise(tri, line_cert_j, p, q, pᵢ, pⱼ, i, j, k, store_history, history)
end
## Swap values and return
i, j = j, i
pᵢ, pⱼ = pⱼ, pᵢ # pᵢ is left of pq, pⱼ is right of pq
return p, i, j, pᵢ, pⱼ
end
function select_initial_triangle_clockwise(tri::Triangulation, p, q, pᵢ, pⱼ, i, j, k, store_history::F=Val(false), history=nothing) where {F}
line_cert_i = point_position_relative_to_line(p, q, pᵢ)
if is_true(store_history) && is_collinear(line_cert_i)
add_edge!(history, k, i)
end
nn = num_neighbours(tri, k)
iter = 0 # when we have Non-convex geometries, sometimes we get stuck in an infinite loop
while is_left(line_cert_i) && iter ≤ nn + 1
iter += 1
j = i
pⱼ = pᵢ
i = get_adjacent(tri, i, k)
pᵢ = get_point(tri, i)
line_cert_i = point_position_relative_to_line(p, q, pᵢ)
if is_true(store_history) && is_collinear(line_cert_i)
add_edge!(history, k, i)
end
end
I = integer_type(tri)
if iter > nn + 1
return I(∅), I(∅), pᵢ, pⱼ
else
return i, j, pᵢ, pⱼ
end
end
function select_initial_triangle_counterclockwise(tri::Triangulation, line_cert_j, p, q, pᵢ, pⱼ, i, j, k, store_history::F=Val(false), history=nothing) where {F}
if is_true(store_history) && is_collinear(line_cert_j)
add_edge!(history, k, j)
end
nn = num_neighbours(tri, k)
iter = 0 # when we have Non-convex geometries, sometimes we get stuck in an infinite loop
while is_right(line_cert_j) && iter ≤ nn + 1
iter += 1
i = j
pᵢ = pⱼ
j = get_adjacent(tri, k, j)
pⱼ = get_point(tri, j)
line_cert_j = point_position_relative_to_line(p, q, pⱼ)
if is_true(store_history) && is_collinear(line_cert_j)
add_edge!(history, k, j)
end
end
I = integer_type(tri)
if iter > nn + 1
return I(∅), I(∅), pᵢ, pⱼ
else
return i, j, pᵢ, pⱼ
end
end
function fix_initial_collinear_edge_for_interior_vertex(tri::Triangulation, k, q, store_history, history, rng, p, neighbouring_edges, i, j, pᵢ, pⱼ, line_cert_i, line_cert_j)
while is_collinear(line_cert_j) || is_collinear(line_cert_i)
if is_collinear(line_cert_j)
opposing_idx = j
on_edge_cert = point_position_on_line_segment(p, pⱼ, q)
else
opposing_idx = i
on_edge_cert = point_position_on_line_segment(p, pᵢ, q)
end
# We want q to be in the direction of of ppᵢ or ppⱼ, which just means not left
# For example, suppose we have
#=
p -------- pⱼ --------- q
\ /
\ /
\ /
\ /
\pᵢ
Then we want to know if q is to the right of `ppⱼ` (here, `line_cert_j` is the collinear certificate)
or to the left. If it is to the left, then it is on the edge `ppⱼ`. This is not a problem, but we let
[`jump_and_march`](@ref) handle this case somewhere else. Thus, we should want to find a case where `q`
is right of `ppⱼ` and not handle this interior edge case, so we check `!is_left(on_edge_cert)`.
=#
if !is_left(on_edge_cert)
if is_on(on_edge_cert) || is_degenerate(on_edge_cert)
is_true(store_history) && add_edge!(history, k, opposing_idx)
return true, p, j, i, pⱼ, pᵢ, line_cert_i, line_cert_j
else
pos_cert = point_position_relative_to_line(p, q, pᵢ)
if is_left(pos_cert)
is_true(store_history) && add_edge!(history, k, opposing_idx)
return true, p, i, j, pᵢ, pⱼ, line_cert_i, line_cert_j
else
is_true(store_history) && add_edge!(history, k, opposing_idx)
return true, p, j, i, pⱼ, pᵢ, line_cert_i, line_cert_j
end
end
else
# If we haven't yet found the edge, let's just pick another one
i, j, pᵢ, pⱼ, line_cert_i, line_cert_j = prepare_initial_edge(tri, neighbouring_edges, p, q, rng)
end
end
return false, p, i, j, pᵢ, pⱼ, line_cert_i, line_cert_j
end
"""
check_for_intersections_with_adjacent_boundary_edges(tri::Triangulation, k, q, ghost_vertex=𝒢) -> (Certificate, Certificate, Vertex, Certificate, Certificate)
Given a boundary vertex `k`, find a triangle adjacent to `k` to locate a triangle or edge containing `q`.
See also [`search_down_adjacent_boundary_edges`](@ref), which uses this function to determine an initial direction to search along a
straight boundary in case `q` is collinear with it.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `k`: The boundary vertex to start from.
- `q`: The query point.
- `ghost_vertex=𝒢`: The ghost vertex corresponding to the boundary that `k` resides on.
# Outputs
- `direction_cert`: The direction of `q` relative to the vertex `k` along the boundary, given as a [`Certificate`](@ref) `Left`, `Right`, or `Outside`. If `is_outside(direction_cert)`, then `q` is not collinear with either of the adjacent boundary edges.
- `q_pos_cert`: The position of `q` relative to the vertex `k` along the boundary, given as a [`Certificate`](@ref) `Left`, `Right`, `On`, `Outside`, or `Degenerate`. This is similar to `direction_cert` in that it will be `Outside` whenever `direction_cert` is, but this certificate can also be `On` to indicate that not only is `q` in the direction given by `direction_cert`, but it is directly on the edge in that direction. If `is_degnerate(q_pos_cert)`, then `q = get_point(tri, next_vertex)`.
- `next_vertex`: The next vertex along the boundary in the direction of `q`, or `k` if `q` is not collinear with either of the adjacent boundary edges.
- `right_cert`: The [`Certificate`](@ref) for the position of `q` relative to the boundary edge right of `k`.
- `left_cert`: The [`Certificate`](@ref) for the position of `q` relative to the boundary edge left of `k`.
"""
function check_for_intersections_with_adjacent_boundary_edges(tri::Triangulation{P,T,BN,W,I}, k, q, ghost_vertex=I(𝒢)) where {P,T,BN,W,I}
p = get_point(tri, k)
right = get_right_boundary_node(tri, k, ghost_vertex)
left = get_left_boundary_node(tri, k, ghost_vertex)
pright, pleft = get_point(tri, right, left)
right_cert = point_position_relative_to_line(p, pright, q)
left_cert = point_position_relative_to_line(pleft, p, q)
flipped_left_cert = point_position_relative_to_line(p, pleft, q) # Useful for the returned value, but we use left_cert below to get a good value for direction_cert
!is_collinear(right_cert) && !is_collinear(left_cert) && return (Cert.Outside, Cert.Outside, k, right_cert, flipped_left_cert)
if is_collinear(right_cert)
direction_cert = point_position_on_line_segment(p, pright, q)
if !is_left(direction_cert)
return (Cert.Right, direction_cert, right, right_cert, flipped_left_cert)
elseif is_left(direction_cert) && !is_collinear(left_cert)
return (Cert.Outside, Cert.Outside, k, right_cert, flipped_left_cert)
end
end
direction_cert = point_position_on_line_segment(pleft, p, q)
if !is_right(direction_cert)
return (Cert.Left, direction_cert, left, right_cert, flipped_left_cert)
else
return (Cert.Outside, Cert.Outside, k, right_cert, flipped_left_cert)
end
end
"""
search_down_adjacent_boundary_edges(tri::Triangulation, k, q, direction_cert, q_pos_cert, next_vertex, store_history=Val(false), history=nothing, ghost_vertex=𝒢) -> (Bool, Certificate, Vertex, Vertex, Vertex)
Starting at the boundary vertex `k`, walks down the boundary in the direction of `q` until finding `q` or finding that it is outside of the triangulation.
See also [`check_for_intersections_with_adjacent_boundary_edges`](@ref), which uses this function to determine an initial direction to search along.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `k`: The boundary vertex to start from.
- `q`: The query point.
- `direction_cert`: The direction of `q` relative to the vertex `k` along the boundary, defined from [`check_for_intersections_with_adjacent_boundary_edges`](@ref).
- `q_pos_cert`: The position of `q` relative to the vertex `k` along the boundary, defined from [`check_for_intersections_with_adjacent_boundary_edges`](@ref).
- `next_vertex`: The next vertex along the boundary in the direction of `q`, defined from [`check_for_intersections_with_adjacent_boundary_edges`](@ref).
- `store_history=Val(false)`: Whether to store the history of the algorithm.
- `history=nothing`: The history of the algorithm. If `store_history`, then this should be a [`PointLocationHistory`](@ref) object.
- `ghost_vertex=𝒢`: The ghost vertex corresponding to the boundary that `k` resides on.
# Outputs
- `return_flag`: Whether to return, or throw an exception.
- `q_pos_cert`: A [`Certificate`](@ref) that is `On` if `q` is on the edge `(u, v)`, and `Outside` if `q` is outside of the triangulation.
- `u`: If `is_on(q_pos_cert)`, this is the first vertex of a positively oriented triangle that `q` is on, so that `q` is on the edge `(u, v)`. Otherwise, `(u, v, w)` is a ghost triangle close to `q`.
- `v`: If `is_on(q_pos_cert)`, this is the second vertex of a positively oriented triangle that `q` is on, so that `q` is on the edge `(u, v)`. Otherwise, `(u, v, w)` is a ghost triangle close to `q`.
- `w`: If `is_on(q_pos_cert)`, this is the third vertex of a positively oriented triangle that `q` is on, so that `q` is on the edge `(u, v)` and `w = get_adjacent(tri, u, v)`. Otherwise, `(u, v, w)` is a ghost triangle close to `q`.
!!! warning "Non-convex geometries"
This function assumes that the geometry is convex. The function will still be able to return, but `is_outside(q_pos_cert)` may not necessarily mean `q`
is outside of the triangulation. The main function [`jump_and_march`](@ref) will have to restart the algorithm if it is found that `is_outside(q_pos_cert)`
was incorrect.
# Extended help
This function works by stepping along vertices on the boundaries in the direction specified by `direction_cert`, using `search_right_down_adjacent_boundary_edges`
if `is_right(direction_cert)` and `search_left_down_adjacent_boundary_edges` otherwise. In these functions, a `while` loop is used to keep stepping until `q_pos_cert`,
which is updated at each iteration, changes value.
"""
function search_down_adjacent_boundary_edges(tri::Triangulation, k, q, direction, q_pos, next_vertex, store_history::F=Val(false), history=nothing, ghost_vertex=integer_type(tri)(𝒢)) where {F}
i = k
j = next_vertex
pⱼ = get_point(tri, j)
if is_right(direction)
return_flag, cert, i, j, k = search_right_down_adjacent_boundary_edges(tri, q, q_pos, store_history, history, ghost_vertex, i, j, pⱼ)
else
return_flag, cert, i, j, k = search_left_down_adjacent_boundary_edges(tri, q, q_pos, store_history, history, ghost_vertex, i, j, pⱼ)
end
return_flag && return cert, i, j, k
throw(PointNotFoundError(tri, q))
end
function search_right_down_adjacent_boundary_edges(tri::Triangulation, q, q_pos, store_history::F, history, ghost_vertex, i, j, pⱼ) where {F}
if is_true(store_history) # in case we don't enter the loop
k′ = get_adjacent(tri, i, j)
add_triangle!(history, i, j, k′)
add_edge!(history, i, j)
end
while is_right(q_pos)
i, pᵢ = j, pⱼ
j = get_right_boundary_node(tri, i, ghost_vertex)
if is_true(store_history)
k′ = get_adjacent(tri, i, j)
add_triangle!(history, i, j, k′)
add_edge!(history, i, j)
end
pⱼ = get_point(tri, j)
right_cert = point_position_relative_to_line(pᵢ, pⱼ, q)
q_pos = !is_collinear(right_cert) ? Cert.Outside : point_position_on_line_segment(pᵢ, pⱼ, q)
end
if is_outside(q_pos)
return (true, Cert.Outside, j, i, get_adjacent(tri, j, i))
elseif is_on(q_pos) || is_degenerate(q_pos)
k = get_adjacent(tri, i, j)
return (true, Cert.On, i, j, k) # true is the return_flag
end
return (false, Cert.Outside, i, j, k)
end
function search_left_down_adjacent_boundary_edges(tri::Triangulation, q, q_pos, store_history::F, history, ghost_vertex, i, j, pⱼ) where {F}
if is_true(store_history) # in case we don't enter the loop
k′ = get_adjacent(tri, j, i)
add_triangle!(history, j, i, k′)
add_edge!(history, i, j) # i,j → j, i to get the ordering of the segments
end
while is_left(q_pos)
i, pᵢ = j, pⱼ
j = get_left_boundary_node(tri, i, ghost_vertex)
if is_true(store_history)
k′ = get_adjacent(tri, j, i)
add_triangle!(history, j, i, k′)
add_edge!(history, i, j)
end
pⱼ = get_point(tri, j)
left_cert = point_position_relative_to_line(pᵢ, pⱼ, q)
q_pos = !is_collinear(left_cert) ? Cert.Outside : point_position_on_line_segment(pⱼ, pᵢ, q)
end
if is_outside(q_pos)
return (true, Cert.Outside, i, j, get_adjacent(tri, i, j))
elseif is_on(q_pos) || is_degenerate(q_pos)
k = get_adjacent(tri, j, i)
return (true, Cert.On, j, i, k)
end
return (false, Cert.Outside, i, j, k)
end
"""
check_for_intersections_with_interior_edges_adjacent_to_boundary_vertex(tri::Triangulation, k, q, right_cert, left_cert, store_history=Val(false), history=nothing, ghost_vertex=𝒢) -> (Bool, Vertex, Vertex, Certificate, Certificate)
Checks for intersections between the line `pq`, where `p = get_point(tri, k)`, and the edges neighbouring `p`, assuming `k` is a boundary node. This function should only be used
after using [`check_for_intersections_with_adjacent_boundary_edges`](@ref).
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `k`: The boundary vertex to start from.
- `q`: The query point.
- `right_cert`: The [`Certificate`](@ref) for the position of `q` relative to the boundary edge right of `k`, coming from [`check_for_intersections_with_adjacent_boundary_edges`](@ref).
- `left_cert`: The [`Certificate`](@ref) for the position of `q` relative to the boundary edge left of `k`, coming from [`check_for_intersections_with_adjacent_boundary_edges`](@ref).
- `store_history=Val(false)`: Whether to store the history of the algorithm.
- `history=nothing`: The history of the algorithm. If `store_history`, then this should be a [`PointLocationHistory`](@ref) object.
- `ghost_vertex=𝒢`: The ghost vertex corresponding to the boundary that `k` resides on.
# Outputs
The output takes the form `(i, j, edge_cert, triangle_cert)`. Rather than defining each output individually, here are the possible froms of the output:
- `(i, j, Single, Outside)`: The line `pq` intersects the edge `pᵢpⱼ` and `(j, i, k)` is a positively oriented triangle so that `pᵢ` is left of `pq` and `pⱼ` is right of `pq`.
- `(i, j, None, Inside)`: The point `q` is inside the positively oriented triangle `(i, j, k)`.
- `(0, 0, None, Outside)`: The point `q` is outside of the triangulation.
- `(i, j, On, Inside)`: The point `q` is on the edge `pᵢpⱼ`, and thus inside the positively oriented triangle `(i, j, k)`.
- `(i, j, Right, Outside)`:` The point `q` is collinear with the edge `pᵢpⱼ`, but is off of it and further into the triangulation.
!!! warning "Non-convex geometries"
This function assumes that the geometry is convex.
# Extended help
This function works in two stages. Firstly, using `check_for_intersections_with_single_interior_edge_adjacent_to_boundary_vertex`, we check for the intersection of `pq` with the edges neighbouring
the vertex `k`, rotating counter-clockwise until we find an intersection or reach the other side of the boundary, starting from the first edge counter-clockwise away from the boundary edge right of
the vertex `k`. By keeping track of the positions of `pq` relative to the current vertex and the previous, we can identify when an intersection is found. If no intersection is found before
reaching the boundary edge left of `k`, then `check_for_intersections_with_triangle_left_to_boundary_vertex` is used to check the remaining triangle.
"""
function check_for_intersections_with_interior_edges_adjacent_to_boundary_vertex(tri::Triangulation, k, q, right_cert, left_cert, store_history::F=Val(false), history=nothing, ghost_vertex=integer_type(tri)(𝒢)) where {F}
I = integer_type(tri)
p = get_point(tri, k)
other_boundary_node = get_left_boundary_node(tri, k, ghost_vertex)
num_interior_neighbours = num_neighbours(tri, k) - 3 # 3 = + the two boundary neighbours + the 𝒢
i = get_right_boundary_node(tri, k, ghost_vertex)
pᵢ = get_point(tri, i)
certᵢ = right_cert # Do not need to check if is_collinear(certᵢ) - this function should only be used after checking check_for_intersections_with_adjacent_boundary_edges anyway
for _ in 1:max(num_interior_neighbours, 1)
# In the above, we need max(num_interior_neighbours, 1) in case the point
# being considered is a corner point on the boundary with no interior edge neighbours.
# If we just had 1:num_interior_neighbours, then nothing would happen in this loop
# since num_interior_neighbours = 0, but we still need to make sure we check the edge
# connecting the two boundary neighbours
return_flag, _i, _j, edge_cert, triangle_cert, i, pᵢ, certᵢ = check_for_intersections_with_single_interior_edge_adjacent_to_boundary_vertex(tri, k, q, store_history, history, p, i, pᵢ, certᵢ)
return_flag && return _i, _j, edge_cert, triangle_cert
end
# Now, we've made it to this point, but we still need to check the triangle that contains the boundary vertex to the left of p.
# This case is kept separate so that we can reuse left_cert
return_flag, _i, _j, edge_cert, triangle_cert = check_for_intersections_with_triangle_left_to_boundary_vertex(tri, k, q, left_cert, store_history, history, p, other_boundary_node, num_interior_neighbours, i, pᵢ, certᵢ)
return_flag && return _i, _j, edge_cert, triangle_cert
# If we've made it to this point in the algorithm, then the point is outside of the triangulation. Again,
# this is using the assumption that the geometry is convex.
return zero(I), zero(I), Cert.None, Cert.Outside
end
function check_for_intersections_with_single_interior_edge_adjacent_to_boundary_vertex(tri::Triangulation, k, q, store_history::F, history, p, i, pᵢ, certᵢ) where {F}
j = get_adjacent(tri, k, i)
pⱼ = get_point(tri, j)
certⱼ = point_position_relative_to_line(p, pⱼ, q)
I = integer_type(tri)
# Start by checking if pq intersects the edge pᵢpⱼ, meaning q is left or right of pᵢ, but the opposite of pⱼ
if (is_left(certᵢ) && is_right(certⱼ)) || (is_right(certᵢ) && is_left(certⱼ))
# If we have this intersection, there are three possibilities. The first is that there is indeed
# an intersection, which we check by counting the intersections of pq with pᵢpⱼ.
# Note that we have to check this in this case since q could be behind the edge - this is not
# checked for interior nodes since we can completely rotate around an interior node.
intersection_cert = line_segment_intersection_type(p, q, pᵢ, pⱼ)
if has_one_intersection(intersection_cert)
# q is not in the triangle, but we still have an intersection.
# In the returned value, we switch i and j so that pᵢ is left of pq and pⱼ is right of pq
is_true(store_history) && add_triangle!(history, i, j, k)
return true, j, i, Cert.Single, Cert.Outside, i, pᵢ, certᵢ # true is the return_flag, and the last three returns are for type stability at the end of the function, in case we need to continue looping in the main function
elseif is_touching(intersection_cert)
# q is on the edge
if is_true(store_history)
add_triangle!(history, i, j, k)
add_edge!(history, i, j)
end
return true, i, j, Cert.On, Cert.Inside, i, pᵢ, certᵢ
end
# There may be no intersection, but it could be inside the triangle.
pⱼp_edge = point_position_relative_to_line(pⱼ, p, q)
ppᵢ_edge = point_position_relative_to_line(p, pᵢ, q)
if is_left(pⱼp_edge) && is_left(ppᵢ_edge)
if is_true(store_history)
add_triangle!(history, i, j, k)
end
return true, i, j, Cert.None, Cert.Inside, i, pᵢ, certᵢ
end
# If none of the above lead to a return, then q must be on the other side of p away from the interior, meaning
# it is in the exterior. Here, we are making use of the fact that the domain is
# convex. When the domain is not convex, this won't necessarily work.
return true, zero(I), zero(I), Cert.None, Cert.Outside, i, pᵢ, certᵢ
end
# If we do not have the condition above, it is still possible that q is on the edge.
if is_collinear(certⱼ)
# First, let us check if q is on the edge.
q_cert = point_position_on_line_segment(p, pⱼ, q)
if is_on(q_cert) || is_degenerate(q_cert)
# Here, q is on the edge, so we consider it as being inside the triangle.
if is_true(store_history)
add_triangle!(history, i, j, k)
pᵢ ≠ q && pⱼ ≠ q && add_edge!(history, i, j) # be careful not to add a collinear edge that is just starting at q
end
return true, i, j, Cert.On, Cert.Inside, i, pᵢ, certᵢ
elseif is_right(q_cert)
# Here, q is to the right of ppⱼ, which just means that it is inside the triangulation.
if is_true(store_history)
add_triangle!(history, i, j, k)
p ≠ q && pⱼ ≠ q && add_edge!(history, k, j) # be careful not to add a collinear edge that is just starting at q
end
return true, j, i, Cert.Right, Cert.Outside, i, pᵢ, certᵢ # flip i and j to get the correct orientation
elseif is_left(q_cert)
# This means that q is left of ppⱼ, but this means that q is away from k, i.e. away from the
# boundary node - it is outside of the triangulation.
return true, zero(I), zero(I), Cert.None, Cert.Outside, i, pᵢ, certᵢ
end
end
# If we still have not returned, then let us rotate onto the next triangle.
return false, zero(I), zero(I), Cert.None, Cert.None, j, pⱼ, certⱼ
end
function check_for_intersections_with_triangle_left_to_boundary_vertex(tri::Triangulation, k, q, left_cert, store_history::F, history, p, other_boundary_node, num_interior_neighbours, i, pᵢ, certᵢ) where {F}
I = integer_type(tri)
if num_interior_neighbours > 0 # It is possible that there are actually no interior nodes around the point k
j = other_boundary_node
pⱼ = get_point(tri, j)
certⱼ = left_cert
if (is_left(certᵢ) && is_right(certⱼ)) || (is_right(certᵢ) && is_left(certⱼ))
intersection_cert = line_segment_intersection_type(p, q, pᵢ, pⱼ)
if has_one_intersection(intersection_cert)
if is_true(store_history)
add_triangle!(history, i, j, k)
end
return true, j, i, Cert.Single, Cert.Outside
end
pⱼp_edge = point_position_relative_to_line(pⱼ, p, q)
ppᵢ_edge = point_position_relative_to_line(p, pᵢ, q)
if is_left(pⱼp_edge) && is_left(ppᵢ_edge)
if is_true(store_history)
add_triangle!(history, i, j, k)
end
return true, i, j, Cert.None, Cert.Inside
end
return true, zero(I), zero(I), Cert.None, Cert.Outside
end
# We do not need to check for collinearities here - this was already done in check_for_intersections_with_adjacent_boundary_edges
end
return false, zero(I), zero(I), Cert.None, Cert.None
end
"""
exterior_jump_and_march(tri::Triangulation, k, q, ghost_vertex=𝒢) -> (Vertex, Vertex)
Given a query point `q` outside of the triangulation, find the ghost triangle containing `q`.
# Arguments
- `tri`: The [`Triangulation`](@ref).
- `k`: The exterior boundary vertex to start from.
- `q`: The query point.
- `ghost_vertex=𝒢`: The ghost vertex corresponding to the boundary that `k` resides on.
# Outputs
- `i`: The first vertex of the edge on the boundary adjoining the positively oriented ghost triangle.
- `j`: The second vertex of the edge on the boundary adjoining the positively oriented ghost triangle.
!!! warning "Non-convex geometries and internal query points"
This function assumes that the geometry is convex. If the geometry is not convex, the returned value may not be correct and should be
checked separately. Additionally, if `q` is actually inside the triangulation, then the result is meaningless.
# Extended help
This function works by first finding the position of `q` relative to `pₘp`, where `pₘ` is the representative point for
the `ghost_vertex` and `p = get_point(tri, k)`. Depending on this position, we rotate counter-clockwise if `q` is
left of the line using `exterior_jump_and_march_rotate_left` and clockwise otherwise using `exterior_jump_and_march_rotate_right`.
By keeping track of the current position of `q` and its position relative to the next ghost edge, we can identify when `q`
resides inside a ghost triangle.
"""
function exterior_jump_and_march(tri::Triangulation, k, q, ghost_vertex=integer_type(tri)(𝒢))
pₘ, pᵢ = get_point(tri, ghost_vertex, k)
i = k
q_position = point_position_relative_to_line(pₘ, pᵢ, q)
if is_left(q_position) # q is left of the ghost edge through pᵢ, so rotate left
return exterior_jump_and_march_rotate_left(tri, q, i, pₘ, ghost_vertex)
else # rotate right
return exterior_jump_and_march_rotate_right(tri, q, i, pₘ, ghost_vertex)
end
end
function exterior_jump_and_march_rotate_left(tri, q, i, pₘ, ghost_vertex)
j = get_right_boundary_node(tri, i, ghost_vertex)
pⱼ = get_point(tri, j)
new_q_pos = point_position_relative_to_line(pₘ, pⱼ, q)
while is_left(new_q_pos)
i = j
j = get_right_boundary_node(tri, i, ghost_vertex)
pⱼ = get_point(tri, j)
new_q_pos = point_position_relative_to_line(pₘ, pⱼ, q)
end
return j, i # Swap the orientation so that i, j is a boundary edge
end
function exterior_jump_and_march_rotate_right(tri, q, i, pₘ, ghost_vertex)
j = get_left_boundary_node(tri, i, ghost_vertex)
pⱼ = get_point(tri, j)
new_q_pos = point_position_relative_to_line(pₘ, pⱼ, q)
while is_right(new_q_pos)
i = j
j = get_left_boundary_node(tri, i, ghost_vertex)
pⱼ = get_point(tri, j)
new_q_pos = point_position_relative_to_line(pₘ, pⱼ, q)
end
return i, j
end
"""
jump_and_march(tri, q; kwargs...) -> Triangle[, Bool]
Find the triangle in the triangulation `tri` containing the query point `q` using the jump-and-march algorithm.
# Arguments
- `tri`: The [`Triangulation`](@ref).
- `q`: The query point.
# Keyword Arguments
- `point_indices=each_solid_vertex(tri)`: The indices of the vertices to consider as possible starting points for the algorithm.
- `m=default_num_samples(num_vertices(point_indices))`: The number of samples to use when selecting the initial point.
- `try_points=()`: A list of points to try as the initial point in addition to the `m` sampled.
- `rng=Random.default_rng()`: The random number generator to use.
- `k=select_initial_point(tri, q; point_indices, m, try_points, rng)`: The initial point to start the algorithm from. See [`select_initial_point`](@ref).
- `store_history=Val(false)`: Whether to store the history of the algorithm.
- `history=nothing`: The history of the algorithm. If `store_history`, then this should be a [`PointLocationHistory`](@ref) object.
- `maxiters=2 + num_exterior_curves(tri) - num_solid_vertices(tri) + num_solid_edges(tri)`: The maximum number of iterations to perform before restarting the algorithm with [`restart_jump_and_march`](@ref).
!!! note "Restarting the algorithm"
If the algorithm restarts, then the initial point `k` is selected again using [`select_initial_point`](@ref), and the algorithm is restarted from there.
This is done if the algorithm gets stuck in a loop, or if the algorithm is not able to find the correct triangle containing `q` after `maxiters` iterations. For a convex
geometry, `maxiters` can be safely ignored, as the sequence of triangles visited is acyclic [see H. Edelsbrunner, An acyclicity theorem for cell complexes in d dimensions, Combinatorica 10 (1990) 251-260.)].
- `concavity_protection=false`: Whether to use concavity protection. See [`concavity_protection_check`](@ref). This is only needed if your triangulation is not convex.
- `use_barriers::Val{U}=Val(false)`: Whether to stop searching beyond any segments in the triangulation.
!!! warning "Found triangles with barriers"
If you are using barriers, it will be your responsibility to verify that any found triangle from this function actually
contains the triangle. This can be verified using the returned `flag` (see below), although the point might still be on the triangle's
boundary.
!!! warning "Walking past vertices of barriers"
If you are using barriers, it is possible that the algorithm can walk past vertices of barriers. One way this can happen is if
the initial search line intersects a vertex, in which case the segment might not be considered. Another way this can happen is if you
start the algorithm directly on a segment vertex, in which case the algorithm can go past it (e.g. this means that it is possible that a
ghost triangle might still be returned if you start the algorithm on a boundary node).
# Output
- `V`: The triangle containing `q`, with type given by `triangle_type(tri)`.
!!! danger "Hitting barriers"
If a barrier is hit before any initial triangle is properly identified, the returned triangle is
`($∅, $∅, $∅)`; this is only possible if `use_barriers == Val(true)`. Moreover, if `use_barriers == Val(true)`,
the final triangle may not even be valid if `invisible_flag == true` (defined below).
If you have `use_barriers == Val(true)`, then we also return
- `invisible_flag`: `false` if the triangle was found without hitting a barrier, and `true` otherwise.
!!! warning "Non-convex geometries"
While this function does still work for non-convex geometries, it may be significantly slower than for convex geometries, as most of the details
of the algorithm assume that the geometry is convex, and so the algorithm may have to restart many times at new initial vertices `k`.
!!! warning "Ghost triangles"
For this function to work best, the triangulation should have ghost triangles, which you can add using `add_ghost_triangles!` in case `tri` does not already have them.
Without ghost triangles, the function may not be able to find the correct triangle containing `q`.
# Extended help
The algorithm underlying this function is complicated and broken into many parts. Here, we describe a brief overview of the algorithm, but note that the
documentation contains a much more detailed description.
1. Firstly, the algorithm is initialised depending on whether `k` is a boundary or an interior vertex, using
[`initialise_jump_and_march_boundary_vertex`](@ref) or [`initialise_jump_and_march_interior_vertex`](@ref) respectively.
2. From the initial triangle `(i, j, k)` chosen, we then check if `q` is one of `pᵢ`, `pⱼ`, and `p = pₖ` and then return according to [`jump_and_march_return_on_vertex`](@ref) if needed.
3. If we do not return above, we need to step from the initial triangle towards `q`. Since we put `pᵢ` and `pⱼ`
to the left and right of the line `pq`, respectively, this means that we step until the triangle `pᵢpⱼq` is no longer
positively oriented. So, while the triangle is positively oriented, we step according to [`jump_and_march_across_triangle`](@ref).
4. If we have not yet returned and the triangle is no longer positively oriented, we check if the triangle is degenerate using [`jump_and_march_degenerate_arrangement`](@ref)
and reinitialise the algorithm if needed. Otherwise, we have found the triangle containing `q` and return the triangle.
"""
function jump_and_march(tri::Triangulation, q;
point_indices=each_solid_vertex(tri),
m=default_num_samples(num_vertices(point_indices)),
try_points=(),
rng::AbstractRNG=Random.default_rng(),
k=select_initial_point(tri, q; point_indices, m, try_points, rng),
store_history::F=Val(false),
history=nothing,
maxiters=2 + num_exterior_curves(tri) - num_solid_vertices(tri) + num_solid_edges(tri),
concavity_protection=false,
use_barriers::Val{U}=Val(false)) where {F,U}
return _jump_and_march(tri, q, k, store_history, history, rng, maxiters, zero(maxiters), concavity_protection, zero(maxiters), use_barriers)
end
"""
initialise_jump_and_march_interior_vertex(tri::Triangulation, q, k, store_history::F, history, rng) -> (Bool, Point, Vertex, Vertex, Point, Point)
Initialise the jump-and-march algorithm for an interior vertex `k`.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `q`: The query point.
- `k`: The interior vertex to start from.
- `store_history`: Whether to store the history of the algorithm.
- `history`: The history of the algorithm. If `store_history`, then this should be a [`PointLocationHistory`](@ref) object.
- `rng`: The random number generator to use.
# Outputs
- `restart_flag`: Whether the algorithm needs to be restarted.
- `p`: The point corresponding to the vertex `k`.
- `i`: The first vertex of the triangle adjoining `k` to start from.
- `j`: The second vertex of the triangle adjoining `k` to start from.
- `pᵢ`: The point corresponding to the vertex `i`.
- `pⱼ`: The point corresponding to the vertex `j`.
# Extended help
This function works by simply using [`select_initial_triangle_interior_vertex`](@ref) to find the initial triangle to start from. A check is made
to see if the edge `(i, j)` refers to a non-existent edge `($∅, $∅)`, in which case the algorithm needs to be restarted.
"""
function initialise_jump_and_march_interior_vertex(tri::Triangulation, q, k, store_history::F, history, rng) where {F}
# If k is not a boundary node, then we can rotate around the point k to find an initial triangle
# to start the search. Additionally, if there are no ghost triangles, we can only hope that q
# is inside the interior, meaning we should only search for the initial triangle here anyway.
p, i, j, pᵢ, pⱼ = select_initial_triangle_interior_vertex(tri, k, q, store_history, history, rng)
if !edge_exists(i) && !edge_exists(j) # When we find a possible infinite loop, we use i==j==∅. Let's reinitialise.
return true, p, i, j, pᵢ, pⱼ # true is the restart_flag
end
is_true(store_history) && add_triangle!(history, j, i, k)
return false, p, i, j, pᵢ, pⱼ
end
"""
initialise_jump_and_march_boundary_vertex(tri::Triangulation, q, k, store_history:, history, ghost_vertex, concavity_protection) -> (Bool, Bool, Triangle, Point, Vertex, Vertex, Point, Point)
Initialise the jump-and-march algorithm for a boundary vertex `k`.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `q`: The query point.
- `k`: The boundary vertex to start from.
- `store_history`: Whether to store the history of the algorithm.
- `history`: The history of the algorithm. If `store_history`, then this should be a [`PointLocationHistory`](@ref) object.
- `ghost_vertex`: The ghost vertex corresponding to the boundary that `k` resides on.
- `concavity_protection`: Whether to use concavity protection. See [`concavity_protection_check`](@ref). This is only needed if your triangulation is not convex.
# Outputs
- `restart_flag`: Whether the algorithm needs to be restarted.
- `return_flag`: Whether the algorithm can return immediately, returning `V`.
- `V`: Either a triangle that can returned if `return_flag = true`, or some triangle that is used for type stability for this return value.
- `p`: The point corresponding to the vertex `k`, or it may be `q` if the algorithm is going to be restarted or `return_flag = true`.
- `i`: The first vertex of the triangle adjoining `k` to start from, or `k` if the algorithm is going to be restarted or `return_flag = true`.
- `j`: The second vertex of the triangle adjoining `k` to start from, or `k` if the algorithm is going to be restarted or `return_flag = true`.
- `pᵢ`: The point corresponding to the vertex `i`, or it may be `q` if the algorithm is going to be restarted or `return_flag = true`.
- `pⱼ`: The point corresponding to the vertex `j`, or it may be `q` if the algorithm is going to be restarted or `return_flag = true`.
# Extended help
There are multiple stages to this initialisation, starting from [`check_for_intersections_with_adjacent_boundary_edges`](@ref).
- If it is found that `q` is not outside of the triangulation, so that `q` is collinear with one of the boundary edges, then we use [`search_down_adjacent_boundary_edges`](@ref) to find where to start, noting
that we can return immediately if `q` is found to be on an adjacent boundary edge. Otherwise, [`exterior_jump_and_march`](@ref) can then be used to find the ghost triangle containing
`q`; if `concavity_protection = true`, then [`concavity_protection_check`](@ref) is used to determine if a restart is needed.
- If is is not found that `q` is outside of the triangulation yet based on information from the adjacent boundary edges, then we need to check the neighbouring
interior edges using [`check_for_intersections_with_interior_edges_adjacent_to_boundary_vertex`](@ref), returning early if `q` is found to be inside one of
the neighbouring triangles. If the line `pq`, where `p = get_point(tri, k)`, does not intersect any of the neighbouring edges and it is not inside any of
the neighbouring triangles, then it must be outside of the triangulation and so we use [`exterior_jump_and_march`](@ref) to find the triangle; as before, [`concavity_protection_check`](@ref)
is used on the found ghost triangle if needed. If there is an intersection, then we return the triangle containing the intersection point that we can start the algorithm from,
and its associated vertices and points.
"""
function initialise_jump_and_march_boundary_vertex(tri::Triangulation, q, k, store_history::F, history, ghost_vertex, concavity_protection) where {F}
direction, q_pos, next_vertex, right_cert, left_cert =
check_for_intersections_with_adjacent_boundary_edges(tri, k, q, ghost_vertex)
Ttype = triangle_type(tri)
if !is_outside(direction)
# q is collinear with one of the edges, so let's jump down these edges and try to find q
q_pos, u, v, w = search_down_adjacent_boundary_edges(tri, k, q, direction, q_pos, next_vertex, store_history, history, ghost_vertex)
if is_on(q_pos)
return false, true, construct_triangle(Ttype, u, v, w), q, k, k, q, q # false is the restart_flag, true is the return_flag. We return q, q, q just to get type stability with all returns
else
u, v = exterior_jump_and_march(tri, u, q, ghost_vertex)
w = get_adjacent(tri, u, v)
V = construct_triangle(Ttype, u, v, w) # Can't just use I(𝒢) here since there could be multiple - just use get_adjacent
if concavity_protection_check(tri, concavity_protection, V, q)
return true, false, V, q, k, k, q, q # the extra returns are just for type stability
else
return false, true, V, q, k, k, q, q
end
end
end
# If we did not find anything from the neighbouring boundary edges, we can search the neighbouring interior edges
i, j, edge_cert, triangle_cert =
check_for_intersections_with_interior_edges_adjacent_to_boundary_vertex(tri, k, q, right_cert, left_cert, store_history, history, ghost_vertex)
if is_inside(triangle_cert)
return false, true, construct_triangle(Ttype, i, j, k), q, i, j, q, q
elseif is_none(edge_cert)
u, v = exterior_jump_and_march(tri, k, q, ghost_vertex)
w = get_adjacent(tri, u, v)
V = construct_triangle(Ttype, u, v, w)
if concavity_protection_check(tri, concavity_protection, V, q)
return true, false, V, q, i, j, q, q
else
return false, true, V, q, i, j, q, q
end
else
p, pᵢ, pⱼ = get_point(tri, k, i, j)
return false, false, construct_triangle(Ttype, i, j, k), p, i, j, pᵢ, pⱼ # the triangle is just for type stability
end
end
"""
jump_and_march_return_on_vertex(tri::Triangulation, q, k, p, pᵢ, pⱼ, i, j) -> (Bool, Bool, Triangle)
Check if `q` is one of the vertices of the triangle `(i, j, k)` and return if needed.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `q`: The query point.
- `k`: The vertex `k` that the algorithm started from.
- `p`: The point corresponding to the vertex `k`.
- `pᵢ`: The point corresponding to the vertex `i`.
- `pⱼ`: The point corresponding to the vertex `j`.
- `i`: The first vertex of the triangle adjoining `k` to start from.
- `j`: The second vertex of the triangle adjoining `k` to start from.
# Outputs
- `restart_flag`: Whether the algorithm needs to be restarted.
- `return_flag`: Whether the algorithm can return immediately, returning `V`.
- `V`: The triangle `(i, j, k)`.
# Extended help
An extra check is made in this algorithm for the case that the point that `q` is equal to is one of the points corresponding to a ghost vertex,
so it may be for example that `q == pᵢ` but `is_ghost_vertex(i)`, in which case the algorithm would need to restart.
"""
function jump_and_march_return_on_vertex(tri::Triangulation, q, k, p, pᵢ, pⱼ, i, j)
# Just return where we currently are. We do need to be careful, though:
# If k was a ghost vertex, then one of pᵢ or pⱼ could come from the
# representative point list, which could mean that q is equal to one of the
# vertices, but without meaning that it is actually in that triangle. So,
# we need to also check for the type of indices we have.
safety_check = (q == p && !is_ghost_vertex(k)) ||
(q == pᵢ && !is_ghost_vertex(i)) ||
(q == pⱼ && !is_ghost_vertex(j))
if safety_check
orientation = triangle_orientation(pᵢ, pⱼ, p)
if is_positively_oriented(orientation)
return false, true, construct_triangle(triangle_type(tri), i, j, k)
else
return false, true, construct_triangle(triangle_type(tri), j, i, k)
end
else
return true, false, construct_triangle(triangle_type(tri), i, j, k)
end
end
"""
jump_and_march_across_triangle(tri::Triangulation, q, k, store_history, history, maxiters, cur_iter, concavity_protection, arrangement, original_k, last_changed, p, i, j, pᵢ, pⱼ) -> (Bool, Bool, Bool, Triangle, Integer, Certificate, Vertex, Vertex, Vertex, Point, Point, Integer, Integer)
Walks across the current triangle past the edge `(i, j)`. progressing the [`jump_and_march`](@ref) algorithm.
# Arguments
- `tri::Triangulation`: The [`Triangulation`](@ref).
- `q`: The query point.
- `k`: The vertex that the algorithm started from.
- `store_history`: Whether to store the history of the algorithm.
- `history`: The history of the algorithm. If `store_history`, then this should be a [`PointLocationHistory`](@ref) object.
- `maxiters`: The maximum number of iterations to perform before restarting the algorithm with [`restart_jump_and_march`](@ref).
- `cur_iter`: The current iteration of the algorithm.
- `concavity_protection`: Whether to use concavity protection. See [`concavity_protection_check`](@ref). This is only needed if your triangulation is not convex.
- `arrangement`: A [`Certificate`](@ref) defining the orientation of the triangle `pᵢpⱼq`.
- `original_k`: The original vertex that the algorithm started from.
- `last_changed`: The last vertex that was changed in the algorithm.
- `p`: The point corresponding to the vertex `original_k`.
- `i`: The first vertex of the triangle adjoining `k` to step from.
- `j`: The second vertex of the triangle adjoining `k` to step from.
- `pᵢ`: The point corresponding to the vertex `i`.
- `pⱼ`: The point corresponding to the vertex `j`.
# Outputs
- `restart_flag`: Whether the algorithm needs to be restarted.
- `return_flag`: Whether the algorithm can return immediately, returning `V`.
- `reinitialise_flag`: Whether the algorithm needs to be reinitialised at a new vertex `k`. (This would only be needed if `!has_ghost_triangles(tri)`.)
- `V`: The triangle stepped into.
- `cur_iter`: The new number of iterations of the algorithm.
- `arrangement`: A [`Certificate`](@ref) defining the orientation of the triangle `pᵢpⱼq` with the updated values of `i` and `j`.
- `k`: The new vertex that the algorithm is at.
- `last_changed`: The last vertex that was changed in the algorithm.
- `original_k`: The original vertex that the algorithm started from.
- `pᵢ`: The point corresponding to the vertex `i`.
- `pⱼ`: The point corresponding to the vertex `j`.
- `i`: The first vertex of the triangle adjoining `k` to step from.
- `j`: The second vertex of the triangle adjoining `k` to step from.
# Extended help
This part of the algorithm is relatively complicated because there are many cases that need to be accounted for. Here we give a brief description of how this step works,
and note that the documentation contains a much more detailed description.
1. Firstly, we need to check whether `k` is an exterior ghost vertex or not. If `k` is an exterior ghost vertex, then this means that we are stepping outside of the
triangulation. Thus, we use [`exterior_jump_and_march`](@ref) to find where `q` is, starting from the `last_changed` vertex. If `concavity_protection = true`, then
[`concavity_protection_check`](@ref) is used to determine if a restart is needed, or if we can return safely. If we reach this step but `!has_ghost_triangles(tri)`,
then the algorithm should need to be reinitialised since `q` should not be outside of the triangulation, and so we return with `reinitialise_flag = true`.
2. Now we consider the case where `k` is not an exterior ghost vertex. We move forward by updating the value of `k` so that `k = get_adjacent(tri, i, j)`, and then consider where `pₖ` is relative
to the line `pq`.
2a. If `pₖ` is to the right of `pq`, then we should update `j` by `j = k`, ensuring that `j` is always to the right of `pq`.
2b. If `pₖ` is to the left of `pq`, then we should update `i` by `i = k`, ensuring that `i` is always to the left of `pq`.
2c. The alternative to 2a and 2b is that `pₖ` is collinear with the edge of `pq`, which could mean that `q` is in the current triangle or it is in a triangle further away. We compute a
[`Certificate`](@ref) that determines where `q` is relative to the triangle `pᵢpⱼpₖ`. If `q` is inside or on this triangle, then we return, restarting if necessary according to
`concavity_protection` and [`concavity_protection_check`](@ref). If we do not yet need to return, then we need to make a decision as to which of `i` and `j` to update, noting that
we want `i` to be left of `pq` and `j` to be right of `pq`, but this is no longer unambiguous since `pₖ` is collinear with `pq`. We make this decision according to `last_changed`:
If `last_changed = i`, then moving left is what caused us to find this collinear edge, and so we send `k` left by letting `i = k`. Otherwise, we send `k` right by letting `j = k`.
3. Now having stepped forward, we recompute the [`Certificate`](@ref) for arrangement and return, setting `restart_flag = true` if `cur_iters ≥ maxiters`.
"""
function jump_and_march_across_triangle(tri::Triangulation, q, k, store_history::F, history, maxiters, cur_iter, concavity_protection, arrangement, original_k, last_changed, p, i, j, pᵢ, pⱼ) where {F}
cur_iter += 1
if is_true(store_history)
if last_changed == i
add_left_vertex!(history, i)
elseif last_changed == j
add_right_vertex!(history, j)
end
end
# We need to step forward. To do this, we need to be careful of ghost vertices.
if is_exterior_ghost_vertex(tri, k)
# If this happens, it means we have hit an outer boundary edge, and so we need to go into the exterior. If there are no
# ghost triangles, though, we just restart the search. Note that interior boundary indices do not matter since the ghost
# triangles there have the same orientation, so we can find them as normal.
if has_ghost_triangles(tri)
i, j = exterior_jump_and_march(tri, last_changed == i ? j : i, q, k) # use last_changed to ensure we get the boundary point
V = construct_triangle(triangle_type(tri), i, j, get_adjacent(tri, i, j))
if concavity_protection_check(tri, concavity_protection, V, q)
return true, false, false, V, cur_iter, arrangement, k, last_changed, original_k, pᵢ, pⱼ, i, j # restart_flag, return_flag, reinitialise_flag, V
else
return false, true, false, V, cur_iter, arrangement, k, last_changed, original_k, pᵢ, pⱼ, i, j
end
else
return false, false, true, construct_triangle(triangle_type(tri), last_changed, last_changed, last_changed), cur_iter, arrangement, k, last_changed, original_k, pᵢ, pⱼ, i, j # just returning a triangle for stability
end
end
# Now we can move forward.
k = get_adjacent(tri, i, j)
pₖ = get_point(tri, k)
pₖ_pos = point_position_relative_to_line(p, q, pₖ)
if is_right(pₖ_pos)
if is_true(store_history)
add_triangle!(history, i, j, k)
end
j, pⱼ = k, pₖ
last_changed = j
elseif is_left(pₖ_pos)
if is_true(store_history)
add_triangle!(history, i, j, k)
end
i, pᵢ = k, pₖ