Code covered by the BSD License  

Highlights from
MESH2D - Automatic Mesh Generation

image thumbnail

MESH2D - Automatic Mesh Generation

by

 

Generates unstructured triangular meshes for general 2D geometry.

quadtree(node,edge,hdata,dhmax,output)
function [p,t,h] = quadtree(node,edge,hdata,dhmax,output)

%  QUADTREE: 2D quadtree decomposition of polygonal geometry.
%
% The polygon is first rotated so that the minimal enclosing rectangle is
% aligned with the Cartesian XY axes. The long axis is aligned with Y. This
% ensures that the quadtree generated for a geometry input that has
% undergone arbitrary rotations in the XY plane is always the same.
%
% The bounding box is recursively subdivided until the dimension of each 
% box matches the local geometry feature size. The geometry feature size is 
% based on the minimum distance between linear geometry segments.
%
% A size function is obtained at the quadtree vertices based on the minimum
% neighbouring box dimension at each vertex. This size function is gradient
% limited to produce a smooth function.
%
% The quadtree is triangulated to form a background mesh, such that the
% size function may be obtained at any XY position within the domain via
% triangle based linear interpolation. The triangulation is done based on
% the quadtree data structures directly (i.e. NOT using Qhull) which is 
% more reliable and produces a consistently oriented triangulation.
%
% The initial rotations are undone.
%
%  node  : [x1,y1; x2,y2; etc] geometry vertices
%  edge  : [n11,n12; n21,n22; etc] geometry edges as connections in NODE
%  hdata : User defined size function structure
%  dhmax : Maximum allowalble relative gradient in the size function
%  wbar  : Handle to progress bar from MESH2D
%
%   p    : Background mesh nodes
%   t    : Background mesh triangles
%   h    : Size function value at p

%   Darren Engwirda : 2007
%   Email           : d_engwirda@hotmail.com
%   Last updated    : 18/11/2007 with MATLAB 7.0

% Bounding box
XYmax = max(node,[],1);
XYmin = min(node,[],1);

% Rotate NODE so that the long axis of the minimum bounding rectangle is
% aligned with the Y axis.
theta = minrectangle(node);
node = rotate(node,theta);

% Rotated XY edge endpoints
edgexy = [node(edge(:,1),:), node(edge(:,2),:)];

% LOCAL FEATURE SIZE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if output
   fprintf('Estimating local geometry feature size\n');
end

% Get size function data
[hmax,edgeh,fun,args] = gethdata(hdata);

% Insert test points along the boundaries at which the LFS can be
% approximated.
wm = 0.5*(edgexy(:,[1,2])+edgexy(:,[3,4]));                                % Use the edge midpoints as a first pass
len = sqrt(sum((edgexy(:,[3,4])-edgexy(:,[1,2])).^2,2));                   % Edge length
L = 2.0*dist2poly(wm,edgexy,2.0*len);                                      % Estimate the LFS at these points by calculating
                                                                           % the distance to the closest edge segment                           
% In cases where edges are separated by less than their length
% we will need to add more points to capture the LFS in these
% regions. This allows us to pick up "long and skinny" geometry
% features
r = 2.0*len./L;                                                            % Compare L (LFS approximation at wm) to the edge lengths
r = round((r-2.0)/2.0);                                                    % Amount of points that need to be added
add = find(r);                                                             % at each edge
if ~isempty(add)
   num = 2*sum(r(add));                                                    % Total number of points to be added
   start = size(wm,1)+1;
   wm = [wm; zeros(num,2)];                                                % Alloc space
   L = [L; zeros(num,1)];
   next = start;
   for j = 1:length(add)                                                   % Loop through edges to be subdivided
      
      ce = add(j);                                                         % Current edge
      num = r(ce);
      tmp = (1:num)'/(num+1);                                              % Subdivision increments
      num = next+2*num-1;

      x1 = edgexy(ce,1); x2 = edgexy(ce,3); xm = wm(ce,1);                 % Edge values
      y1 = edgexy(ce,2); y2 = edgexy(ce,4); ym = wm(ce,2);

      wm(next:num,:) = [x1+tmp*(xm-x1), y1+tmp*(ym-y1)                     % Add to list
                        xm+tmp*(x2-xm), ym+tmp*(y2-ym)];
      
      L(next:num) = L(ce);                                                 % Upper bound on LFS
                     
      next = num+1;
   
   end
   L(start:end) = dist2poly(wm(start:end,:),edgexy,L(start:end));          % Estimate LFS at the new points
end

% Compute the required size along the edges for any boundary layer size
% functions and add additional points where necessary.
if ~isempty(edgeh)
   for j = 1:size(edgeh,1)
      if L(edgeh(j,1))>=edgeh(j,2)
        
         cw = edgeh(j,1);
         r = 2.0*len(cw)/edgeh(j,2);
         r = ceil((r)/2.0);                                          % Number of points to be added
         tmp = (1:r-1)'/r;

         x1 = edgexy(cw,1); x2 = edgexy(cw,3); xm = wm(cw,1);              % Edge values
         y1 = edgexy(cw,2); y2 = edgexy(cw,4); ym = wm(cw,2);

         wm = [wm; x1+tmp*(xm-x1), y1+tmp*(ym-y1);                         % Add to list
                   xm+tmp*(x2-xm), ym+tmp*(y2-ym)];

         L(cw) = edgeh(j,2);                                                    % Update LFS
         L = [L; edgeh(j,2)*ones(2*length(tmp),1)];
      
      end
   end
end

% To speed the point location in the quadtree decomposition
% sort the LFS points based on y-value
[i,i] = sort(wm(:,2));
wm = wm(i,:);
L = L(i);
nw = size(wm,1);

% UNBALANCED QUADTREE DECOMPOSITION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if output
   fprintf('Quadtree decomposition\n');
end

xymin = min([edgexy(:,[1,2]); edgexy(:,[3,4])]);                           % Bounding box
xymax = max([edgexy(:,[1,2]); edgexy(:,[3,4])]);

dim = 2.0*max(xymax-xymin);                                                    % Bbox dimensions
xm = 0.5*(xymin(1)+xymax(1));
ym = 0.5*(xymin(2)+xymax(2));

% Setup boxes with a consistent CCW node order
%  b(k,1) = n1 : bottom left
%  b(k,2) = n2 : bottom right
%  b(k,3) = n3 : top right
%  b(k,4) = n4 : top left

% Start with bbox
p = [xm-0.5*dim, ym-0.5*dim
     xm+0.5*dim, ym-0.5*dim
     xm+0.5*dim, ym+0.5*dim
     xm-0.5*dim, ym+0.5*dim];
b = [1,2,3,4];

% User defined size functions
pr = rotate(p,-theta);
h = userhfun(pr(:,1),pr(:,2),fun,args,hmax,XYmin,XYmax);

pblock = 5*nw;                                                             % Alloc memory in blocks
bblock = pblock;

np = size(p,1);
nb = size(b,1);
test = true(nb,1);
while true                                                           
   
   vec = find(test(1:nb));                                                 % Boxes to be checked at this step
   if isempty(vec)
      break
   end

   N = np;
   for k = 1:length(vec)                                                   % Loop through boxes to be checked for subdivision
      
      m  = vec(k);                                                         % Current box

      n1 = b(m,1);   n2 = b(m,2);                                          % Corner nodes
      n3 = b(m,3);   n4 = b(m,4);
      x1 = p(n1,1);  y1 = p(n1,2);                                         % Corner xy
      x2 = p(n2,1);  y4 = p(n4,2);

      % Binary search to find first wm with y>=ymin for current box
      if wm(1,2)>=y1
         start = 1;
      elseif wm(nw,2)<y1
         start = nw+1;
      else
         lower = 1;
         upper = nw;
         for i = 1:nw
            start = round(0.5*(lower+upper));
            if wm(start,2)<y1
               lower = start;
            elseif wm(start-1,2)<y1
               break;
            else
               upper = start;
            end
         end
      end
      
      % Init LFS as the min of corner user defined size function values
      LFS = 1.5*h(n1);
      if 1.5*h(n2)<LFS, LFS = 1.5*h(n2); end
      if 1.5*h(n3)<LFS, LFS = 1.5*h(n3); end
      if 1.5*h(n4)<LFS, LFS = 1.5*h(n4); end

      % Loop through all WM in box and take min LFS
      for i = start:nw                                                     % Loop through points (acending y-value order)
         if (wm(i,2)<=y4)                                                  % Check box bounds and current min
            if (wm(i,1)>=x1) && (wm(i,1)<=x2) && (L(i)<LFS)
               LFS = L(i);                                                 % New min found - reset
            end
         else                                                              % Due to the sorting
            break;
         end
      end

      % Split box into 4
      if (x2-x1)>=LFS 
         
         if (np+5)>=size(p,1)                                              % Alloc memory on demand
            p = [p; zeros(pblock,2)];
            pblock = 2*pblock;
         end
         if (nb+3)>=size(b,1)
            b = [b; zeros(bblock,4)];
            test = [test; true(bblock,1)];
            bblock = 2*bblock;
         end

         xm = x1+0.5*(x2-x1);                                              % Current midpoints
         ym = y1+0.5*(y4-y1);

         % New nodes
         p(np+1,1) = xm;   p(np+1,2) = ym;
         p(np+2,1) = xm;   p(np+2,2) = y1;
         p(np+3,1) = x2;   p(np+3,2) = ym;
         p(np+4,1) = xm;   p(np+4,2) = y4;
         p(np+5,1) = x1;   p(np+5,2) = ym;   

         % New boxes
         b(m,1)      = n1;               % Box 1
         b(m,2)      = np+2;
         b(m,3)      = np+1;
         b(m,4)      = np+5;
         b(nb+1,1)   = np+2;             % Box 2
         b(nb+1,2)   = n2;
         b(nb+1,3)   = np+3;
         b(nb+1,4)   = np+1;
         b(nb+2,1)   = np+1;             % Box 3
         b(nb+2,2)   = np+3;
         b(nb+2,3)   = n3;
         b(nb+2,4)   = np+4;
         b(nb+3,1)   = np+5;             % Box 4
         b(nb+3,2)   = np+1;
         b(nb+3,3)   = np+4;
         b(nb+3,4)   = n4;

         nb = nb+3;
         np = np+5;
      else
         test(m) = false;
      end
   end

   % User defined size function at new nodes
   pr = rotate(p(N+1:np,:),-theta);
   h = [h; userhfun(pr(:,1),pr(:,2),fun,args,hmax,XYmin,XYmax)];

end
p = p(1:np,:);
b = b(1:nb,:);

% Remove duplicate nodes
[p,i,j] = unique(p,'rows');                               
h = h(i);
b = reshape(j(b),size(b));

% FORM SIZE FUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if output
   fprintf('Forming element size function\n');
end

% Unique edges
e = unique(sort([b(:,[1,2]); b(:,[2,3]); b(:,[3,4]); b(:,[4,1])],2),'rows');
L = sqrt(sum((p(e(:,1),:)-p(e(:,2),:)).^2,2));                             % Edge length

ne = size(e,1);
for k = 1:ne                                                               % Initial h - minimum neighbouring edge length
   Lk = L(k);
   if Lk<h(e(k,1)), h(e(k,1)) = Lk; end             
   if Lk<h(e(k,2)), h(e(k,2)) = Lk; end
end
h = min(h,hmax);

% Gradient limiting
tol = 1.0e-06;
while true                                                                 % Loop over the edges of the background mesh ensuring
   h_old = h;                                                              % that dh satisfies the dhmax tolerance
   for k = 1:ne                                                            % Loop over edges
      n1 = e(k,1);
      n2 = e(k,2);
      Lk = L(k);
      if h(n1)>h(n2)                                                       % Ensure grad(h)<=dhmax
         dh = (h(n1)-h(n2))/Lk;
         if dh>dhmax
            h(n1) = h(n2) + dhmax*Lk;
         end
      else
         dh = (h(n2)-h(n1))/Lk;
         if dh>dhmax
            h(n2) = h(n1) + dhmax*Lk;
         end
      end
   end
   if norm((h-h_old)./h,'inf')<tol                                         % Test convergence
      break
   end
end

% TRIANGULATE QUADTREE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if output
   fprintf('Triangulating quadtree\n');
end

if size(b,1)==1
   % Split box diagonally into 2 tri's
   t = [b(1),b(2),b(3); b(1),b(3),b(4)];
else

   % Get node-to-node connectivity
   % First column is column count per row
   % Max neighbours is 8 due to quadtree setup
   n2n = zeros(size(p,1),9);
   for k = 1:size(e,1)
      % Nodes in kth edge
      n1 = e(k,1);
      n2 = e(k,2);
      % Indexing
      n2n(n1,1) = n2n(n1,1)+1;                                             % Node 1
      n2n(n1,n2n(n1,1)+1) = n2;
      n2n(n2,1) = n2n(n2,1)+1;                                             % Node 2
      n2n(n2,n2n(n2,1)+1) = n1;
   end

   % Check for regular boxes with no mid-side nodes
   num = n2n(:,1)<=4;
   reg = all(num(b),2);

   % Split regular boxes diagonally into 2 tri's
   t = [b(reg,[1,2,3]); b(reg,[1,3,4])];
  
   if ~all(reg)
      
      % Use the N2N connectivity to directly triangulate the quadtree
      % nodes. Some additional nodes may be added at the centroids of some
      % boxes to facilitate triangulation. The triangluation is not
      % necessarily Delaunay, but should always be high quality and
      % symmetric where possible.

      b = b(~reg,:);                                                       % Boxes that still need to be dealt with
      nb = size(b,1);
      nt = size(t,1);
      
      % Alloc space
      t = [t; zeros(5*nb,3)];                                              % Has to be a least 5 times as many tri's as boxes
      nlist = zeros(512,1);                                                % Shouldn't ever be exceeded!

      for k = 1:nb

         % Corner nodes
         n1 = b(k,1); n2 = b(k,2);
         n3 = b(k,3); n4 = b(k,4);

         % Assemble node list for kth box in CCW order
         nlist(1) = n1;
         count = 1;
         next = 2;
         while true
            
            cn = nlist(next-1);

            % Find the closest node to CN travelling CCW around box
            old = inf;
            for j = 1:n2n(cn,1)
               nn = n2n(cn,j+1);
               dx = p(nn,1)-p(cn,1);
               dy = p(nn,2)-p(cn,2);
               if count==1                         % Edge 12
                  if (dx>0.0) && (dx<old)
                     old = dx;
                     tmp = nn;
                  end
               elseif count==2                     % Edge 23
                  if (dy>0.0) && (dy<old)
                     old = dy;
                     tmp = nn;
                  end
               elseif count==3                     % Edge 34
                  if (dx<0.0) && (abs(dx)<old)
                     old = abs(dx);
                     tmp = nn;
                  end
               else                                % Edge 41
                  if (dy<0.0) && (abs(dy)<old)
                     old = abs(dy);
                     tmp = nn;
                  end
               end

            end
            
            if tmp==n1                                                     % Back to start - Done!
               break
            elseif (count<4) && (tmp==b(k,count+1))                        % New edge
               count = count+1;
            end
            nlist(next) = tmp;
            next = next+1;
            
         end
         nnode = next-1;

         if (nt+nnode)>=size(t,1)                                          % Realloc memory on demand
            t = [t; zeros(nb,3)];
         end
         if (np+1)>=size(p,1)
            p = [p; zeros(nb,2)];
            h = [h; zeros(nb,1)];
         end
         
         % Triangulate box
         if nnode==4                                                       % Special treatment if no mid-side nodes
                                                                           % Split box diagonally into 2 tri's
            % New tri's
            t(nt+1,1) = n1;                     % t1
            t(nt+1,2) = n2;
            t(nt+1,3) = n3;
            t(nt+2,1) = n1;                     % t2
            t(nt+2,2) = n3;
            t(nt+2,3) = n4;
            
            % Update count
            nt = nt+2;
            
         elseif nnode==5                                                   % Special treatment if only 1 mid-side node
                                                                           % Split box into 3 tri's centred at mid-side node
            % Find the mid-side node
            j = 2;
            while j<=4
               if nlist(j)~=b(k,j)
                  break
               end
               j = j+1;
            end
           
            % Permute indexing so that the split is always between n1,n2
            if j==3
               n1 = b(k,2);   n2 = b(k,3);
               n3 = b(k,4);   n4 = b(k,1);
            elseif j==4
               n1 = b(k,3);   n2 = b(k,4);
               n3 = b(k,1);   n4 = b(k,2);
            elseif j==5
               n1 = b(k,4);   n2 = b(k,1);
               n3 = b(k,2);   n4 = b(k,3);
            end
            
            % New tri's
            t(nt+1,1) = n1;                     % t1
            t(nt+1,2) = nlist(j);
            t(nt+1,3) = n4;
            t(nt+2,1) = nlist(j);               % t2
            t(nt+2,2) = n2;
            t(nt+2,3) = n3;
            t(nt+3,1) = n4;                     % t3
            t(nt+3,2) = nlist(j);
            t(nt+3,3) = n3;

            % Update count
            nt = nt+3;
            
         else                                                              % Connect all mid-side nodes to an additional node
                                                                           % introduced at the centroid
            % New tri's
            xave = 0.0;
            yave = 0.0;
            have = 0.0;
            for j = 1:nnode-1
               jj = nlist(j);
               % New tri's
               t(nt+j,1) = jj;
               t(nt+j,2) = np+1;
               t(nt+j,3) = nlist(j+1);
               % Averaging
               xave = xave+p(jj,1);
               yave = yave+p(jj,2);
               have = have+h(jj);
            end
            jj = nlist(nnode);
            % Last tri
            t(nt+nnode,1) = jj;
            t(nt+nnode,2) = np+1;
            t(nt+nnode,3) = nlist(1);
            % New node
            p(np+1,1)   = (xave+p(jj,1)) /nnode;
            p(np+1,2)   = (yave+p(jj,2)) /nnode;
            h(np+1)     = (have+h(jj))   /nnode;

            % Update count
            nt = nt+nnode;
            np = np+1;

         end

      end
      p = p(1:np,:);
      h = h(1:np);
      t = t(1:nt,:);

   end

end

% Undo rotation
p = rotate(p,-theta);

end      % quadtree()


%% SUB-FUNCTIONS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function theta = minrectangle(p)

% Find the rotation angle that must be applied to the 2D points in P so
% that the long axis of the minimum bounding rectangle is aligned with the 
% Y axis.

n = size(p,1);
if numel(p)~=2*n
   error('P must be an Nx2 array');
end

if n>2
   
   % Convex hull edge segments
   e = convhulln(p);
   
   % Keep convex points
   i = unique(e(:));
   p = p(i,:);
   
   % Re-index to keep E consistent
   j = zeros(size(p,1),1);
   j(i) = 1;
   j = cumsum(j);
   e = j(e);
   
   % Angles of hull segments
   dxy = p(e(:,2),:)-p(e(:,1),:);
   ang = atan2(dxy(:,2),dxy(:,1));            
   
   % Check all hull edge segments
   Aold = inf;
   for k = 1:size(e,1)
      % Rotate through -ang(k)
      pr = rotate(p,-ang(k));
      % Compute area of bounding rectangle and save if better
      dxy = max(pr,[],1)-min(pr,[],1);
      A = dxy(1)*dxy(2);
      if A<Aold
         Aold = A;
         theta = -ang(k);
      end
   end
   
   % Check result to ensure that the long axis is aligned with Y
   pr = rotate(p,theta);
   dxy = max(pr,[],1)-min(pr,[],1);
   if dxy(1)>dxy(2)
      % Need to flip XY
      theta = theta+0.5*pi;
   end
   
else
   % 1 or 2 points, degenerate bounding rectangle in either case
   theta = 0.0;
end

end      % minrectangle()

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function p = rotate(p,theta)

% Rotate the 2D points in P through the angle THETA (radians).

stheta = sin(theta);
ctheta = cos(theta);

p = p*[ctheta, stheta; -stheta, ctheta];

end      % rotate()

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function h = userhfun(x,y,fun,args,hmax,xymin,xymax)

% Evaluate user defined size function.

if ~isempty(fun)
   h = feval(fun,x,y,args{:});
   if size(h)~=size(x)
      error('Incorrect user defined size function. SIZE(H) must equal SIZE(X).');
   end
else
   h = inf*ones(size(x));
end
h = min(h,hmax);

% Limit to domain
out = (x>xymax(1))|(x<xymin(1))|(y>xymax(2))|(y<xymin(2));
h(out) = inf;

if any(h<=0.0)
   error('Incorrect user defined size function. H must be positive.');
end

end      % userhfun()

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [hmax,edgeh,fun,args] = gethdata(hdata)

% Check the user defined size functions

d_hmax  = inf;
d_edgeh = [];
d_fun   = '';
d_args  = {};

if ~isempty(hdata)
   if ~isstruct(hdata)
      error('HDATA must be a structure');
   end
   if numel(hdata)~=1
      error('HDATA cannot be an array of structures');
   end
   fields = fieldnames(hdata);
   names = {'hmax','edgeh','fun','args'};
   for k = 1:length(fields)
      if ~any(strcmp(fields{k},names))
         error('Invalid field in HDATA');
      end
   end
   if isfield(hdata,'hmax')
      if (numel(hdata.hmax)~=1) || (hdata.hmax<=0)
         error('HDATA.HMAX must be a positive scalar');
      else
         hmax = hdata.hmax;
      end
   else
      hmax = d_hmax;
   end
   if isfield(hdata,'edgeh')
      if (numel(hdata.edgeh)~=2*size(hdata.edgeh,1)) || any(hdata.edgeh(:)<0)
         error('HDATA.EDGEH must be a positive Kx2 array');
      else
         edgeh = hdata.edgeh;
      end
   else
      edgeh = d_edgeh;
   end
   if isfield(hdata,'fun')
      if ~ischar(hdata.fun) && ~isa(hdata.fun,'function_handle')
         error('HDATA.FUN must be a function name or a function handle');
      else
         fun = hdata.fun;
      end
   else
      fun = d_fun;
   end
   if isfield(hdata,'args')
      if ~iscell(hdata.args)
         error(['HDATA.ARGS must be a cell array of additional' ...
            'inputs for HDATA.FUN']);
      else
         args = hdata.args;
      end
   else
      args = d_args;
   end
else
   hmax  = d_hmax;
   edgeh = d_edgeh;
   fun   = d_fun;
   args  = d_args;
end

end      % gethdata()

Contact us