You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

221 lines
9.5 KiB

function [intersect, t, u, v, xcoor] = TriangleRayIntersection (...
orig, dir, vert0, vert1, vert2, varargin)
%TRIANGLERAYINTERSECTION Ray/triangle intersection.
% INTERSECT = TriangleRayIntersection(ORIG, DIR, VERT1, VERT2, VERT3)
% calculates ray/triangle intersections using the algorithm proposed
% BY Möller and Trumbore (1997), implemented as highly vectorized
% MATLAB code. The ray starts at ORIG and points toward DIR. The
% triangle is defined by vertix points: VERT1, VERT2, VERT3. All input
% arrays are in Nx3 or 1x3 format, where N is number of triangles or
% rays.
%
% [INTERSECT, T, U, V, XCOOR] = TriangleRayIntersection(...)
% Returns:
% * Intersect - boolean array of length N informing which line and
% triangle pair intersect
% * t - distance from the ray origin to the intersection point in
% units of |dir|. Provided only for line/triangle pair that
% intersect unless 'fullReturn' parameter is true.
% * u,v - barycentric coordinates of the intersection point
% * xcoor - carthesian coordinates of the intersection point
%
% TriangleRayIntersection(...,'param','value','param','value'...) allows
% additional param/value pairs to be used. Allowed parameters:
% * planeType - 'one sided' or 'two sided' (default) - how to treat
% triangles. In 'one sided' version only intersections in single
% direction are counted and intersections with back facing
% tringles are ignored
% * lineType - 'ray' (default), 'line' or 'segment' - how to treat rays:
% - 'line' means infinite (on both sides) line;
% - 'ray' means infinite (on one side) ray comming out of origin;
% - 'segment' means line segment bounded on both sides
% * border - controls border handling:
% - 'normal'(default) border - triangle is exactly as defined.
% Intersections with border points can be easily lost due to
% rounding errors.
% - 'inclusive' border - triangle is marginally larger.
% Intersections with border points are always captured but can
% lead to double counting when working with surfaces.
% - 'exclusive' border - triangle is marginally smaller.
% Intersections with border points are not captured and can
% lead to under-counting when working with surfaces.
% * epsilon - (default = 1e-5) controls border size
% * fullReturn - (default = false) controls returned variables t, u, v,
% and xcoor. By default in order to save time, not all t, u & v are
% calculated, only t, u & v for intersections can be expected.
% fullReturn set to true will force the calculation of them all.
%
% ALGORITHM:
% Function solves
% |t|
% M * |u| = (o-v0)
% |v|
% for [t; u; v] where M = [-d, v1-v0, v2-v0]. u,v are barycentric coordinates
% and t - the distance from the ray origin in |d| units
% ray/triangle intersect if u>=0, v>=0 and u+v<=1
%
% NOTE:
% The algorithm is able to solve several types of problems:
% * many faces / single ray intersection
% * one face / many rays intersection
% * one face / one ray intersection
% * many faces / many rays intersection
% In order to allow that to happen all imput arrays are expected in Nx3
% format, where N is number of vertices or rays. In most cases number of
% vertices is different than number of rays, so one of the imputs will
% have to be cloned to have the right size. Use "repmat(A,size(B,1),1)".
%
% Based on:
% *"Fast, minimum storage ray-triangle intersection". Tomas Möller and
% Ben Trumbore. Journal of Graphics Tools, 2(1):21--28, 1997.
% http://www.graphics.cornell.edu/pubs/1997/MT97.pdf
% * http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/raytri/
% * http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/raytri/raytri.c
%
% Author:
% Jarek Tuszynski (jaroslaw.w.tuszynski@leidos.com)
%
% License: BSD license (http://en.wikipedia.org/wiki/BSD_licenses)
%% Transpose inputs if needed
if (size(orig ,1)==3 && size(orig ,2)~=3), orig =orig' ; end
if (size(dir ,1)==3 && size(dir ,2)~=3), dir =dir' ; end
if (size(vert0,1)==3 && size(vert0,2)~=3), vert0=vert0'; end
if (size(vert1,1)==3 && size(vert1,2)~=3), vert1=vert1'; end
if (size(vert2,1)==3 && size(vert2,2)~=3), vert2=vert2'; end
%% In case of single points clone them to the same size as the rest
N = max([size(orig,1), size(dir,1), size(vert0,1), size(vert1,1), size(vert2,1)]);
if (size(orig ,1)==1 && N>1 && size(orig ,2)==3), orig = repmat(orig , N, 1); end
if (size(dir ,1)==1 && N>1 && size(dir ,2)==3), dir = repmat(dir , N, 1); end
if (size(vert0,1)==1 && N>1 && size(vert0,2)==3), vert0 = repmat(vert0, N, 1); end
if (size(vert1,1)==1 && N>1 && size(vert1,2)==3), vert1 = repmat(vert1, N, 1); end
if (size(vert2,1)==1 && N>1 && size(vert2,2)==3), vert2 = repmat(vert2, N, 1); end
%% Check if all the sizes match
SameSize = (any(size(orig)==size(vert0)) && ...
any(size(orig)==size(vert1)) && ...
any(size(orig)==size(vert2)) && ...
any(size(orig)==size(dir )) );
assert(SameSize && size(orig,2)==3, ...
'All input vectors have to be in Nx3 format.');
%% Read user preferences
eps = 1e-5;
planeType = 'two sided';
lineType = 'ray';
border = 'normal';
fullReturn = false;
nVarargs = length(varargin);
k = 1;
if nVarargs>0 && isstruct(varargin{1})
% This section is provided for backward compability only
options = varargin{1};
if (isfield(options, 'eps' )), eps = options.eps; end
if (isfield(options, 'triangle')), planeType= options.triangle; end
if (isfield(options, 'ray' )), lineType = options.ray; end
if (isfield(options, 'border' )), border = options.border; end
else
while (k<=nVarargs)
assert(ischar(varargin{k}), 'Incorrect input parameters')
switch lower(varargin{k})
case 'eps'
eps = abs(varargin{k+1});
k = k+1;
case 'planetype'
planeType = lower(strtrim(varargin{k+1}));
k = k+1;
case 'border'
border = lower(strtrim(varargin{k+1}));
k = k+1;
case 'linetype'
lineType = lower(strtrim(varargin{k+1}));
k = k+1;
case 'fullreturn'
fullReturn = (double(varargin{k+1})~=0);
k = k+1;
end
k = k+1;
end
end
%% Set up border parameter
switch border
case 'normal'
zero=0.0;
case 'inclusive'
zero=eps;
case 'exclusive'
zero=-eps;
otherwise
error('Border parameter must be either "normal", "inclusive" or "exclusive"')
end
%% initialize default output
intersect = false(size(orig,1),1); % by default there are no intersections
t = inf+zeros(size(orig,1),1); u=t; v=t;
xcoor = nan+zeros(size(orig));
%% Find faces parallel to the ray
edge1 = vert1-vert0; % find vectors for two edges sharing vert0
edge2 = vert2-vert0;
tvec = orig -vert0; % vector from vert0 to ray origin
pvec = cross(dir, edge2,2); % begin calculating determinant - also used to calculate U parameter
det = sum(edge1.*pvec,2); % determinant of the matrix M = dot(edge1,pvec)
switch planeType
case 'two sided' % treats triangles as two sided
angleOK = (abs(det)>eps); % if determinant is near zero then ray lies in the plane of the triangle
case 'one sided' % treats triangles as one sided
angleOK = (det>eps);
otherwise
error('Triangle parameter must be either "one sided" or "two sided"');
end
if all(~angleOK), return; end % if all parallel than no intersections
%% Different behavior depending on one or two sided triangles
det(~angleOK) = nan; % change to avoid division by zero
u = sum(tvec.*pvec,2)./det; % 1st barycentric coordinate
if fullReturn
% calculate all variables for all line/triangle pairs
qvec = cross(tvec, edge1,2); % prepare to test V parameter
v = sum(dir .*qvec,2)./det; % 2nd barycentric coordinate
t = sum(edge2.*qvec,2)./det; % 'position on the line' coordinate
% test if line/plane intersection is within the triangle
ok = (angleOK & u>=-zero & v>=-zero & u+v<=1.0+zero);
else
% limit some calculations only to line/triangle pairs where it makes
% a difference. It is tempting to try to push this concept of
% limiting the number of calculations to only the necessary to "u"
% and "t" but that produces slower code
v = nan+zeros(size(u)); t=v;
ok = (angleOK & u>=-zero & u<=1.0+zero); % mask
% if all line/plane intersections are outside the triangle than no intersections
if ~any(ok), intersect = ok; return; end
qvec = cross(tvec(ok,:), edge1(ok,:),2); % prepare to test V parameter
v(ok,:) = sum(dir(ok,:).*qvec,2) ./ det(ok,:); % 2nd barycentric coordinate
if (~strcmpi(lineType,'line')) % 'position on the line' coordinate
t(ok,:) = sum(edge2(ok,:).*qvec,2)./det(ok,:);
end
% test if line/plane intersection is within the triangle
ok = (ok & v>=-zero & u+v<=1.0+zero);
end
%% Test where along the line the line/plane intersection occurs
switch lineType
case 'line' % infinite line
intersect = ok;
case 'ray' % ray is bound on one side
intersect = (ok & t>=-zero); % intersection on the correct side of the origin
case 'segment' % segment is bound on two sides
intersect = (ok & t>=-zero & t<=1.0+zero); % intersection between origin and destination
otherwise
error('lineType parameter must be either "line", "ray" or "segment"');
end
%% calculate intersection coordinates if requested
if (nargout>4)
ok = intersect | fullReturn;
xcoor(ok,:) = vert0(ok,:) ...
+ edge1(ok,:).*repmat(u(ok,1),1,3) ...
+ edge2(ok,:).*repmat(v(ok,1),1,3);
end