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~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~