Code covered by the BSD License  

Highlights from
"boundary" class v2.1: a wrapper for surface objects

image thumbnail
from "boundary" class v2.1: a wrapper for surface objects by Kenneth Eaton
Adds functionality for fast detection of intersections between line segments and rendered surfaces

boundary(hObject,varargin)
function boundaryObject = boundary(hObject,varargin)
%BOUNDARY   Creates a boundary object wrapper for surface objects.
%   OBJ = BOUNDARY(HAXES,'PropertyName',PropertyValue,...) creates a
%   wrapper for surface objects that adds functionality for fast detection
%   of intersections between a line segment and the triangular facets of
%   the rendered surface object. BOUNDARY is designed to be used in place
%   of calls to SURFACE, and OBJ is an object with reference behavior
%   designed to mimic a surface object handle (although it cannot be
%   returned by FINDOBJ). HAXES should be a valid axes handle, followed by
%   parameter/value pairs (or any valid format used by the SET command).
%   This input list must contain at a minimum one of the following sets of
%   parameters:
%
%   -'XData', 'YData', and 'ZData': This will add a surface object to axes
%    HAXES. Any other properties specified should be valid surface object
%    properties.
%   -'Faces' and 'Vertices': This will add a patch object to axes HAXES.
%    The 'Faces' property must be N-by-3 (i.e. triangular patches). Any
%    other properties specified should be valid patch object properties.
%
%   OBJ = BOUNDARY(HOBJECT,...) creates a boundary object given the handle
%   HOBJECT of a valid surface or patch object. As above, the 'Faces'
%   property of the patch object must be N-by-3 (i.e. triangular patches).
%   HOBJECT can be followed by additional parameter/value pairs.
%
%   Objects of class boundary have an additional overloaded method:
%   INTERSECT. This method computes the [x y z] coordinates of the point
%   where a user-defined line segment intersects the rendered surface.
%   Type 'help boundary/intersect' for complete information about the
%   INTERSECT method.
%
%   The object properties 'Faces' and 'Vertices', which are normal
%   properties for patch objects, can be accessed using the GET command for
%   boundary objects, even if they were created from surface objects.
%   Objects of class boundary also have three additional properties that
%   control their behavior:
%
%   1) 'AutoUpdate': To compute the intersection of a line segment and the
%      rendered surface, an equivalent triangular surface representation is
%      computed to represent the triangular facets of the graphics created
%      by the SURFACE command. In addition, a number of variables are
%      precomputed and stored to increase the speed of computation of the
%      INTERSECT algorithm. When 'AutoUpdate' is set to 'on' (the default),
%      all of these precomputed variables are recomputed whenever the
%      surface graphics are modified (i.e., when the 'XData', 'YData', or
%      'ZData' properties are set). When 'AutoUpdate' is set to 'off', this
%      automatic updating is disabled. This may be necessary for faster
%      rendering when changes to the surface are being animated. When
%      'AutoUpdate' is set to 'pulse', the precomputed variables are
%      recomputed once and then 'AutoUpdate' is reset to 'off'.
%
%   2) 'Alignment': When MATLAB plots a surface the orientation of the
%      triangular facets of that surface depend on the values of the
%      'Renderer' and 'RendererMode' properties of the figure. When
%      'Renderer' is set to 'painters' or 'zbuffer', then pattern to the
%      left is used:
%
%        'diagonal'      |                 'anti-diagonal'
%                        |
%        +---+---+       |                    +---+---+
%        |  /|  /|       |                    |\  |\  |
%        | / | / |       |                    | \ | \ |
%        |/  |/  |       |                    |  \|  \|
%      y +---+---+       |                  y +---+---+
%        |  /|  /|       |                    |\  |\  |
%        | / | / |       |                    | \ | \ |
%        |/  |/  |       |                    |  \|  \|
%        +---+---+       |                    +---+---+
%            x           |                        x
%                        |
%         painters       |                      OpenGL
%         zbuffer        |   min(N,M) >= 2 + floor(150 / (max(N,M) - 1))
%
%      This is the 'diagonal' alignment, with each triangles hypotenuse
%      oriented along the line y = x. When 'Renderer' is set to 'OpenGL',
%      the 'anti-diagonal' alignment on the right is used, with each
%      triangles hypotenuse oriented along the line y = -x. When
%      'RendererMode' is set to 'auto', MATLAB will choose a renderer based
%      on (among other things) the dimensions of the surface being
%      rendered. For an N-by-M surface, the pattern on the right (OpenGL
%      rendering) will be used when the equation below it is satisfied.
%
%      Since MATLAB also uses a number of other factors to choose the
%      renderer (such as the presence of transparent objects), then the
%      automatic selection of the 'Alignment' property for boundary objects
%      (which controls how the equivalent triangular surface representation
%      is constructed) may not match the actual alignment of the rendered
%      surface. The best course of action is to explicitly set the figures
%      'Renderer' property to whatever you want it to be and THEN create
%      your boundary objects. This will reduce the chance of a mismatch
%      between the rendered surface and the equivalent triangular
%      representation.
%
%      The above patterns have been tested for MATLAB Version 7.1.0.246 on
%      a PC, and it is uncertain if they are applicable across all versions
%      and platforms. Therefore, the user is allowed to set the 'Alignment'
%      property themselves to override the default triangle alignment in
%      situations when it appears to be in error, or in situations when the
%      renderer is changed after the boundary object is created.
%      'Alignment' can be set to 'diagonal' (the default) or
%      'anti-diagonal'. This will not change the renderer graphics; it will
%      only change the equivalent triangular surface representation so that
%      it might better match the rendered one. The 'Alignment' property is
%      only valid for boundary objects created as wrappers for surface
%      objects; it has no effect on patch objects. Changes to 'Alignment'
%      won't take effect if 'AutoUpdate' is set to 'off'. See example #1
%      below for how to check whether the equivalent triangular
%      representation matches the rendered surface.
%
%   3) 'IndexFcn': By default, the INTERSECT algorithm will check all
%      triangular facets of the surface for an intersection with a line
%      segment. For a speed improvement, the 'IndexFcn' property can be set
%      to the handle of a user-supplied function that returns a subset of
%      triangular facets with which to start the computation. 'IndexFcn'
%      should accept as an input a 2-by-3 matrix (where each row is the
%      [x y z] coordinate of an end point of the line segment) and should
%      return as an output a set of linear indices for the subset of
%      triangular facets on which INTERSECT should operate.
%
%      The indexing scheme for triangular facets is illustrated below for a
%      4-by-4 surface (thus using a default 'diagonal' alignment):
%
%                           +------+------+------+
%                           |     /|     /|     /|
%                           | 3  / | 6  / | 9  / |
%                           |   /  |   /  |   /  |
%                           |  /   |  /   |  /   |
%                           | / 12 | / 15 | / 18 |
%                           |/     |/     |/     |
%                           +------+------+------+
%                           |     /|     /|     /|
%                 ^         | 2  / | 5  / | 8  / |
%                 |         |   /  |   /  |   /  |
%        y-axis (rows of Z) |  /   |  /   |  /   |
%                           | / 11 | / 14 | / 17 |
%                           |/     |/     |/     |
%                           +------+------+------+
%                           |     /|     /|     /|
%                           | 1  / | 4  / | 7  / |
%                           |   /  |   /  |   /  |
%                           |  /   |  /   |  /   |
%                           | / 10 | / 13 | / 16 |
%                           |/     |/     |/     |
%                           +------+------+------+
%
%                          x-axis (columns of Z) ->
%
%      Note that the triangular facets abutting the left side of each
%      square in the grid are indexed first in a column-wise fashion,
%      followed by the triangular facets abutting the right side. An N-by-M
%      surface will thus be rendered with 2*(N-1)*(M-1) triangular facets.
%      For an 'anti-diagonal' alignment (i.e. OpenGL rendering) the facets
%      abutting the left side of each square are still indexed first in a
%      column-wise fashion, followed by the facets abutting the right side.
%
%   Example #1: Display the equivalent triangular surface representation of
%               a rendered surface.
%
%      [X,Y] = meshgrid(1:10);
%      Z = peaks(10);
%      hSurface = boundary(gca,'XData',X,'YData',Y,'ZData',Z,...
%                          'EdgeColor','none');
%      triFaces = get(hSurface,'Faces');
%      triVertices = get(hSurface,'Vertices');
%      hold on;
%      hMesh = patch('Faces',triFaces,'Vertices',triVertices,...
%                    'FaceColor','none');
%      NOTE: The mesh lines should lay flat along the surface; if not, the
%            default 'Alignment' selection is in error and should be
%            changed manually.
%
%   Example #2: Detect the intersection between a line and a surface.
%
%      [X,Y] = meshgrid(1:49);
%      Z = peaks;
%      hSurface = boundary(gca,'XData',X,'YData',Y,'ZData',Z);
%      hold on;
%      axis equal;
%      vector = [10.*rand(2,2)+20 [-10; 10]];
%      pointXYZ = intersect(hSurface,vector);
%      line(vector(:,1),vector(:,2),vector(:,3),'Color','r');
%      plot3(pointXYZ(1),pointXYZ(2),pointXYZ(3),'r*');
%
%   Example #3: Interpolate a curve along a rendered surface (compared to
%               interp2 results).
%
%      [X,Y] = meshgrid(1:3);
%      Z = [0 0 0; 0 1 0; 0 0 0];
%      hSurface = surface(X,Y,Z);
%      hSurface = boundary(hSurface);   % Example of converting a handle
%      hold on;
%      axis equal;
%      x = (1:0.1:3).';
%      y = 4-x;
%      pointArray = intersect(hSurface,[x y 2.*ones(size(x))],[0 0 -3]);
%      pointArray = vertcat(pointArray{:});
%      z = pointArray(:,3);
%      line(x,y,z,'Color','r');   % INTERSECT-derived curve
%      line(x,y,interp2(X,Y,Z,x,y),'Color','g');   % INTERP2-derived curve
%      view(-39,13);
%
%   See also boundary/delete, boundary/get, boundary/intersect,
%            boundary/ishandle, boundary/set

% Author: Ken Eaton
% Last modified: 11/25/08
%--------------------------------------------------------------------------

  % Initializations:

  nInputs = nargin;
  TOLERANCE = eps;
  hBoundary = -1;
  isSurface = true;
  triFaces = [];
  triVertices = [];
  triVertex = [];
  triLegLeft = [];
  triLegRight = [];
  triValid = [];
  allValid = [];
  triNormal = [];
  triBoundary = [];
  DPT = [];
  C1 = [];
  C2 = [];
  C3 = [];
  DEN = [];
  alignment = '';
  autoUpdate = 'on';
  indexFcn = {};
  handlerInput = {};
  returnOutput = false;
  handlerOutput = {};

  % Parse input argument list:

  try

    switch nInputs,

      %====================================================================
      case 0,  % No input; return empty boundary object

        boundaryObject = struct('handler',@handler);
        boundaryObject = class(boundaryObject,'boundary');
        return;

      %====================================================================
      case 1,  % Input should be a boundary, surface, or patch object

        boundaryObject = [];
        check_handle_object_input;
        if isempty(boundaryObject),
          compute_faces_vertices;
          update_precomputed;
          boundaryObject = struct('handler',@handler);
          boundaryObject = class(boundaryObject,'boundary');
        end

      %====================================================================
      otherwise,  % Inputs should be a handle object followed by p/v pairs

        % Check inputs:

        check_handle_object_input;
        [propertyArray,valueArray] = format_param_list(varargin);
        propertyArray = lower(propertyArray);

        % Set boundary-specific properties:

        clearIndex = set_boundary_properties(propertyArray,valueArray);
        propertyArray(clearIndex) = [];
        valueArray(clearIndex) = [];
        if strcmp(autoUpdate,'pulse'),
          autoUpdate = 'off';
        end

        % Plot boundary surface (if necessary) and create boundary object:

        if ishandle(hBoundary),
          set(hBoundary,propertyArray,valueArray);
          compute_faces_vertices;
        else
          initialize_boundary(propertyArray,valueArray);
        end
        update_precomputed;
        boundaryObject = struct('handler',@handler);
        boundaryObject = class(boundaryObject,'boundary');

    end

  catch

    rethrow(mask_last_error('boundary'));

  end

%~~~Begin nested functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  %------------------------------------------------------------------------
  function check_handle_object_input
  %
  %   Function for checking if the handle object input is valid.
  %
  %------------------------------------------------------------------------

    switch class(hObject),

      %====================================================================
      case 'boundary',  % Input object is of class 'boundary'; it should be
                        %   the only input

        if (nInputs > 1),
          error('boundary:invalidHandle',...
                ['Handle argument must be a valid axes, surface, or ' ...
                 'patch object.']);
        else
          boundaryObject = hObject;
          return;
        end

      %====================================================================
      case 'double',  % Input object is of class 'double'; it must be
                      %   checked to see if it is a valid handle object

        if ishandle(hObject),
          handleType = get(hObject,'Type');
        else
          handleType = 'neither';
        end
        switch handleType,
          case 'axes',  % Input object is an axes handle
            if (nInputs == 1),
              error('boundary:invalidInput',...
                    ['Single input argument must be a surface or ' ...
                     'patch object.']);
            end
          case 'surface',  % Input object is a surface handle
            hBoundary = hObject;
          case 'patch',  % Input object is a patch handle
            isSurface = false;
            hBoundary = hObject;
            if (size(get(hBoundary,'Faces'),2) ~= 3),
              error('boundary:badPropertySize',...
                    ['''Faces'' property of patch object should be an ' ...
                     'N-by-3 matrix.']);
            end
          otherwise,  % Input object is invalid
            error('boundary:invalidHandle',...
                  ['Handle argument must be a valid axes, surface, or ' ...
                   'patch object.']);
        end

      %====================================================================
      otherwise,  % Input object is of a class that cannot be converted

        error('boundary:conversionError',...
              ['Conversion from ',class(hObject),' to boundary is not ',...
               'supported.']);

    end

  end

  %------------------------------------------------------------------------
  function compute_alignment(nRows,nCols)
  %
  %   Computes the default 'Alignment' property.
  %
  %------------------------------------------------------------------------

    if isempty(alignment),
      hParent = hObject;
      while (~strcmp(get(hParent,'Type'),'figure')),
        hParent = get(hParent,'Parent');
      end
      isOpenGL = strcmp(get(hParent,'Renderer'),'OpenGL');
      if strcmp(get(hParent,'RendererMode'),'auto'),
        if (isOpenGL || ...
            (min(nRows,nCols) >= 2+floor(150/(max(nRows,nCols)-1)))),
          alignment = 'anti-diagonal';
        else
          alignment = 'diagonal';
        end
      elseif isOpenGL,
        alignment = 'anti-diagonal';
      else
        alignment = 'diagonal';
      end
    end

  end

  %------------------------------------------------------------------------
  function compute_faces_vertices
  %
  %   Computes the face/vertex data for the triangular boundary surface.
  %
  %------------------------------------------------------------------------

    if isSurface,
      valueArray = get(hBoundary,{'XData' 'YData' 'ZData'});
      [nRows,nCols] = size(valueArray{1});
      compute_alignment(nRows,nCols);
      triFaces = surface_faces(nRows,nCols,alignment);
      triVertices = reshape([valueArray{:}],nRows*nCols,3);
    else
      triFaces = get(hBoundary,'Faces');
      triVertices = get(hBoundary,'Vertices');
    end

  end

  %------------------------------------------------------------------------
  function initialize_boundary(propertyArray,valueArray)
  %
  %   Initializes the boundary surface.
  %
  %------------------------------------------------------------------------

    [isFound,index] = ismember({'xdata' 'ydata' 'zdata'},propertyArray);
    if all(isFound),

      % Create a surface boundary object:

      nDims = cellfun('ndims',valueArray(index));
      nRows = cellfun('size',valueArray(index),1);
      nCols = cellfun('size',valueArray(index),2);
      if (any(nDims > 2) || any(nRows == 1) || any(nCols == 1) || ...
          any(nRows ~= nRows(1)) || any(nCols ~= nCols(1))),
        error('boundary:badPropertySize',...
              ['''X/Y/ZData'' properties should all be N-by-M ' ...
               'matrices, where N and M are greater than 1.']);
      end
      compute_alignment(nRows(1),nCols(1));
      triFaces = surface_faces(nRows(1),nCols(1),alignment);
      triVertices = reshape([valueArray{index}],nRows(1)*nCols(1),3);
      plotFcn = @surface;
      plotData = valueArray(index);
      propertyArray(index) = [];
      valueArray(index) = [];

    else

      % Create a patch boundary object:

      [isFound,index] = ismember({'faces' 'vertices'},propertyArray);
      if all(isFound),
        if (size(valueArray{index(1)},2) ~= 3),
          error('boundary:badPropertySize',...
                '''Faces'' property should be an N-by-3 matrix.');
        end
        isSurface = false;
        alignment = 'diagonal';
        triFaces = valueArray{index(1)};
        triVertices = valueArray{index(2)};
        plotFcn = @patch;
        plotData = {};
      else
        error('boundary:missingProperties',...
              ['Input argument list must contain parameter/value ' ...
               'pairs for defining surface objects (''XData'', ' ...
               '''YData'', and ''ZData'') or patch objects (''Faces'' ' ...
               'and ''Vertices'').']);
      end

    end
    hBoundary = plotFcn(plotData{:},'Parent',hObject,propertyArray,...
                        valueArray);

  end

  %------------------------------------------------------------------------
  function index = set_boundary_properties(propertyArray,valueArray)
  %
  %   Function for setting boundary-specific properties.
  %
  %------------------------------------------------------------------------

    [isFound,index] = ismember({'alignment' 'autoupdate' 'indexfcn'},...
                               propertyArray);
    if isFound(1),
      set_alignment(valueArray{index(1)});
    end
    if isFound(2),
      set_autoupdate(valueArray{index(2)});
    end
    if isFound(3),
      set_indexfcn(valueArray{index(3)});
    end
    index = index(isFound);

    %----------------------------------------------------------------------
    function set_alignment(value)
    %
    %   Function for checking and setting the 'Alignment' property.
    %
    %----------------------------------------------------------------------

      if (~ischar(value)),
        error('boundary:badPropertyType',...
              '''Alignment'' property should be a string.');
      end
      value = lower(value);
      if (~any(strcmp(value,{'diagonal' 'anti-diagonal'}))),
        error('boundary:badPropertyValue',...
              'Bad value for boundary object property: ''Alignment''.');
      end
      alignment = value;

    end

    %----------------------------------------------------------------------
    function set_autoupdate(value)
    %
    %   Function for checking and setting the 'AutoUpdate' property.
    %
    %----------------------------------------------------------------------

      if (~ischar(value)),
        error('boundary:badPropertyType',...
              '''AutoUpdate'' property should be a string.');
      end
      value = lower(value);
      if (~any(strcmp(value,{'on' 'off' 'pulse'}))),
        error('boundary:badPropertyValue',...
              'Bad value for boundary object property: ''AutoUpdate''.');
      end
      autoUpdate = value;

    end

    %----------------------------------------------------------------------
    function set_indexfcn(value)
    %
    %   Function for checking and setting the 'IndexFcn' property.
    %
    %----------------------------------------------------------------------

      if (~isa(value,'function_handle')),
        error('boundary:badPropertyType',...
              '''IndexFcn'' property should be a function_handle.');
      end
      indexFcn = value;

    end

  end

  %------------------------------------------------------------------------
  function update_precomputed
  %
  %   Updates the precomputed data for the triangular boundary surface.
  %
  %------------------------------------------------------------------------

    triVertex = triVertices(triFaces(:,2),:);
    triLegLeft = triVertices(triFaces(:,1),:)-triVertex;
    triLegRight = triVertices(triFaces(:,3),:)-triVertex;
    triValid = any((abs(triLegLeft-triLegRight) > realmin),2) & ...
               any((abs(triLegLeft) > realmin),2) & ...
               any((abs(triLegRight) > realmin),2);
    allValid = all(triValid);
    triNormal = cross(triLegLeft,triLegRight);
    triNormal(triValid,:) = triNormal(triValid,:)./...
                            (sqrt(sum(triNormal(triValid,:).^2,2))*...
                             ones(1,3));
    triBoundary = max(sum(triLegLeft.^2,2),sum(triLegRight.^2,2));
    triBoundary = triBoundary+eps(triBoundary);
    DPT = sum(triNormal.*triVertex,2);
    C1 = sum(triLegLeft.*triLegRight,2);
    C2 = sum(triLegLeft.*triLegLeft,2);
    C3 = sum(triLegRight.*triLegRight,2);
    DEN = C1.^2-C2.*C3;
    C1(triValid) = C1(triValid)./DEN(triValid);
    C2(triValid) = C2(triValid)./DEN(triValid);
    C3(triValid) = C3(triValid)./DEN(triValid);

  end

  %------------------------------------------------------------------------
  function varargout = handler(operation,varargin)
  %
  %   Function for handling boundary object method operations.
  %
  %------------------------------------------------------------------------

    handlerInput = varargin;
    returnOutput = (nargout > 0);
    handlerOutput = {};
    switch operation,
      case 'delete',    delete(hBoundary);
      case 'get',       handler_get;
      case 'intersect', handler_intersect;
      case 'ishandle',  handlerOutput = {ishandle(hBoundary)};
      case 'set',       handler_set;
      otherwise,        error('boundary:invalidOperation',...
                              'Invalid operation for boundary object.');
    end
    varargout = handlerOutput;

  end

  %------------------------------------------------------------------------
  function handler_get
  %
  %   Function for handling boundary object get operation.
  %
  %------------------------------------------------------------------------

    %======================================================================
    if returnOutput,  % Return property values

      propertyArray = handlerInput{1}.propertyArray;
      if isempty(propertyArray),  % Return a structure of values
        handlerOutput = get(hBoundary);
        handlerOutput.Alignment = alignment;
        handlerOutput.AutoUpdate = autoUpdate;
        handlerOutput.IndexFcn = indexFcn;
        handlerOutput = {orderfields(handlerOutput)};
      else  % Return a cell array of values
        handlerOutput = cell(1,numel(propertyArray));
        index1 = strcmpi('Alignment',propertyArray);
        index2 = strcmpi('AutoUpdate',propertyArray);
        index3 = strcmpi('IndexFcn',propertyArray);
        if isSurface,
          index4 = strcmpi('Faces',propertyArray);
          index5 = strcmpi('Vertices',propertyArray);
          index = ~(index1 | index2 | index3 | index4 | index5);
        else
          index4 = [];
          index5 = [];
          index = ~(index1 | index2 | index3);
        end
        handlerOutput(index) = get(hBoundary,propertyArray(index));
        if any(index1),
          handlerOutput(index1) = {alignment};
        end
        if any(index2),
          handlerOutput(index2) = {autoUpdate};
        end
        if any(index3),
          handlerOutput(index3) = {indexFcn};
        end
        if any(index4),
          handlerOutput(index4) = {triFaces};
        end
        if any(index5),
          handlerOutput(index5) = {triVertices};
        end
        handlerOutput = {handlerOutput};
      end

    %======================================================================
    else  % Display property values

      disp(['    Alignment = ' alignment]);
      disp(['    AutoUpdate = ' autoUpdate]);
      if isempty(indexFcn),
        disp('    IndexFcn = ');
      else
        disp('    IndexFcn = [ (1 by 1) function_handle array]');
      end
      get(hBoundary);

    end

  end

  %------------------------------------------------------------------------
  function handler_intersect
  %
  %   Function for handling boundary object intersect operation.
  %
  %------------------------------------------------------------------------

    % Initializations:

    positionArray = handlerInput{1}.position;
    vectorArray = handlerInput{1}.vector;
    option = handlerInput{1}.option;
    nLines = size(positionArray,1);
    lineData = cell(nLines,3);
    lineData(:,2) = {inf};

    % Compute intersections for each line segment:

    oldState = warning('off','MATLAB:divideByZero');
    for iLine = 1:nLines,

      % Initializations:

      position = positionArray(iLine,:);
      vector = vectorArray(iLine,:);
      if all(abs(vector) < realmin),
        continue;
      end
      newPosition = position+vector;

      % Find triangles in planes that intersect the line segment:

      if (allValid && isempty(indexFcn)),  % Check all triangles
        DPOLD = triNormal*(position.');
        DPNEW = triNormal*(newPosition.');
        r = (DPT-DPOLD)./(DPNEW-DPOLD);
        triIndex = find((r > -TOLERANCE) & (r < 1+TOLERANCE));
        r = r(triIndex);
      else  % Check a subset of valid triangles using indexFcn
        if isempty(indexFcn),
          triIndex = find(triValid);
        else
          triIndex = indexFcn([position; newPosition]);
          triIndex = triIndex(triValid(triIndex));
        end
        if isempty(triIndex),
          continue;
        end
        tempNormal = triNormal(triIndex,:);
        DPOLD = tempNormal*(position.');
        DPNEW = tempNormal*(newPosition.');
        r = (DPT(triIndex)-DPOLD)./(DPNEW-DPOLD);
        planeIndex = ((r > -TOLERANCE) & (r < 1+TOLERANCE));
        r = r(planeIndex);
        triIndex = triIndex(planeIndex);
      end
      if isempty(triIndex),
        continue;
      end

      % Find the intersection points with the nearby subset of triangles:

      w = r*newPosition+(1-r)*position-triVertex(triIndex,:);
      nearIndex = (sum(w.^2,2) < triBoundary(triIndex));
      if (~any(nearIndex)),
        continue;
      end
      r = r(nearIndex);
      w = w(nearIndex,:);
      triIndex = triIndex(nearIndex);
      wdotu = sum(w.*triLegLeft(triIndex,:),2);
      wdotv = sum(w.*triLegRight(triIndex,:),2);
      C = C1(triIndex);
      s = C.*wdotv-C3(triIndex).*wdotu;
      t = C.*wdotu-C2(triIndex).*wdotv;
      impactIndex = find((s > -TOLERANCE) & (t > -TOLERANCE) & ...
                         ((s+t) < 1+TOLERANCE));
      if isempty(impactIndex),
        continue;
      end

      % Select intersection point data to output:

      r = r(impactIndex);
      s = s(impactIndex);
      t = t(impactIndex);
      triIndex = triIndex(impactIndex);
      if (numel(r) > 1),
        switch option,
          case '-first',
            [r,minIndex] = min(r);
            s = s(minIndex);
            t = t(minIndex);
            triIndex = triIndex(minIndex);
          case '-last',
            [r,maxIndex] = max(r);
            s = s(maxIndex);
            t = t(maxIndex);
            triIndex = triIndex(maxIndex);
          case '-all',
            s = s*ones(1,3);
            t = t*ones(1,3);
        end
      end
      lineData{iLine,1} = triVertex(triIndex,:)+...
                          s.*triLegLeft(triIndex,:)+...
                          t.*triLegRight(triIndex,:);
      lineData{iLine,2} = r;
      lineData{iLine,3} = triNormal(triIndex,:);

    end
    warning(oldState);

    % Format output:

    if (nLines > 1),
      handlerOutput = {lineData(:,1),lineData(:,2),lineData(:,3)};
    else
      handlerOutput = lineData;
    end

  end

  %------------------------------------------------------------------------
  function handler_set
  %
  %   Function for handling boundary object set operation.
  %
  %------------------------------------------------------------------------

    propertyArray = lower(handlerInput{1}.propertyArray);
    valueArray = handlerInput{1}.valueArray;
    %======================================================================
    if isempty(valueArray),  % Return/display possible property values

      %====================================================================
      if returnOutput,  % Return possible property values

        if isempty(propertyArray),  % Return a structure of values
          handlerOutput = set(hBoundary);
          handlerOutput.Alignment = {{'diagonal'; 'anti-diagonal'}};
          handlerOutput.AutoUpdate = {{'on'; 'off'; 'pulse'}};
          handlerOutput.IndexFcn = {};
          handlerOutput = {orderfields(handlerOutput)};
        else  % Return a cell array of values
          switch propertyArray{1},
            case 'alignment',
              handlerOutput = {'diagonal' 'anti-diagonal'};
            case 'autoupdate',
              handlerOutput = {'on' 'off' 'pulse'};
            case 'indexfcn',
              handlerOutput = {{}};
            otherwise,
              handlerOutput = {set(hBoundary,propertyArray{1})};
          end
        end

      %====================================================================
      else  % Display possible property values on screen

        if isempty(propertyArray),  % Display all values
          objectString = set(hBoundary);
          propertyArray = [fieldnames(objectString); 'Alignment'; ...
                           'AutoUpdate'; 'IndexFcn'];
          objectString = [struct2cell(objectString); ...
                          {{'diagonal' 'anti-diagonal'}}; ...
                          {{'on' 'off' 'pulse'}}; {{}}];
          [propertyArray,index] = sort(propertyArray);
          objectString = objectString(index);
          for iProperty = 1:numel(propertyArray),
            property = propertyArray{iProperty};
            value = objectString{iProperty};
            if isempty(value),
              objectString{iProperty} = ['    ' property];
            else
              value{1} = ['[ {' value{1} '} |'];
              value(2:(end-1)) = strcat({' '},value(2:(end-1)),' |');
              value{end} = [' ' value{end} ' ]'];
              objectString{iProperty} = ['    ' property ': ' value{:}];
            end
          end
          disp(char(objectString));
        else  % Display a single value
          switch propertyArray{1},
            case 'alignment',
              disp('[ {diagonal} | anti-diagonal ]');
            case 'autoupdate',
              disp('[ {on} | off | pulse ]');
            case 'indexfcn',
              disp(['A boundary object''s "IndexFcn" property does ' ...
                    'not have a fixed set of property values.']);
            otherwise,
              set(hBoundary,propertyArray{1});
          end
        end

      end

    %======================================================================
    else  % Set properties

      alignFlag = any(strcmp(propertyArray,'alignment'));
      clearIndex = set_boundary_properties(propertyArray,valueArray);
      propertyArray(clearIndex) = [];
      valueArray(clearIndex) = [];
      set(hBoundary,propertyArray,valueArray);
      flagNames = {'xdata' 'ydata' 'zdata' 'faces' 'vertices'};
      if (strcmp(autoUpdate,'pulse') || (strcmp(autoUpdate,'on') && ...
          any([alignFlag ismember(flagNames,propertyArray)]))),
        if isSurface,
          valueArray = get(hBoundary,{'XData' 'YData' 'ZData'});
          [nRows,nCols] = size(valueArray{1});
          if (alignFlag || ((nRows*nCols) ~= size(triVertices,1))),
            triFaces = surface_faces(nRows,nCols,alignment);
          end
          triVertices = reshape([valueArray{:}],nRows*nCols,3);
        else
          triFaces = get(hBoundary,'Faces');
          triVertices = get(hBoundary,'Vertices');
        end
        update_precomputed;
      end
      if strcmp(autoUpdate,'pulse'),
        autoUpdate = 'off';
      end

    end

  end

%~~~End nested functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

end

%~~~Begin subfunctions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

%--------------------------------------------------------------------------
function faces = surface_faces(nRows,nCols,alignment)
%
%   Computes the triangular faces with which a surface is rendered.
%
%--------------------------------------------------------------------------

  triFaces = reshape(1:(nRows*nCols),nRows,nCols);
  index1 = 1:(nRows-1);
  index2 = 2:nRows;
  index3 = 1:(nCols-1);
  index4 = 2:nCols;
  Q1 = triFaces(index1,index3);
  Q1 = Q1(:);
  Q2 = triFaces(index2,index3);
  Q2 = Q2(:);
  Q3 = triFaces(index1,index4);
  Q3 = Q3(:);
  Q4 = triFaces(index2,index4);
  Q4 = Q4(:);
  if strcmp(alignment,'diagonal'),
    faces = [Q1 Q2 Q4; Q1 Q4 Q3];
  else
    faces = [Q1 Q2 Q3; Q2 Q4 Q3];
  end

end

%~~~End subfunctions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Contact us at files@mathworks.com