-
Notifications
You must be signed in to change notification settings - Fork 14
/
intriangulation.m
371 lines (299 loc) · 18.7 KB
/
intriangulation.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
function in = intriangulation(vertices,faces,testp,heavytest)
% intriangulation: Test points in 3d wether inside or outside a (closed) triangulation
% usage: in = intriangulation(vertices,faces,testp,heavytest)
%
% arguments: (input)
% vertices - points in 3d as matrix with three columns
%
% faces - description of triangles as matrix with three columns.
% Each row contains three indices into the matrix of vertices
% which gives the three cornerpoints of the triangle.
%
% testp - points in 3d as matrix with three columns
%
% heavytest - int n >= 0. Perform n additional randomized rotation tests.
%
% IMPORTANT: the set of vertices and faces has to form a watertight surface!
%
% arguments: (output)
% in - a vector of length size(testp,1), containing 0 and 1.
% in(nr) = 0: testp(nr,:) is outside the triangulation
% in(nr) = 1: testp(nr,:) is inside the triangulation
% in(nr) = -1: unable to decide for testp(nr,:)
%
% Thanks to Adam A for providing the FEX submission voxelise. The
% algorithms of voxelise form the algorithmic kernel of intriangulation.
%
% Thanks to Sven to discussions about speed and avoiding problems in
% special cases.
%
% Example usage:
%
% n = 10;
% vertices = rand(n, 3)-0.5; % Generate random points
% tetra = delaunayn(vertices); % Generate delaunay triangulization
% faces = freeBoundary(TriRep(tetra,vertices)); % use free boundary as triangulation
% n = 1000;
% testp = 2*rand(n,3)-1; % Generate random testpoints
% in = intriangulation(vertices,faces,testp);
% % Plot results
% h = trisurf(faces,vertices(:,1),vertices(:,2),vertices(:,3));
% set(h,'FaceColor','black','FaceAlpha',1/3,'EdgeColor','none');
% hold on;
% plot3(testp(:,1),testp(:,2),testp(:,3),'b.');
% plot3(testp(in==1,1),testp(in==1,2),testp(in==1,3),'ro');
%
% See also: intetrahedron, tsearchn, inpolygon
%
% Author: Johannes Korsawe, heavily based on voxelise from Adam A.
% E-mail: [email protected]
% Release: 1.3
% Release date: 25/09/2013
% check number of inputs
if nargin<3,
fprintf('??? Error using ==> intriangulation\nThree input matrices are needed.\n');in=[];return;
end
if nargin==3,
heavytest = 0;
end
% check size of inputs
if size(vertices,2)~=3 || size(faces,2)~=3 || size(testp,2)~=3,
fprintf('??? Error using ==> intriagulation\nAll input matrices must have three columns.\n');in=[];return;
end
ipmax = max(faces(:));zerofound = ~isempty(find(faces(:)==0, 1));
if ipmax>size(vertices,1) || zerofound,
fprintf('??? Error using ==> intriangulation\nThe triangulation data is defect. use trisurf(faces,vertices(:,1),vertices(:,2),vertices(:,3)) for test of deficiency.\n');return;
end
% loop for heavytest
inreturn = zeros(size(testp,1),1);VER = vertices;TESTP = testp;
for n = 1:heavytest+1,
% Randomize
if n>1,
v=rand(1,3);D=rotmatrix(v/norm(v),rand*180/pi);vertices=VER*D;testp = TESTP*D;
else,
vertices=VER;
end
% Preprocessing data
meshXYZ = zeros(size(faces,1),3,3);
for loop = 1:3,
meshXYZ(:,:,loop) = vertices(faces(:,loop),:);
end
% Basic idea (ingenious from FeX-submission voxelise):
% If point is inside, it will cross the triangulation an uneven number of times in each direction (x, -x, y, -y, z, -z).
% The function VOXELISEinternal is about 98% identical to its version inside voxelise.m.
% This includes the elaborate comments. Thanks to Adam A!
% z-direction:
% intialization of results and correction list
[in,cl] = VOXELISEinternal(testp(:,1),testp(:,2),testp(:,3),meshXYZ);
% x-direction:
% has only to be done for those points, that were not determinable in the first step --> cl
[in2,cl2] = VOXELISEinternal(testp(cl,2),testp(cl,3),testp(cl,1),meshXYZ(:,[2,3,1],:));
% Use results of x-direction that determined "inside"
in(cl(in2==1)) = 1;
% remaining indices with unclear result
cl = cl(cl2);
% y-direction:
% has only to be done for those points, that were not determinable in the first and second step --> cl
[in3,cl3] = VOXELISEinternal(testp(cl,3),testp(cl,1),testp(cl,2),meshXYZ(:,[3,1,2],:));
% Use results of y-direction that determined "inside"
in(cl(in3==1)) = 1;
% remaining indices with unclear result
cl = cl(cl3);
% mark those indices, where all three tests have failed
in(cl) = -1;
if n==1,
inreturn = in; % Starting guess
else,
% if ALWAYS inside, use as inside!
% I = find(inreturn ~= in);
% inreturn(I(in(I)==0)) = 0;
% if AT LEAST ONCE inside, use as inside!
I = find(inreturn ~= in);
inreturn(I(in(I)==1)) = 1;
end
end
in = inreturn;
end
%==========================================================================
function [OUTPUT,correctionLIST] = VOXELISEinternal(testx,testy,testz,meshXYZ)
% Prepare logical array to hold the logical data:
OUTPUT = false(size(testx,1),1);
%Identify the min and max x,y coordinates of the mesh:
meshZmin = min(min(meshXYZ(:,3,:)));meshZmax = max(max(meshXYZ(:,3,:)));
%Identify the min and max x,y,z coordinates of each facet:
meshXYZmin = min(meshXYZ,[],3);meshXYZmax = max(meshXYZ,[],3);
%======================================================
% TURN OFF DIVIDE-BY-ZERO WARNINGS
%======================================================
%This prevents the Y1predicted, Y2predicted, Y3predicted and YRpredicted
%calculations creating divide-by-zero warnings. Suppressing these warnings
%doesn't affect the code, because only the sign of the result is important.
%That is, 'Inf' and '-Inf' results are ok.
%The warning will be returned to its original state at the end of the code.
warningrestorestate = warning('query', 'MATLAB:divideByZero');
%warning off MATLAB:divideByZero
%======================================================
% START COMPUTATION
%======================================================
correctionLIST = []; %Prepare to record all rays that fail the voxelisation. This array is built on-the-fly, but since
%it ought to be relatively small should not incur too much of a speed penalty.
% Loop through each testpoint.
% The testpoint-array will be tested by passing rays in the z-direction through
% each x,y coordinate of the testpoints, and finding the locations where the rays cross the mesh.
facetCROSSLIST = zeros(1,1e3); % uses countindex: nf
nm = size(meshXYZmin,1);
for loop = 1:length(OUTPUT),
nf = 0;
% % - 1a - Find which mesh facets could possibly be crossed by the ray:
% possibleCROSSLISTy = find( meshXYZmin(:,2)<=testy(loop) & meshXYZmax(:,2)>=testy(loop) );
% % - 1b - Find which mesh facets could possibly be crossed by the ray:
% possibleCROSSLIST = possibleCROSSLISTy( meshXYZmin(possibleCROSSLISTy,1)<=testx(loop) & meshXYZmax(possibleCROSSLISTy,1)>=testx(loop) );
% Do - 1a - and - 1b - faster
possibleCROSSLISTy = find((testy(loop)-meshXYZmin(:,2)).*(meshXYZmax(:,2)-testy(loop))>0);
possibleCROSSLISTx = (testx(loop)-meshXYZmin(possibleCROSSLISTy,1)).*(meshXYZmax(possibleCROSSLISTy,1)-testx(loop))>0;
possibleCROSSLIST = possibleCROSSLISTy(possibleCROSSLISTx);
if isempty(possibleCROSSLIST)==0 %Only continue the analysis if some nearby facets were actually identified
% - 2 - For each facet, check if the ray really does cross the facet rather than just passing it close-by:
% GENERAL METHOD:
% 1. Take each edge of the facet in turn.
% 2. Find the position of the opposing vertex to that edge.
% 3. Find the position of the ray relative to that edge.
% 4. Check if ray is on the same side of the edge as the opposing vertex.
% 5. If this is true for all three edges, then the ray definitely passes through the facet.
%
% NOTES:
% 1. If the ray crosses exactly on an edge, this is counted as crossing the facet.
% 2. If a ray crosses exactly on a vertex, this is also taken into account.
for loopCHECKFACET = possibleCROSSLIST'
%Check if ray crosses the facet. This method is much (>>10 times) faster than using the built-in function 'inpolygon'.
%Taking each edge of the facet in turn, check if the ray is on the same side as the opposing vertex. If so, let testVn=1
Y1predicted = meshXYZ(loopCHECKFACET,2,2) - ((meshXYZ(loopCHECKFACET,2,2)-meshXYZ(loopCHECKFACET,2,3)) * (meshXYZ(loopCHECKFACET,1,2)-meshXYZ(loopCHECKFACET,1,1))/(meshXYZ(loopCHECKFACET,1,2)-meshXYZ(loopCHECKFACET,1,3)));
YRpredicted = meshXYZ(loopCHECKFACET,2,2) - ((meshXYZ(loopCHECKFACET,2,2)-meshXYZ(loopCHECKFACET,2,3)) * (meshXYZ(loopCHECKFACET,1,2)-testx(loop))/(meshXYZ(loopCHECKFACET,1,2)-meshXYZ(loopCHECKFACET,1,3)));
if (Y1predicted > meshXYZ(loopCHECKFACET,2,1) && YRpredicted > testy(loop)) || (Y1predicted < meshXYZ(loopCHECKFACET,2,1) && YRpredicted < testy(loop)) || (meshXYZ(loopCHECKFACET,2,2)-meshXYZ(loopCHECKFACET,2,3)) * (meshXYZ(loopCHECKFACET,1,2)-testx(loop)) == 0
% testV1 = 1; %The ray is on the same side of the 2-3 edge as the 1st vertex.
else
% testV1 = 0; %The ray is on the opposite side of the 2-3 edge to the 1st vertex.
% As the check is for ALL three checks to be true, we can continue here, if only one check fails
continue;
end %if
Y2predicted = meshXYZ(loopCHECKFACET,2,3) - ((meshXYZ(loopCHECKFACET,2,3)-meshXYZ(loopCHECKFACET,2,1)) * (meshXYZ(loopCHECKFACET,1,3)-meshXYZ(loopCHECKFACET,1,2))/(meshXYZ(loopCHECKFACET,1,3)-meshXYZ(loopCHECKFACET,1,1)));
YRpredicted = meshXYZ(loopCHECKFACET,2,3) - ((meshXYZ(loopCHECKFACET,2,3)-meshXYZ(loopCHECKFACET,2,1)) * (meshXYZ(loopCHECKFACET,1,3)-testx(loop))/(meshXYZ(loopCHECKFACET,1,3)-meshXYZ(loopCHECKFACET,1,1)));
if (Y2predicted > meshXYZ(loopCHECKFACET,2,2) && YRpredicted > testy(loop)) || (Y2predicted < meshXYZ(loopCHECKFACET,2,2) && YRpredicted < testy(loop)) || (meshXYZ(loopCHECKFACET,2,3)-meshXYZ(loopCHECKFACET,2,1)) * (meshXYZ(loopCHECKFACET,1,3)-testx(loop)) == 0
% testV2 = 1; %The ray is on the same side of the 3-1 edge as the 2nd vertex.
else
% testV2 = 0; %The ray is on the opposite side of the 3-1 edge to the 2nd vertex.
% As the check is for ALL three checks to be true, we can continue here, if only one check fails
continue;
end %if
Y3predicted = meshXYZ(loopCHECKFACET,2,1) - ((meshXYZ(loopCHECKFACET,2,1)-meshXYZ(loopCHECKFACET,2,2)) * (meshXYZ(loopCHECKFACET,1,1)-meshXYZ(loopCHECKFACET,1,3))/(meshXYZ(loopCHECKFACET,1,1)-meshXYZ(loopCHECKFACET,1,2)));
YRpredicted = meshXYZ(loopCHECKFACET,2,1) - ((meshXYZ(loopCHECKFACET,2,1)-meshXYZ(loopCHECKFACET,2,2)) * (meshXYZ(loopCHECKFACET,1,1)-testx(loop))/(meshXYZ(loopCHECKFACET,1,1)-meshXYZ(loopCHECKFACET,1,2)));
if (Y3predicted > meshXYZ(loopCHECKFACET,2,3) && YRpredicted > testy(loop)) || (Y3predicted < meshXYZ(loopCHECKFACET,2,3) && YRpredicted < testy(loop)) || (meshXYZ(loopCHECKFACET,2,1)-meshXYZ(loopCHECKFACET,2,2)) * (meshXYZ(loopCHECKFACET,1,1)-testx(loop)) == 0
% testV3 = 1; %The ray is on the same side of the 1-2 edge as the 3rd vertex.
else
% testV3 = 0; %The ray is on the opposite side of the 1-2 edge to the 3rd vertex.
% As the check is for ALL three checks to be true, we can continue here, if only one check fails
continue;
end %if
nf=nf+1;facetCROSSLIST(nf)=loopCHECKFACET;
end %for
% Use only values ~=0
facetCROSSLIST = facetCROSSLIST(1:nf);
% - 3 - Find the z coordinate of the locations where the ray crosses each facet:
gridCOzCROSS = zeros(1,nf);
for loopFINDZ = facetCROSSLIST
% METHOD:
% 1. Define the equation describing the plane of the facet. For a
% more detailed outline of the maths, see:
% http://local.wasp.uwa.edu.au/~pbourke/geometry/planeeq/
% Ax + By + Cz + D = 0
% where A = y1 (z2 - z3) + y2 (z3 - z1) + y3 (z1 - z2)
% B = z1 (x2 - x3) + z2 (x3 - x1) + z3 (x1 - x2)
% C = x1 (y2 - y3) + x2 (y3 - y1) + x3 (y1 - y2)
% D = - x1 (y2 z3 - y3 z2) - x2 (y3 z1 - y1 z3) - x3 (y1 z2 - y2 z1)
% 2. For the x and y coordinates of the ray, solve these equations to find the z coordinate in this plane.
planecoA = meshXYZ(loopFINDZ,2,1)*(meshXYZ(loopFINDZ,3,2)-meshXYZ(loopFINDZ,3,3)) + meshXYZ(loopFINDZ,2,2)*(meshXYZ(loopFINDZ,3,3)-meshXYZ(loopFINDZ,3,1)) + meshXYZ(loopFINDZ,2,3)*(meshXYZ(loopFINDZ,3,1)-meshXYZ(loopFINDZ,3,2));
planecoB = meshXYZ(loopFINDZ,3,1)*(meshXYZ(loopFINDZ,1,2)-meshXYZ(loopFINDZ,1,3)) + meshXYZ(loopFINDZ,3,2)*(meshXYZ(loopFINDZ,1,3)-meshXYZ(loopFINDZ,1,1)) + meshXYZ(loopFINDZ,3,3)*(meshXYZ(loopFINDZ,1,1)-meshXYZ(loopFINDZ,1,2));
planecoC = meshXYZ(loopFINDZ,1,1)*(meshXYZ(loopFINDZ,2,2)-meshXYZ(loopFINDZ,2,3)) + meshXYZ(loopFINDZ,1,2)*(meshXYZ(loopFINDZ,2,3)-meshXYZ(loopFINDZ,2,1)) + meshXYZ(loopFINDZ,1,3)*(meshXYZ(loopFINDZ,2,1)-meshXYZ(loopFINDZ,2,2));
planecoD = - meshXYZ(loopFINDZ,1,1)*(meshXYZ(loopFINDZ,2,2)*meshXYZ(loopFINDZ,3,3)-meshXYZ(loopFINDZ,2,3)*meshXYZ(loopFINDZ,3,2)) - meshXYZ(loopFINDZ,1,2)*(meshXYZ(loopFINDZ,2,3)*meshXYZ(loopFINDZ,3,1)-meshXYZ(loopFINDZ,2,1)*meshXYZ(loopFINDZ,3,3)) - meshXYZ(loopFINDZ,1,3)*(meshXYZ(loopFINDZ,2,1)*meshXYZ(loopFINDZ,3,2)-meshXYZ(loopFINDZ,2,2)*meshXYZ(loopFINDZ,3,1));
if abs(planecoC) < 1e-14
planecoC=0;
end
gridCOzCROSS(facetCROSSLIST==loopFINDZ) = (- planecoD - planecoA*testx(loop) - planecoB*testy(loop)) / planecoC;
end %for
if isempty(gridCOzCROSS),continue;end
%Remove values of gridCOzCROSS which are outside of the mesh limits (including a 1e-12 margin for error).
gridCOzCROSS = gridCOzCROSS( gridCOzCROSS>=meshZmin-1e-12 & gridCOzCROSS<=meshZmax+1e-12 );
%Round gridCOzCROSS to remove any rounding errors, and take only the unique values:
gridCOzCROSS = round(gridCOzCROSS*1e10)/1e10;
% Replacement of the call to unique (gridCOzCROSS = unique(gridCOzCROSS);) by the following line:
tmp = sort(gridCOzCROSS);I=[0,tmp(2:end)-tmp(1:end-1)]~=0;gridCOzCROSS = [tmp(1),tmp(I)];
% - 4 - Label as being inside the mesh all the voxels that the ray passes through after crossing one facet before crossing another facet:
if rem(numel(gridCOzCROSS),2)==0 % Only rays which cross an even number of facets are voxelised
for loopASSIGN = 1:(numel(gridCOzCROSS)/2)
voxelsINSIDE = (testz(loop)>gridCOzCROSS(2*loopASSIGN-1) & testz(loop)<gridCOzCROSS(2*loopASSIGN));
OUTPUT(loop) = voxelsINSIDE;
if voxelsINSIDE,break;end
end %for
elseif numel(gridCOzCROSS)~=0 % Remaining rays which meet the mesh in some way are not voxelised, but are labelled for correction later.
correctionLIST = [ correctionLIST; loop ];
end %if
end %if
end %for
%======================================================
% RESTORE DIVIDE-BY-ZERO WARNINGS TO THE ORIGINAL STATE
%======================================================
warning(warningrestorestate)
% J.Korsawe: A correction is not possible as the testpoints need not to be
% ordered in any way.
% voxelise contains a correction algorithm which is appended here
% without changes in syntax.
return
%======================================================
% USE INTERPOLATION TO FILL IN THE RAYS WHICH COULD NOT BE VOXELISED
%======================================================
%For rays where the voxelisation did not give a clear result, the ray is
%computed by interpolating from the surrounding rays.
countCORRECTIONLIST = size(correctionLIST,1);
if countCORRECTIONLIST>0
%If necessary, add a one-pixel border around the x and y edges of the
%array. This prevents an error if the code tries to interpolate a ray at
%the edge of the x,y grid.
if min(correctionLIST(:,1))==1 || max(correctionLIST(:,1))==numel(gridCOx) || min(correctionLIST(:,2))==1 || max(correctionLIST(:,2))==numel(gridCOy)
gridOUTPUT = [zeros(1,voxcountY+2,voxcountZ);zeros(voxcountX,1,voxcountZ),gridOUTPUT,zeros(voxcountX,1,voxcountZ);zeros(1,voxcountY+2,voxcountZ)];
correctionLIST = correctionLIST + 1;
end
for loopC = 1:countCORRECTIONLIST
voxelsforcorrection = squeeze( sum( [ gridOUTPUT(correctionLIST(loopC,1)-1,correctionLIST(loopC,2)-1,:) ,...
gridOUTPUT(correctionLIST(loopC,1)-1,correctionLIST(loopC,2),:) ,...
gridOUTPUT(correctionLIST(loopC,1)-1,correctionLIST(loopC,2)+1,:) ,...
gridOUTPUT(correctionLIST(loopC,1),correctionLIST(loopC,2)-1,:) ,...
gridOUTPUT(correctionLIST(loopC,1),correctionLIST(loopC,2)+1,:) ,...
gridOUTPUT(correctionLIST(loopC,1)+1,correctionLIST(loopC,2)-1,:) ,...
gridOUTPUT(correctionLIST(loopC,1)+1,correctionLIST(loopC,2),:) ,...
gridOUTPUT(correctionLIST(loopC,1)+1,correctionLIST(loopC,2)+1,:) ,...
] ) );
voxelsforcorrection = (voxelsforcorrection>=4);
gridOUTPUT(correctionLIST(loopC,1),correctionLIST(loopC,2),voxelsforcorrection) = 1;
end %for
%Remove the one-pixel border surrounding the array, if this was added
%previously.
if size(gridOUTPUT,1)>numel(gridCOx) || size(gridOUTPUT,2)>numel(gridCOy)
gridOUTPUT = gridOUTPUT(2:end-1,2:end-1,:);
end
end %if
%disp([' Ray tracing result: ',num2str(countCORRECTIONLIST),' rays (',num2str(countCORRECTIONLIST/(voxcountX*voxcountY)*100,'%5.1f'),'% of all rays) exactly crossed a facet edge and had to be computed by interpolation.'])
end %function
%==========================================================================
function D = rotmatrix(v,deg)
% calculate the rotation matrix about v by deg degrees
deg=deg/180*pi;
if deg~=0,
v=v/norm(v);
v1=v(1);v2=v(2);v3=v(3);ca=cos(deg);sa=sin(deg);
D=[ca+v1*v1*(1-ca),v1*v2*(1-ca)-v3*sa,v1*v3*(1-ca)+v2*sa;
v2*v1*(1-ca)+v3*sa,ca+v2*v2*(1-ca),v2*v3*(1-ca)-v1*sa;
v3*v1*(1-ca)-v2*sa,v3*v2*(1-ca)+v1*sa,ca+v3*v3*(1-ca)];
else,
D=eye(3,3);
end
end