-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpoint2trimesh.m
959 lines (728 loc) · 38.2 KB
/
point2trimesh.m
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
function [ distances, surface_points, faces2, vertices2, corresponding_vertices_ID, new_faces_ID ] = point2trimesh( varargin )
% -------------------------------------------------------
%
% point2trimesh - Distance between a point and a triangulated surface in 3D
%
% The shortest line connecting a point and a triangulation in 3D is
% computed. The nearest point on the surface as well as the distance
% is returned. The distance is signed according to face normals to
% identify on which side of the surface the query point resides.
% The implementation is optimized for speed, and depending on your
% application you can use linear or parallel computation.
%
% Point insertion functionality
% (this feature is experimental and not optimized for speed):
% If the function is called with more than two output arguments,
% the surface points are included into the given triangulation
% and Delaunay conditions are restored locally. If triangles with small
% angles occur, additional vertices are inserted to eliminate them
% if possible.
%
% Algorithm: From every query point,
% - the nearest vertex
% - the nearest point on the edges and
% - the nearest point on the triangle's surfaces
% is calculated and the minimum distance out of these three is returned.
%
% Ver. 1.0.0
%
% Created: Daniel Frisch (29.03.2015)
% Last modified: Daniel Frisch (24.06.2015)
%
% ------------------------------------------------------
%
% Inputs:
% Pairs of parameter names and corresponding values.
% Structure arrays are expanded into separate inputs,
% where each field name corresponds to an input parameter name.
% Parameters:
% - 'Faces' (#faces x 3) Triangulation connectivity list. Each row defines a face, elements are vertex IDs.
% - 'Vertices' (#vertices x 3) Point matrix. Columns are x,y,z coordinates, row numbers are vertex IDs.
% - 'QueryPoints' (#qPoints x 3) Columns are x,y,z coordinates; each row defines a query point. Can be empty.
% - 'MaxDistance' (1 x 1) If the distance between a surface_point and its nearest vertex is within this range,
% no new vertex is inserted into the mesh. This helps avoiding
% triangles with small angles. (default: 1/10 the smallest inradius)
% - 'UseSubSurface' (1 x 1) Logical. If true (default), the distance to edges and surfaces is only calculated
% for faces that are connected to the vertex nearest to the query point.
% This speeds up the calculation but if the distance between two opposite parts
% of the surface is less than the spacing of the vertices, wrong results are produced.
% In the vectorized algorithm, 'SubSurface' is always false.
% - 'Algorithm' 'linear' (default): query points are processed successively in a 'for' loop.
% Use this if you have only few query points.
% 'parallel': query points are processed in parallel with 'parfor'.
% If no parallel pool exists, Matlab creates one automatically (which takes half a minute).
% It shuts down after 30 min idle time by default, but you can change that in the "Parallel Preferences".
% If Matlab doesn't correctly recognize the number of cores of your processor,
% change the "Number of workers" in "Manage Cluster Profiles".
% 'vectorized': query points are processed altogether in a vectorized manner.
% Be careful, this needs much RAM if you have many query points.
% This option is mostly included to show that vectorization does not always speed up the calculation:
% In the linear code, every query point can be assigned an individual cutout of the whole surface.
% 'linear_vectorized_subfunctions': query points and their individual cutout surfaces
% are processed successively in a for loop by functions that are capable of processing more than one point.
% This option is included to show that the non-vectorized functions are faster
% if only one point is processed at a time.
% 'parallel_vectorized_subfunctions': query points and their individual cutout surfaces
% are processed in parallel in a parfor loop by functions that are capable of processing more than one point.
% Again, this option is included to show that the non-vectorized functions are faster
% if only one point is processed at a time.
%
% Outputs:
% - distances (#qPoints x 1) Vector with the point-surface distances; sign depends on normal vectors.
% - surface_points (#qPoints x 3) Matrix with the corresponding nearest points on the surface.
% - faces2 (#faces2 x 3) Connectivity matrix of the triangulation including the surface_points as dedicated vertices
% - vertices2 (#vertices2 x 3) Point/Vertex matrix of the triangulation including the surface_points as dedicated vertices
% - corresponding_vertices_ID (#qPoints x 1) Vector with the IDs of the vertices corresponding to the query points
% - new_faces_ID Vector with the IDs of the new or modified faces (to give them a different color, for example)
%
%
% Usage example:
% FV.faces = [5 3 1; 3 2 1; 3 4 2; 4 6 2];
% FV.vertices = [2.5 8.0 1; 6.5 8.0 2; 2.5 5.0 1; 6.5 5.0 0; 1.0 6.5 1; 8.0 6.5 1.5];
% points = [2 4 2; 4 6 2; 5 6 2];
% [distances,surface_points] = point2trimesh(FV, 'QueryPoints', points);
% patch(FV,'FaceAlpha',.5); xlabel('x'); ylabel('y'); zlabel('z'); axis equal; hold on
% plot3M = @(XYZ,varargin) plot3(XYZ(:,1),XYZ(:,2),XYZ(:,3),varargin{:});
% plot3M(points,'*r')
% plot3M(surface_points,'*k')
% plot3M(reshape([shiftdim(points,-1);shiftdim(surface_points,-1);shiftdim(points,-1)*NaN],[],3),'k')
%
%
%% Dependencies
% [flist,plist] = matlab.codetools.requiredFilesAndProducts('point2trimesh.m'); [flist'; {plist.Name}']
%
% (no dependencies)
%
%% Parse Inputs
% valdiation functions
faceChk = @(x) validateattributes(x,{'double','int32' },{'real','finite','nonnan','positive' ,'integer','size',[NaN 3]});
vertChk = @(x) validateattributes(x,{'double','single'},{'real','finite','nonnan' ,'size',[NaN 3]});
pointChk = @(x) validateattributes(x,{'double' },{'real','finite','nonnan' });
distChk = @(x) validateattributes(x,{'double' },{'real','finite','nonnan','nonnegative' ,'scalar' });
logicChk = @(x) validateattributes(x,{'logical' },{'scalar'});
charChk = @(x) validateattributes(x,{'char' },{'nonempty','vector'});
parser = inputParser;
parser.FunctionName = mfilename;
parser.addParameter('Faces' ,[] ,faceChk);
parser.addParameter('Vertices' ,[] ,vertChk);
parser.addParameter('QueryPoints' ,[] ,pointChk);
parser.addParameter('MaxDistance' ,[] ,distChk);
parser.addParameter('UseSubSurface' ,true ,logicChk);
parser.addParameter('Algorithm' ,'linear',charChk);
parser.parse(varargin{:});
faces = double(parser.Results.Faces);
vertices = double(parser.Results.Vertices);
assert(~isempty(faces) && ~isempty(vertices), 'Invalid argument: ''Faces'' and ''Vertices'' mustn''t be empty.')
assert(max(faces(:))<=size(vertices,1), 'The value of ''Faces'' is invalid: the maximum vertex ID is bigger than the number of vertices in ''Vertices''')
qPoints = parser.Results.QueryPoints;
useSubSurface = parser.Results.UseSubSurface;
if nargout>2, insertPoints = true;
else insertPoints = false; end
% Calculate normals
r1 = vertices(faces(:,1),:); % (#faces x 3) % 1st vertex of every face
r2 = vertices(faces(:,2),:); % (#faces x 3) % 2nd vertex of every face
r3 = vertices(faces(:,3),:); % (#faces x 3) % 3rd vertex of every face
normals = cross((r2-r1),(r3-r1),2); % (#faces x 3) normal vector of every face
normals = bsxfun(@rdivide,normals,sqrt(sum(normals.^2,2))); % (#faces x 3) normalized normal vector of every face
if isempty(qPoints)
distances = [];
surface_points = [];
faces2 = faces;
vertices2 = vertices;
corresponding_vertices_ID = [];
new_faces_ID = [];
return
end
%% Distance Calculation
nQPoints = size(qPoints,1);
D = NaN(nQPoints,1);
P = NaN(nQPoints,3);
if insertPoints
max_distance = parser.Results.MaxDistance;
if isempty(max_distance)
tri = triangulation(faces,vertices);
[~,r] = tri.incenter;
max_distance = min(r)/10;
end
max_distance = max(max_distance,100*eps);
is_new_face = zeros(size(faces,1),1);
is_new_vertex = zeros(size(vertices,1),1);
corresponding_vertices_ID = NaN(size(qPoints,1),1);
end
switch parser.Results.Algorithm
case {'linear','normal'}
for r = 1:nQPoints
% Determine the surface points
point = qPoints(r,:); % (1 x 3) query point
[d,p,f] = processPoint(faces,vertices,point,normals, @distance_to_vertices,@distance_to_edges,@distance_to_surfaces, useSubSurface);
D(r) = d;
P(r,:) = p;
if insertPoints
% Include the surface points as new vertices into the mesh and restore Delaunay conditions
[ tri, is_new_face, is_new_vertex, new_vertex_ID ] = insert_vertex( triangulation(faces,vertices), p, f, max_distance, is_new_face, is_new_vertex );
faces = tri.ConnectivityList;
vertices = tri.Points;
normals = tri.faceNormal;
corresponding_vertices_ID(r) = new_vertex_ID;
end
end
case 'parallel'
assert(~insertPoints,'''Algorithm'', ''%s'' doesn''t support including the surface points into the geometry. Call point2trimesh with fewer output arguments or use ''linear'' algorithm.',parser.Results.Algorithm)
parfor r = 1:nQPoints
point = qPoints(r,:); % (1 x 3) query point
[d,p] = processPoint(faces,vertices,point,normals, @distance_to_vertices,@distance_to_edges,@distance_to_surfaces, useSubSurface);
D(r) = d;
P(r,:) = p;
end
case 'vectorized'
assert(~insertPoints,'''Algorithm'', ''%s'' doesn''t support including the surface points into the geometry. Call point2trimesh with fewer output arguments or use ''linear'' algorithm.',parser.Results.Algorithm)
if useSubSurface && ~ismember('UseSubSurface',parser.UsingDefaults)
warning('You specified ''UseSubSurface'',true, but ''Algorithm'',''vectorized'' always searches on the complete surface')
end
[D1,P1] = distance_to_vertices_vectorized(faces,vertices,qPoints,normals); % (#qPoints x 1), (#qPoints x 3), (#qPoints x 1)
[D2,P2] = distance_to_edges_vectorized (faces,vertices,qPoints,normals);
[D3,P3] = distance_to_surfaces_vectorized(faces,vertices,qPoints,normals);
% find minimum distance type
D = [D1,D2,D3]; % (#qPoints x 3)
P = cat(3,P1,P2,P3); % (#qPoints x xyz x 3)
[~,I] = min(abs(D),[],2);
D = D(sub2ind(size(D),(1:length(I))',I));
% extract nearest point on surface
P = permute(P,[2,3,1]); % (xyz x 3 x #qPoints)
sz = [size(P) 1 1];
P = P(:,sub2ind(sz(2:3),I,(1:length(I))')); % (xyz x #qPoints)
P = P'; % (#qPoints x xyz)
case 'linear_vectorized_subfunctions'
for r = 1:nQPoints
% Determine the surface points
point = qPoints(r,:); % (1 x 3) query point
[d,p,f] = processPoint(faces,vertices,point,normals, @distance_to_vertices_vectorized,@distance_to_edges_vectorized,@distance_to_surfaces_vectorized, useSubSurface);
D(r) = d;
P(r,:) = p;
if insertPoints
% Include the surface points as new vertices into the mesh and restore Delaunay conditions
[ tri, is_new_face, is_new_vertex, new_vertex_ID ] = insert_vertex( triangulation(faces,vertices), p, f, max_distance, is_new_face, is_new_vertex );
faces = tri.ConnectivityList;
vertices = tri.Points;
normals = tri.faceNormal;
corresponding_vertices_ID(r) = new_vertex_ID;
end
end
case 'parallel_vectorized_subfunctions'
assert(~insertPoints,'''Algorithm'', ''%s'' doesn''t support including the surface points into the geometry. Call point2trimesh with fewer output arguments or use ''linear'' algorithm.',parser.Results.Algorithm)
parfor r = 1:nQPoints
point = qPoints(r,:); % (1 x 3) query point
[d,p] = processPoint(faces,vertices,point,normals, @distance_to_vertices_vectorized,@distance_to_edges_vectorized,@distance_to_surfaces_vectorized, useSubSurface);
D(r) = d;
P(r,:) = p;
end
otherwise
error('The value of ''Algorithm'' is invalid.')
end
if insertPoints
% Despite the Delaunay condition is fulfilled, the
% triangulation might still contain triangles with small angles.
% To prevent this, insert vertices at the circumcenters of these triangles.
tri = triangulation(faces,vertices);
while true
[minAng,worstFace] = minimumAngle(tri,(1:size(tri.ConnectivityList,1))');
insertPt = tri.circumcenter(worstFace);
[~,insertPt,f] = processPoint(tri.ConnectivityList,tri.Points,insertPt,tri.faceNormal, @distance_to_vertices,@distance_to_edges,@distance_to_surfaces, useSubSurface);
[~,r] = tri.incenter(f);
[ tri2, is_new_face2, is_new_vertex2 ] = insert_vertex( tri, insertPt, f, r/2, is_new_face, is_new_vertex );
[minAng2,~] = minimumAngle(tri2,(1:size(tri2.ConnectivityList,1))');
if minAng2 > minAng+eps
tri = tri2;
is_new_face = is_new_face2;
is_new_vertex = is_new_vertex2;
else
break;
end
end
faces2 = tri.ConnectivityList;
vertices2 = tri.Points;
new_faces_ID = find(is_new_face);
end
% return output arguments
distances = D; % (#qPoints x 1)
surface_points = P; % (#qPoints x 3)
end
%% Non-vectorized Distance Functions
% (can process only one point)
function [D,P,F] = processPoint(faces,vertices,point,normals, distance_to_vertices,distance_to_edges,distance_to_surfaces, useSubSurface)
d = NaN(3,1); % (distanceTypes x 1)
p = NaN(3,3); % (distanceTypes x xyz)
f = NaN(3,1); % (distanceTypes x 1)
% find nearest vertice
[d(1),p(1,:),f(1),v] = distance_to_vertices(faces,vertices,point,normals);
% d: (1 x 1) signed distance to surface
% p: (1 x 3) corresponding point on surface
% v: (1 x 1) nearest vertex
% connectedFaces: (#connectedFaces x 1) face indices
if useSubSurface
[tri2,~,faces_2To1] = subSurface( triangulation(faces,vertices), v, [], 2 );
faces2 = tri2.ConnectivityList;
vertices2 = tri2.Points;
normals = normals(faces_2To1,:);
else
faces2 = faces;
vertices2 = vertices;
end
% find nearest point on all edges
[d(2),p(2,:),f(2)] = distance_to_edges(faces2,vertices2,point,normals);
% find nearest point on all surfaces
[d(3),p(3,:),f(3)] = distance_to_surfaces(faces2,vertices2,point,normals);
if useSubSurface
% translate back f(2) and f(3)
f(2:3) = faces_2To1(f(2:3));
end
% find minimum distance type
[~,I] = min(abs(d),[],1);
D = d(I);
P = p(I,:);
F = f(I);
end
function [D,P,F,V] = distance_to_vertices(faces,vertices,qPoint,normals)
% find nearest vertex
[D,nearestVertexID] = min(sum(bsxfun(@minus,vertices,qPoint).^2,2),[],1);
D = sqrt(D);
P = vertices(nearestVertexID,:); % (1 x 3)
V = nearestVertexID;
% find faces that belong to the vertex
connectedFaces = find(any(faces==nearestVertexID,2)); % (#connectedFaces x 1) face indices
assert(length(connectedFaces)>=1,'Vertex %u is not connected to any face.',nearestVertexID)
F = connectedFaces(1);
n = normals(connectedFaces,:); % (#connectedFaces x 3) normal vectors
% scalar product between distance vector and normal vectors
coefficients = dot2(n,qPoint-P);
sgn = signOfLargest(coefficients);
D = D*sgn;
end
function [D,P,F] = distance_to_edges(faces,vertices,qPoint,normals)
% Point-point representation of all edges
edges = [faces(:,[1,2]); faces(:,[1,3]); faces(:,[2,3])]; % (#edges x 2) vertice IDs
% Intersection between tangent of edge lines and query point
r1 = vertices(edges(:,1),:); % (#edges x 3) first point of every edge
r2 = vertices(edges(:,2),:); % (#edges x 3) second point of every edge
t = dot( bsxfun(@minus,qPoint,r1), r2-r1, 2) ./ sum((r2-r1).^2,2); % (#edges x 1) location of intersection relative to r1 and r2
t(t<=0) = NaN; % exclude intersections not between the two vertices r1 and r2
t(t>=1) = NaN;
% Distance between intersection and query point
P = r1 + bsxfun(@times,(r2-r1),t); % (#edges x 3) intersection points
D = bsxfun(@minus,qPoint,P); % (#edges x 3)
D = sqrt(sum(D.^2,2)); % (#edges x 1)
[D,I] = min(D,[],1); % (1 x 1)
P = P(I,:);
% find faces that belong to the edge
inds = edges(I,:); % (1 x 2)
inds = permute(inds,[3,1,2]); % (1 x 1 x 2)
inds = bsxfun(@eq,faces,inds); % (#faces x 3 x 2)
inds = any(inds,3); % (#faces x 3)
inds = sum(inds,2)==2; % (#faces x 1) logical indices which faces belong to the nearest edge of the query point
F = find(inds,1);
n = normals(inds,:); % (#connectedFaces x 3) normal vectors
% scalar product between distance vector and normal vectors
coefficients = dot2(n,qPoint-P); % (#connectedFaces x 1)
sgn = signOfLargest(coefficients);
D = D*sgn;
end
function [D,P,F] = distance_to_surfaces(faces,vertices,point,normals)
r1 = vertices(faces(:,1),:); % (#faces x 3) % 1st vertex of every face
r2 = vertices(faces(:,2),:); % (#faces x 3) % 2nd vertex of every face
r3 = vertices(faces(:,3),:); % (#faces x 3) % 3rd vertex of every face
vq = bsxfun(@minus,point,r1); % (#faces x 3)
D = dot(vq,normals,2); % (#faces x 1) distance to surface
rD = bsxfun(@times,normals,D); % (#faces x 3) vector from surface to query point
P = bsxfun(@minus,point,rD); % (#faces x 3) nearest point on surface; can be outside triangle
% find barycentric coordinates (query point as linear combination of two edges)
r31r31 = sum((r3-r1).^2,2); % (#faces x 1)
r21r21 = sum((r2-r1).^2,2); % (#faces x 1)
r21r31 = dot(r2-r1,r3-r1,2); % (#faces x 1)
r31vq = dot(r3-r1,vq,2); % (#faces x 1)
r21vq = dot(r2-r1,vq,2); % (#faces x 1)
d = r31r31.*r21r21 - r21r31.^2; % (#faces x 1)
bary = NaN(size(faces,1),3); % (#faces x 3)
bary(:,1) = (r21r21.*r31vq-r21r31.*r21vq)./d; % (#faces x 3)
bary(:,2) = (r31r31.*r21vq-r21r31.*r31vq)./d; % (#faces x 3)
bary(:,3) = 1 - bary(:,1) - bary(:,2); % (#faces x 3)
% tri = triangulation(faces,vertices);
% bary = tri.cartesianToBarycentric((1:size(faces,1))',P); % (#faces x 3)
% exclude intersections that are outside the triangle
D( abs(d)<=eps | any(bary<=0,2) | any(bary>=1,2) ) = NaN; % (#faces x 1)
% find nearest face for query point
[~,I] = min(abs(D),[],1); % (1 x 1)
D = D(I); % (1 x 1)
P = P(I,:); % (1 x 3)
F = I; % (1 x 1)
end
function [tri2,highlight_faces2,faces_2To1,vertices_2To1] = subSurface( tri, vertex, highlight_faces, iterations )
% enlarge surface by vertex attachments
nFaces = size(tri.ConnectivityList,1);
nVertices = size(tri.Points,1);
faceL = ind2logical([],nFaces);
verticeL = ind2logical([],nVertices);
verticeL(vertex) = true;
for x = 1:iterations
faceIDs = tri.vertexAttachments(find(verticeL))'; %#ok<FNDSB> no array indexing
%faceIDs = cell2mat(faceIDs);
faceIDs = [faceIDs{:}];
faceL(faceIDs) = true;
vertexIDs = tri.ConnectivityList(faceL,:);
verticeL(vertexIDs) = true;
end
% faceL = any(tri.ConnectivityList==vertex,2);
% for x = 1:iterations-1
% verts1 = tri.ConnectivityList(faceL,:);
% faceL = any(ismember(tri.ConnectivityList,verts1),2);
% end
[connectedVertices,~,newIDs] = unique(tri.ConnectivityList(faceL,:));
faces2 = reshape(newIDs,[],3); % (#connectedFaces x 3) new face connectivity list
vertices2 = tri.Points(connectedVertices,:); % (#connectedVertices x 3) new vertice list
tri2 = triangulation(faces2,vertices2);
highlight_faces2 = ind2logical(highlight_faces,nFaces);
highlight_faces2 = highlight_faces2(faceL);
highlight_faces2 = find(highlight_faces2);
faces_2To1 = find(faceL);
vertices_2To1 = connectedVertices;
end
function logical = ind2logical(indices,len)
logical = false(len,1);
logical(indices(~isnan(indices))) = true;
end
%% Vectorized Distance Functions
% (can process more than one point on the same mesh)
function [D,P,F,V] = distance_to_vertices_vectorized(faces,vertices,points,normals)
% Requires Statistics Toolbox
% [D,I] = pdist2(vertices,points, 'euclidean', 'Smallest',1); % (1 x #points)
% D = D'; % (#points x 1)
% I = I'; % (#points x 1)
D = sum(bsxfun(@minus,permute(vertices,[3,2,1]),points).^2,2); % (#points x 1 x #vertices)
[D,I] = min(D,[],3); % (#points x 1)
D = sqrt(D); % (#points x 1)
P = vertices(I,:); % (#points x 3)
V = I;
% find faces that belong to the vertex
inds = I; % (#points x 1)
inds = permute(inds,[3,2,1]); % (1 x 1 x #points)
inds = bsxfun(@eq,faces,inds); % (#faces x 3 x #points)
inds = any(inds,2); % (#faces x 1 x #points) logical indices which faces belong to the nearest edge of a query point
inds = permute(inds,[1,3,2]); % (#faces x #points)
inds = num2cell(inds,1); % (1 x #points) cell array with (#faces x 1) logical indices
n = cellfun(@(x) normals(x,:), inds, 'UniformOutput',false)'; % (#points x 1) cell array with (#connectedFaces x 3) normal vectors
F = cellfun(@(x) find(x,1), inds)'; % (#points x 1)
% scalar product between distance vector and normal vectors
coefficients = cellfun(@dot2, n, num2cell(points-P,2), 'UniformOutput',false);
sgn = cellfun(@signOfLargest,coefficients);
D = D.*sgn;
end
function [D,P,F] = distance_to_edges_vectorized(faces,vertices,points,normals)
euclid = @(A,dim) sqrt(sum(A.^2,dim));
% Point-point representation of all edges
edges = [faces(:,[1,2]); faces(:,[1,3]); faces(:,[2,3])]; % (#edges x 2) vertice IDs
% Intersection between tangent of edge lines and query points
r1 = vertices(edges(:,1),:); % (#edges x 3) first point of every edge
r2 = vertices(edges(:,2),:); % (#edges x 3) second point of every edge
qp = permute(points,[3,2,1]); % (1 x 3 x #points) query points
t = bsxfun(@rdivide,...
dot2(bsxfun(@minus,qp,r1), r2-r1),...
sum((r2-r1).^2,2)); % (#edges x 1 x #points) location of intersection relative to r1 and r2
t(t<=0) = NaN; % exclude intersections not between the two vertices r1 and r2
t(t>=1) = NaN;
% Distance between intersection and query points
P = bsxfun(@plus,...
r1,...
bsxfun(@times,...
(r2-r1),...
t)); % (#edges x 3 x #points) intersection points
D = bsxfun(@minus,qp,P); % (#edges x 3 x #points)
D = euclid(D,2); % (#edges x 1 x #points)
[D,I] = min(D,[],1); % (1 x 1 x #points)
D = squeeze(D); % (#points x 1)
I = squeeze(I); % (#points x 1)
P = permute(P,[2,1,3]); % (3 x #edges x #points)
sz = [size(P) 1 1];
P = P(:,sub2ind(sz(2:3),I,(1:length(I))')); % (3 x #points)
P = P'; % (#points x 3)
% find faces that belong to the edge
inds = edges(I,:); % (#points x 2)
inds = permute(inds,[4,3,1,2]); % (1 x 1 x #points x 2)
inds = bsxfun(@eq,faces,inds); % (#faces x 3 x #points x 2)
inds = any(inds,4); % (#faces x 3 x #points)
inds = sum(inds,2)==2; % (#faces x 1 x #points) logical indices which faces belong to the nearest edge of a query point
inds = permute(inds,[1,3,2]); % (#faces x #points)
inds = num2cell(inds,1); % (1 x #points) cell array with (#faces x 1) logical indices
n = cellfun(@(x) normals(x,:), inds, 'UniformOutput',false)'; % (#points x 1) cell array with (#connectedFaces x 3) normal vectors
F = cellfun(@(x) find(x,1), inds)'; % (#points x 1)
% scalar product between distance vector and normal vectors
coefficients = cellfun(@dot2, n, num2cell(points-P,2), 'UniformOutput',false);
sgn = cellfun(@signOfLargest,coefficients);
D = D.*sgn;
end
function [D,P,F] = distance_to_surfaces_vectorized(faces,vertices,points,normals)
r1 = vertices(faces(:,1),:); % (#faces x 3) % 1st vertex of every face
r2 = vertices(faces(:,2),:); % (#faces x 3) % 2nd vertex of every face
r3 = vertices(faces(:,3),:); % (#faces x 3) % 3rd vertex of every face
qp = permute(points,[3,2,1]); % (1 x 3 x #points) query points
vq = bsxfun(@minus,qp,r1); % (#faces x 3 x #points)
D = dot2(vq,normals); % (#faces x 1 x #points) distance to surface
rD = bsxfun(@times,normals,D); % (#faces x 3 x #points) vector from surface to query point
P = bsxfun(@minus,qp,rD); % (#faces x 3 x #points) nearest point on surface; can be outside triangle
% find barycentric coordinates (query point as linear combination of two edges)
r31r31 = sum((r3-r1).^2,2); % (#faces x 1)
r21r21 = sum((r2-r1).^2,2); % (#faces x 1)
r21r31 = dot(r2-r1,r3-r1,2); % (#faces x 1)
r31vq = dot2(r3-r1,vq); % (#faces x 1 x #points)
r21vq = dot2(r2-r1,vq); % (#faces x 1 x #points)
d = r31r31.*r21r21 - r21r31.^2; % (#faces x 1)
bary = NaN(size(faces,1), 2, size(points,1)); % (#faces x 3 x #points)
bary(:,1,:) = bsxfun(@rdivide, bsxfun(@times,r21r21,r31vq) - bsxfun(@times,r21r31,r21vq), d);
bary(:,2,:) = bsxfun(@rdivide, bsxfun(@times,r31r31,r21vq) - bsxfun(@times,r21r31,r31vq), d);
bary(:,3,:) = 1 - bary(:,1,:) - bary(:,2,:); % (#faces x 3 x #points)
% exclude intersections that are outside the triangle
D( any(bary<=0,2) | any(bary>=1,2) ) = NaN; % (#faces x 1 x #points)
D( abs(d)<=eps, :, : ) = NaN;
% find nearest face for every query point
[~,I] = min(abs(D),[],1); % (1 x 1 x #points)
I = squeeze(I); % (#points x 1)
D = D(sub2ind(size(D),I,ones(length(I),1),(1:length(I))'));
D = squeeze(D); % (#points x 1)
P = permute(P,[2,1,3]); % (3 x #faces x #points)
sz = [size(P) 1 1];
P = P(:,sub2ind(sz(2:3),I,(1:length(I))')); % (3 x #points)
P = P'; % (#points x 3)
F = I; % (#points x 1)
end
function sgn = signOfLargest(coeff)
[~,I] = max(abs(coeff));
sgn = sign(coeff(I));
if sgn==0, sgn=1; end
end
function d = dot2(A,B)
% dot product along 2nd dimension with singleton extension
d = sum(bsxfun(@times,A,B),2);
end
%% Insert Vertex into Triangulation
function [ tri2, is_new_face, is_new_vertex, new_vertex_ID ] = insert_vertex( tri, new_vertex, nearest_face, max_distance, is_new_face, is_new_vertex)
tri2 = tri;
% Adjacent triangles that have a dihedral angle that deviates from pi by an
% angle greater than filterangle are preserved (no Delaunay flipping is performed)
% This conserves the previous triangulation shape.
filterangle = pi/3;
% Do nothing if new_vertex is close to any existing vertex
nearest_face_def = tri2.ConnectivityList(nearest_face,:); % (1 x 3) vertice IDs
nearest_face_vertices = tri2.Points(nearest_face_def,:); % (3 x xyz) vertice coordinates
[distance,I] = min(sqrt(sum(bsxfun(@minus,new_vertex,nearest_face_vertices).^2,2)));
nearest_vertex_ID = nearest_face_def(I);
if distance<=max_distance
new_vertex_ID = nearest_vertex_ID;
return
end
% Three cases:
% - Point is on a boundary edge
% - Point is on an edge inside the mesh
% - Point is inside a triangle (not on an edge)
% To create new faces, the old face is copied and vertice ID are replaced
% to maintain the normal orientation of the face.
% Change to matrix representation to be able to edit the triangulation
faces = tri2.ConnectivityList; % (#faces x 3)
vertices = tri2.Points; % (#vertices x 3)
nVertices = size(tri2.Points,1);
% Distance to edge
[edgeDist,pt] = distance_to_edges(nearest_face_def,vertices,new_vertex,tri2.faceNormal);
if ~isnan(edgeDist) && abs(edgeDist) <= max_distance
% Point is on an edge
bary = tri2.cartesianToBarycentric(nearest_face,pt); % (1 x 3)
[~,I] = sort(bary); % ascending: first entry is the small one
faceDef = faces(nearest_face,:); % (1 x 3)
edge_verts = faceDef(I(2:3)); % (1 x 2) two vertices of edge
edge_faces = tri2.edgeAttachments(edge_verts); % (1 x 1) cell array
edge_faces = edge_faces{1}; % (#edgeFaces x 1) faces at the edge
FE = tri2.featureEdges(filterangle);
if ismember(edge_verts,FE,'rows') || ismember(flip(edge_verts),FE,'rows')
% Otherwise triangles with small angles will be produced
new_vertex = pt;
end
if numel(edge_faces)==1
% Boundary edge
% Add 1 new vertex
vertices = [vertices;new_vertex]; % (#vertices x 3)
is_new_vertex = [is_new_vertex;1]; % (#vertices x 1)
new_vertex_ID = nVertices+1; % scalar integer
% Add 2 new faces
new_faces = [faces(nearest_face,:); faces(nearest_face,:)]; % (2 x 3) copy old face
new_faces(1,I(2)) = new_vertex_ID; % (2 x 3) insert new vertex
new_faces(2,I(3)) = new_vertex_ID; % (2 x 3) insert new vertex
faces = [faces;new_faces]; % (#faces x 3)
is_new_face = [is_new_face;1;1]; % (#faces x 1)
% Remove 1 old face
faces(nearest_face,:) = []; % (#faces x 3)
is_new_face(nearest_face) = []; % (#faces x 1)
% Indices for Delaunay check
nFaces = size(faces,1); % scalar integer
delaunay_check = nFaces-1:nFaces; % (2 x 1) vector
elseif numel(edge_faces)==2
% Two attached faces on edge
% Opposite vertices
edge_faces_def = faces(edge_faces,:); % (2 x 3)
Lia = ismember(edge_faces_def,edge_verts); % logical (2 x 3), true where vertice IDs are on edge
replace_face1 = find(Lia(1,:)); % (1 x 2) position of edge's vertice IDs in edge face 1
replace_face2 = find(Lia(2,:)); % (1 x 2) position of edge's vertice IDs in edge face 2
% Add 1 new vertex
vertices = [vertices;new_vertex]; % (#vertices x 3)
new_vertex_ID = size(vertices,1); % scalar integer
is_new_vertex = [is_new_vertex;1]; % (#vertices x 1)
% Add 4 new faces
new_faces = [ % (4 x 3) copy old faces
edge_faces_def(1,:)
edge_faces_def(1,:)
edge_faces_def(2,:)
edge_faces_def(2,:)
];
new_faces(1,replace_face1(1)) = new_vertex_ID; % (4 x 3) insert new vertex
new_faces(2,replace_face1(2)) = new_vertex_ID;
new_faces(3,replace_face2(1)) = new_vertex_ID;
new_faces(4,replace_face2(2)) = new_vertex_ID;
faces = [faces;new_faces]; % (#faces x 3)
is_new_face = [is_new_face;1;1;1;1]; % (#faces x 1)
% Remove 2 old faces
faces(edge_faces,:) = []; % (#faces x 3)
is_new_face(edge_faces) = []; % (#faces x 1)
% Indices for Delaunay check
nFaces = size(faces,1); % scalar integer
delaunay_check = nFaces-3:nFaces; % (1 x 4) vector
else
error('More than two faces (%s) on an edge. This case is not implemented yet.',mat2str(edge_faces))
end
else
% Point is not on an edge
% Add 1 new vertex
vertices = [vertices;new_vertex]; % (#vertices x 3)
is_new_vertex = [is_new_vertex;1]; % (#vertices x 1)
new_vertex_ID = nVertices+1; % scalar integer
% Add 3 new faces
nearest_face_def = faces(nearest_face,:); % (1 x 3)
new_faces = repmat(nearest_face_def,3,1); % (3 x 3) copy old face
new_faces(1,1) = new_vertex_ID; % insert new vertex ID
new_faces(2,2) = new_vertex_ID;
new_faces(3,3) = new_vertex_ID;
faces = [faces;new_faces]; % (#faces x 3)
is_new_face = [is_new_face;1;1;1]; % (#faces x 1)
% Remove 1 old face
faces(nearest_face,:) = []; % (#faces x 1)
is_new_face(nearest_face) = []; % (#faces x 1)
% Indices for Delaunay check
nFaces = size(faces,1); % scalar
delaunay_check = nFaces-2:nFaces; % (1 x 3) vector
end
% Change to triangulation class representation again
tri2 = triangulation(faces,vertices);
% Restore Delaunay conditions
for k = delaunay_check
[tri2, changed_faces] = makeDelaunay(tri2, k, filterangle);
is_new_face = is_new_face | changed_faces; % (#faces x 1)
end
end
%% Restore Delaunay Conditions
function [tri, changed_faces] = makeDelaunay( tri, face, filterangle )
% Ensures that the Delaunay condition around a given face is met.
% If an edge has to be flipped, the resulting changed faces are checked, too.
% TODO Currently, more Delaunay checks than necessary are performed
changed_faces = false(size(tri.ConnectivityList,1),1); % (#faces x 1) logical vector which faces that have been flipped
unchecked_faces = false(size(tri.ConnectivityList,1),1); % (#faces x 1) logical vector which faces still have to be checked
unchecked_faces(face) = true;
while any(unchecked_faces)
current_face = find(unchecked_faces,1);
unchecked_faces(current_face) = false;
[tri, changed_faces2] = makeDelaunay_neighbors( tri, current_face, filterangle );
unchecked_faces = unchecked_faces | changed_faces2;
changed_faces = changed_faces | changed_faces2;
end
end
function [tri, changed_faces] = makeDelaunay_neighbors( tri, face, filterangle )
% Ensures that the Delaunay condition between the given face and its neighbors is met
% After one flipping is performed, the corresponding faces are marked
% unchecked and the function returns.
% For every neighbor face, check Delaunay condition and flip edge if necessary
changed_faces = false(size(tri.ConnectivityList,1),1); % (#faces x 1) logical vector which faces that have been changed
% Find neighbor faces and their vertices
attached_faces = tri.neighbors(face)'; % (3 x 1) neighbor face IDs
attached_faces(isnan(attached_faces)) = []; % (#attached_faces x 1) face IDs
attached_vertice_IDs = tri.ConnectivityList(attached_faces,:); % (#attached_faces x 3) vertice IDs
own_vertice_IDs = tri.ConnectivityList(face,:); % (1 x 3) vertice IDs
lia = ismember(attached_vertice_IDs,own_vertice_IDs); % (#attached_faces x 3) logical index
attached_vertice_IDs_T = attached_vertice_IDs'; % (3 x #attached_faces) vertice IDs
opposite_vertice_IDs = attached_vertice_IDs_T(~lia'); % (#attached_faces x 1) vertice IDs
for neighborID = 1:size(attached_faces,1)
face1 = face;
face2 = attached_faces(neighborID);
faces = [face1;face2]; % (2 x 1) face IDs
vertex1 = opposite_vertice_IDs(neighborID);
vertex2 = own_vertice_IDs(~ismember(own_vertice_IDs,attached_vertice_IDs(neighborID,:)));
vertices = [vertex1;vertex2]; % (2 x 1) vertice IDs
assert(length(vertices)==2)
if ~isDelaunay( tri, faces, vertices )
% Where sharp/acute/small angles occur, there is no Delaunay possible
% TODO if points to insert are outside the surface, the feature
% edges should be detected before any change of the mesh is performed
% (This is not the case here, since we use only surface projection points)
FE = tri.featureEdges(filterangle);
edge = intersect(tri.ConnectivityList(faces(1),:),tri.ConnectivityList(faces(2),:));
sharpEdge = ismember(edge,FE,'rows') || ismember(flip(edge),FE,'rows');
% don't flip if other neighbors have more than one common vertex to flip partner
neighbors = tri.neighbors(faces(1));
neighbors(isnan(neighbors)) = [];
neighbors(neighbors==faces(2)) = []; % neighbors of faces(1), without flip partner faces(2)
nNeighbors = arrayfun(@(neighbor) length(intersect(tri.ConnectivityList(neighbor,:),tri.ConnectivityList(faces(2),:))), neighbors);
assert(~any(nNeighbors==0))
commonVertices = any(nNeighbors>1);
if ~sharpEdge && ~commonVertices
tri2 = flipFaces( tri, faces, vertices );
% Check if minimum angle increased by the flip
if minimumAngle(tri2,faces)>minimumAngle(tri,faces)
tri = tri2;
changed_faces(faces) = true;
return
end
end
end
end
end
function [minAng,face, sortAngles,sortFaces] = minimumAngle( tri, faces )
facesDef = tri.ConnectivityList(faces,:); % (#faces x 3)
facesCoord = tri.Points(facesDef(:),:); % (#faces*3 x xyz)
facesCoord = reshape(facesCoord,[],3,3); % (#faces x 3 x xyz)
ind = nchoosek(1:3,2); % (3 x 2)
sideLength = facesCoord(:,ind(:,1),:)-facesCoord(:,ind(:,2),:); % (#faces x 3 x xyz)
sideLength = sqrt(sum(sideLength.^2,3)); % (#faces x 3)
[~,r] = tri.circumcenter(faces); % (#faces x 1)
sinAngle = bsxfun(@rdivide,sideLength,2*r); % (#faces x 3)
angle = asin(sinAngle); % (#faces x 3)
minAngles = min(angle,[],2); % (#faces x 1)
[minAng,face] = min(minAngles); % scalars
[sortAngles,sortFaces] = sort(minAngles); % (#faces x 1)
assert(minAng==sortAngles(1))
assert(face==sortFaces(1))
end
function isDelaunay = isDelaunay( tri, faces, vertices )
% Checks if two faces (that share an edge) meet the Delaunay condition
% - tri: triangulation object
% - faces: (2 x 1) face IDs
% - vertices: (2 x 1) vertice IDs of opposite vertices
[CC,r] = tri.circumcenter(faces); % CC: (2 x xyz) centers; r: (2 x 1) radius
distance = sqrt(sum((tri.Points(vertices,:)-CC).^2,2)); % (2 x 1) distances
isDelaunay = all( distance > r+eps );
end
function tri2 = flipFaces( tri, faces, vertices )
tri2 = tri;
% Change definition of two faces in the ConnectivityList
face1_verts = tri2.ConnectivityList(faces(1),:);
face2_verts = tri2.ConnectivityList(faces(2),:);
chgVerts = face1_verts(face1_verts~=vertices(2));
face1_verts(face1_verts==chgVerts(1)) = vertices(1);
face2_verts(face2_verts==chgVerts(2)) = vertices(2);
% Create updated triangulation object
ConnectivityList = tri2.ConnectivityList;
ConnectivityList(faces(1),:) = face1_verts;
ConnectivityList(faces(2),:) = face2_verts;
tri2 = triangulation(ConnectivityList,tri2.Points);
end