├── test-data
└── img
│ ├── aabb-tree-1-small.png
│ └── aabb-tree-2-small.png
├── LICENSE.md
├── mapvert.m
├── maprect.m
├── mesh-file
├── inspect.m
├── loadmsh.m
├── certify.m
└── savemsh.m
├── exchange.m
├── drawtree.m
├── README.md
├── queryset.m
├── scantree.m
├── maketree.m
└── aabbdemo.m
/test-data/img/aabb-tree-1-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwirda/aabb-tree/HEAD/test-data/img/aabb-tree-1-small.png
--------------------------------------------------------------------------------
/test-data/img/aabb-tree-2-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwirda/aabb-tree/HEAD/test-data/img/aabb-tree-2-small.png
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | `AABB-TREE` is licensed under the following terms:
2 |
3 | This program may be freely redistributed under the condition that the copyright notices (including this entire header) are not removed, and no compensation is received through use of the software. Private, research, and institutional use is free. You may distribute modified versions of this code `UNDER THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE SAME FILE REMAIN UNDER COPYRIGHT OF THE ORIGINAL AUTHOR, BOTH SOURCE AND OBJECT CODE ARE MADE FREELY AVAILABLE WITHOUT CHARGE, AND CLEAR NOTICE IS GIVEN OF THE MODIFICATIONS`. Distribution of this code as part of a commercial system is permissible `ONLY BY DIRECT ARRANGEMENT WITH THE AUTHOR`. (If you are not directly supplying this code to a customer, and you are instead telling them how they can obtain it for free, then you are not required to make any arrangement with me.)
4 |
5 | `DISCLAIMER`: Neither I nor the University of Sydney warrant this code in any way whatsoever. This code is provided "as-is" to be used at your own risk.
6 |
--------------------------------------------------------------------------------
/mapvert.m:
--------------------------------------------------------------------------------
1 | function [tm,im] = mapvert(tr,pi)
2 | %MAPVERT find the tree-to-vertex mappings.
3 | % [TM,IM] = MAPVERT(TR,PI) returns the tree-to-vertex and
4 | % vertex-to-tree mappings for a given aabb-tree TR and a
5 | % collection of query vertices PI.
6 | %
7 | % The tree-to-item mapping TM is a structure representing
8 | % the intersection of the items PI with the tree TR. TM.II
9 | % is an M-by-1 array of tree indices and TM.LL is an
10 | % M-by-1 cell array of item lists. Specifically, items in
11 | % the list TM.LL{JJ} intersect with the node TM.II(JJ).
12 | %
13 | % The item-to-tree mapping IM is a structure representing
14 | % the inverse mapping. IM.II is an N-by-1 array of item
15 | % indices and IM.LL is an N-by-1 cell array of node lists.
16 | % Specifically, nodes in the list IM.LL{JJ} intersect with
17 | % the item IM.II(JJ).
18 | %
19 | % See also QUERYSET, MAPRECT, MAKETREE
20 |
21 | % Darren Engwirda : 2014 --
22 | % Email : de2363@columbia.edu
23 | % Last updated : 06/04/2017
24 |
25 | %----------------------- call SCANTREE to do the actual work
26 | if (nargout == +1)
27 | [tm ] = scantree(tr,pi,@partvert);
28 | else
29 | [tm,im] = scantree(tr,pi,@partvert);
30 | end
31 |
32 | end
33 |
34 | function [j1,j2] = partvert(pi,b1,b2)
35 | %PARTVERT partition points between boxes B1,B2 for SCANTREE.
36 |
37 | j1 = true(size(pi,1),1);
38 | j2 = true(size(pi,1),1);
39 |
40 | nd = size(b1,2) / +2;
41 |
42 | for ax = +1 : nd
43 | %--------------- remains TRUE if inside bounds along axis AX
44 | j1 = j1 & pi(:,ax)>=b1(ax+nd*0) ...
45 | & pi(:,ax)<=b1(ax+nd*1) ;
46 |
47 | %--------------- remains TRUE if inside bounds along axis AX
48 | j2 = j2 & pi(:,ax)>=b2(ax+nd*0) ...
49 | & pi(:,ax)<=b2(ax+nd*1) ;
50 | end
51 |
52 | end
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/maprect.m:
--------------------------------------------------------------------------------
1 | function [tm,im] = maprect(tr,pr)
2 | %MAPRECT find the tree-to-rectangle mappings.
3 | % [TM,IM] = MAPRECT(TR,PR) returns the tree-to-rectangle
4 | % and rectangle-to-tree mappings for a given aabb-tree TR
5 | % and a collection of query vertices PI.
6 | %
7 | % The tree-to-item mapping TM is a structure representing
8 | % the intersection of the items PI with the tree TR. TM.II
9 | % is an M-by-1 array of tree indices and TM.LL is an
10 | % M-by-1 cell array of item lists. Specifically, items in
11 | % the list TM.LL{JJ} intersect with the node TM.II(JJ).
12 | %
13 | % The item-to-tree mapping IM is a structure representing
14 | % the inverse mapping. IM.II is an N-by-1 array of item
15 | % indices and IM.LL is an N-by-1 cell array of node lists.
16 | % Specifically, nodes in the list IM.LL{JJ} intersect with
17 | % the item IM.II(JJ).
18 | %
19 | % See also QUERYSET, MAPVERT, MAKETREE
20 |
21 | % Darren Engwirda : 2017 --
22 | % Email : de2363@columbia.edu
23 | % Last updated : 09/04/2017
24 |
25 | %----------------------- call SCANTREE to do the actual work
26 | if (nargout == +1)
27 | [tm ] = scantree(tr,pr,@partrect);
28 | else
29 | [tm,im] = scantree(tr,pr,@partrect);
30 | end
31 |
32 | end
33 |
34 | function [j1,j2] = partrect(pr,b1,b2)
35 | %PARTRECT partition points between boxes B1,B2 for SCANTREE.
36 |
37 | j1 = true(size(pr,1),1) ;
38 | j2 = true(size(pr,1),1) ;
39 |
40 | nd = size(b1,2) / +2;
41 |
42 | for ax = +1 : nd
43 | %--------------- remains TRUE if inside bounds along axis AX
44 | j1 = j1 & pr(:,ax+nd*1) ...
45 | >= b1( ax+nd*0) ...
46 | & pr(:,ax+nd*0) ...
47 | <= b1( ax+nd*1) ;
48 |
49 | %--------------- remains TRUE if inside bounds along axis AX
50 | j2 = j2 & pr(:,ax+nd*1) ...
51 | >= b2( ax+nd*0) ...
52 | & pr(:,ax+nd*0) ...
53 | <= b2( ax+nd*1) ;
54 | end
55 |
56 | end
57 |
58 |
59 |
--------------------------------------------------------------------------------
/mesh-file/inspect.m:
--------------------------------------------------------------------------------
1 | function [okay] = inspect(mesh,varargin)
2 | %INSPECT helper routine to safely query a MESH structure.
3 |
4 | %-----------------------------------------------------------
5 | % Darren Engwirda
6 | % github.com/dengwirda/jigsaw-matlab
7 | % 19-Dec-2018
8 | % darren.engwirda@columbia.edu
9 | %-----------------------------------------------------------
10 | %
11 |
12 | okay = false; base = ''; item = '';
13 |
14 | if (nargin >= +2), base = varargin{1}; end
15 | if (nargin >= +3), item = varargin{2}; end
16 |
17 | if (nargin <= +1 || nargin >= +4)
18 | error('inspect:incorrectNumbInputs' , ...
19 | 'Incorrect number of arguments!') ;
20 | end
21 |
22 | if (~isstruct(mesh))
23 | error('inspect:incorrectInputClass' , ...
24 | 'MESH must be a valid struct.') ;
25 | end
26 | if (~isempty(base) && ~ischar(base))
27 | error('inspect:incorrectInputClass' , ...
28 | 'BASE must be a valid string!') ;
29 | end
30 | if (~isempty(item) && ~ischar(item))
31 | error('inspect:incorrectInputClass' , ...
32 | 'ITEM must be a valid string!') ;
33 | end
34 |
35 | if (isempty(item))
36 | %-- default ITEM kinds given BASE types
37 | switch (lower(base))
38 | case 'point', item = 'coord' ;
39 | case 'edge2', item = 'index' ;
40 | case 'tria3', item = 'index' ;
41 | case 'quad4', item = 'index' ;
42 | case 'tria4', item = 'index' ;
43 | case 'hexa8', item = 'index' ;
44 | case 'wedg6', item = 'index' ;
45 | case 'pyra5', item = 'index' ;
46 | case 'bound', item = 'index' ;
47 | end
48 | end
49 |
50 | if (isempty(item))
51 | %-- check whether MESH.BASE exists
52 | okay = isfield(mesh,base) && ...
53 | ~isempty(mesh.(base)) ;
54 | else
55 | %-- check whether MESH.BASE.ITEM exists
56 | okay = isfield(mesh,base) && ...
57 | isfield(mesh.(base),item) && ...
58 | ~isempty(mesh.(base).(item)) ;
59 | end
60 |
61 | end
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/exchange.m:
--------------------------------------------------------------------------------
1 | function [qp,qx] = exchange(pp,px)
2 | %EXCHANGE change the "associativity" of a sparse list set.
3 | % [QP,QX] = EXCHANGE(PP,PX) returns a new set of sparse
4 | % lists {QP,QX} as the "transpose" of the input collection
5 | % {PP,PX}. This operation can be used to form the inverse
6 | % associations between two collections of objects. For
7 | % example, given two collections A and B, if the input set
8 | % {PP,PI} represents the indexing of A-into-B, the new set
9 | % {QP,QI} represents the indexing of B-into-A.
10 | % List sets are defined via a "compressed sparse" format,
11 | % with the II-th list in a given collection {XP,XX} stored
12 | % as XX(XP(II,1):XP(II,2)).
13 | %
14 | % See also QUERYSET
15 |
16 | % Darren Engwirda : 2020 --
17 | % Email : darren.engwirda@columbia.edu
18 | % Last updated : 05/06/2020
19 |
20 | %---------------------------------------------- basic checks
21 | if ( ~isnumeric(pp) || ~isnumeric(px))
22 | error('exchange:incorrectInputClass', ...
23 | 'Incorrect input class.') ;
24 | end
25 |
26 | if (size(pp,2) ~= 2 || size(px,2) ~= 1 )
27 | error('exchange:incorrectDimensions', ...
28 | 'Incorrect input dimensions.') ;
29 | end
30 |
31 | if (min(px(:)) <= 0 || ...
32 | max(pp(:)) > length(px) )
33 | error('exchange:invalidListIndexing', ...
34 | 'Invalid list indexing.') ;
35 | end
36 |
37 | %----------------------------------- compute list "tranpose"
38 |
39 | qp = zeros(max(px),2);
40 | qx = zeros(length(px),1);
41 |
42 | for ip = 1:length(pp) % accumulate column count
43 | for ii = pp(ip,1):pp(ip,2)
44 | qp(px(ii),2) = qp(px(ii),2) + 1;
45 | end
46 | end
47 |
48 | Z = qp(:,2) == +0 ; % deal with "empty" lists
49 |
50 | qp(:,2) = cumsum(qp(:,2));
51 | qp(:,1) = [+1;qp(1:end-1,2)+1];
52 |
53 | for ip = 1:length(pp) % tranpose of items array
54 | for ii = pp(ip,1):pp(ip,2)
55 | qx(qp(px(ii),1)) = ip;
56 | qp(px(ii),1) = qp(px(ii),1) + 1;
57 | end
58 | end
59 |
60 | qp(:,1) = [+1;qp(1:end-1,2)+1];
61 |
62 | qp(Z,1) = +0; % deal with "empty" lists
63 | qp(Z,2) = -1;
64 |
65 | end
66 |
67 |
68 |
--------------------------------------------------------------------------------
/drawtree.m:
--------------------------------------------------------------------------------
1 | function drawtree(tr,varargin)
2 | %DRAWTREE draw an aabb-tree generated using MAKETREE.
3 | % DRAWTREE(TR) draws the tree TR for cases in R^2 and R^3.
4 | %
5 | % See also MAKETREE
6 |
7 | % Darren Engwirda : 2014 --
8 | % Email : darren.engwirda@columbia.edu
9 | % Last updated : 18/12/2014
10 |
11 | %---------------------------------------------- basic checks
12 | if (~isa(tr,'struct') || ...
13 | ~isfield(tr,'xx') || ...
14 | ~isfield(tr,'ii') || ...
15 | ~isfield(tr,'ll') )
16 | error('drawtree:incorrectInputClass', ...
17 | 'Incorrect aabb-tree.') ;
18 | end
19 |
20 | %----------------------------------------- find "leaf" nodes
21 | lf = ~cellfun('isempty', tr.ll) ;
22 |
23 | fc = [.95,.95,.55] ;
24 | ec = [.15,.15,.15] ;
25 |
26 | %-------------------------- draw all "leaf" nodes as patches
27 | switch (size(tr.xx,2))
28 | case 4
29 | %----------------------------------------------- tree in R^2
30 | np = numel(find(lf));
31 | %------------------------------------------------- nodes
32 | pp = [tr.xx(lf,1),tr.xx(lf,2)
33 | tr.xx(lf,3),tr.xx(lf,2)
34 | tr.xx(lf,3),tr.xx(lf,4)
35 | tr.xx(lf,1),tr.xx(lf,4)
36 | ] ;
37 | %------------------------------------------------- faces
38 | bb = [(1:np)'+np*0,(1:np)'+np*1,...
39 | (1:np)'+np*2,(1:np)'+np*3
40 | ] ;
41 |
42 | case 6
43 | %----------------------------------------------- tree in R^3
44 | np = numel(find(lf));
45 | %------------------------------------------------- nodes
46 | pp = [tr.xx(lf,1),tr.xx(lf,2),tr.xx(lf,3)
47 | tr.xx(lf,4),tr.xx(lf,2),tr.xx(lf,3)
48 | tr.xx(lf,4),tr.xx(lf,5),tr.xx(lf,3)
49 | tr.xx(lf,1),tr.xx(lf,5),tr.xx(lf,3)
50 | tr.xx(lf,1),tr.xx(lf,2),tr.xx(lf,6)
51 | tr.xx(lf,4),tr.xx(lf,2),tr.xx(lf,6)
52 | tr.xx(lf,4),tr.xx(lf,5),tr.xx(lf,6)
53 | tr.xx(lf,1),tr.xx(lf,5),tr.xx(lf,6)
54 | ] ;
55 | %------------------------------------------------- faces
56 | bb = [(1:np)'+np*0,(1:np)'+np*1,...
57 | (1:np)'+np*2,(1:np)'+np*3
58 | (1:np)'+np*4,(1:np)'+np*5,...
59 | (1:np)'+np*6,(1:np)'+np*7
60 | (1:np)'+np*0,(1:np)'+np*3,...
61 | (1:np)'+np*7,(1:np)'+np*4
62 | (1:np)'+np*3,(1:np)'+np*2,...
63 | (1:np)'+np*6,(1:np)'+np*7
64 | (1:np)'+np*2,(1:np)'+np*1,...
65 | (1:np)'+np*5,(1:np)'+np*6
66 | (1:np)'+np*1,(1:np)'+np*0,...
67 | (1:np)'+np*4,(1:np)'+np*5
68 | ] ;
69 |
70 | otherwise
71 | %--------------------------- what to do with a tree in R^d!?
72 | error('scantree:unsupportedDimension', ...
73 | 'Unsupported tree dimensionality.') ;
74 | end
75 |
76 | patch('faces',bb,'vertices',pp,'facecolor',fc,...
77 | 'edgecolor',ec,'facealpha',+.2);
78 |
79 | end
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## `AABB-Tree: Spatial Indexing in MATLAB`
2 |
3 | A d-dimensional `aabb-tree` implementation in `MATLAB` / `OCTAVE`.
4 |
5 | The `AABB-TREE` toolbox provides d-dimensional `aabb-tree` construction and search for arbitrary collections of spatial objects. These tree-based indexing structures are useful when seeking to implement efficient spatial queries, reducing the complexity of intersection tests between collections of objects. Specifically, given two "well-distributed" collections `P` and `Q`, use of `aabb`-type acceleration allows the set of intersections to be computed in `O(|P|*log(|Q|))`, which is typically a significant improvement over the `O(|P|*|Q|)` operations required by "brute-force" methods.
6 |
7 | Given a collection of objects, an `aabb-tree` partitions the axis-aligned bounding-boxes (`AABB`'s) associated with the elements in the collection into a (binary) "tree" -- a hierarchy of "nodes" (hyper-rectangles) that each store a subset of the collection. In contrast to other geometric tree types (`quadtrees`, `kd-trees`, etc), `aabb-trees` are applicable to collections of general objects, rather than just points.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ### `Quickstart`
17 |
18 | After downloading and unzipping the current repository, navigate to the installation directory within `MATLAB` / `OCTAVE` and run the set of examples contained in `aabbdemo.m`:
19 | ````
20 | aabbdemo(1); % build a tree for a 2-dimensional triangulation.
21 | aabbdemo(2); % build a tree for a 3-dimensional triangulation.
22 | aabbdemo(3); % compare a "fast" "aabb-accelerated" search with a "slow" brute-force computation.
23 | ````
24 |
25 | ### `License`
26 |
27 | This program may be freely redistributed under the condition that the copyright notices (including this entire header) are not removed, and no compensation is received through use of the software. Private, research, and institutional use is free. You may distribute modified versions of this code `UNDER THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE SAME FILE REMAIN UNDER COPYRIGHT OF THE ORIGINAL AUTHOR, BOTH SOURCE AND OBJECT CODE ARE MADE FREELY AVAILABLE WITHOUT CHARGE, AND CLEAR NOTICE IS GIVEN OF THE MODIFICATIONS`. Distribution of this code as part of a commercial system is permissible `ONLY BY DIRECT ARRANGEMENT WITH THE AUTHOR`. (If you are not directly supplying this code to a customer, and you are instead telling them how they can obtain it for free, then you are not required to make any arrangement with me.)
28 |
29 | `DISCLAIMER`: Neither I nor the University of Sydney warrant this code in any way whatsoever. This code is provided "as-is" to be used at your own risk.
30 |
31 | ### `References`
32 |
33 | `AABB-TREE` is used extensively in the grid-generator `MESH2D`. The tree-construction and search methods employed in the `AABB-TREE` library are described in further detail here:
34 |
35 | `[1]` - Darren Engwirda, Locally-optimal Delaunay-refinement and optimisation-based mesh generation, Ph.D. Thesis, School of Mathematics and Statistics, The University of Sydney, September 2014.
36 |
37 |
--------------------------------------------------------------------------------
/queryset.m:
--------------------------------------------------------------------------------
1 | function [qi,qp,qj] = queryset(tr,tm,fn,varargin)
2 | %QUERYSET spatial queries for AABB-indexed collections.
3 | % [QI,QP,QJ] = QUERYSET(TR,TM,FN) computes a spatial query
4 | % on an indexed collection. TR is the AABB-tree built to
5 | % index the collection, TM is the query-to-tree mapping
6 | % structure, and FN is the intersection "kernel" function,
7 | % called to compute actual intersections.
8 | %
9 | % A set of intersecting objects is returned for each item,
10 | % such that for each query item QI(II), a list of interse-
11 | % cting objects QJ(QP(II,1):QP(II,2)) is returned.
12 | %
13 | % [PK,CK] = FN(PJ,CJ,A1,...,AN) is an intersection kernel
14 | % function called for each non-empty node in the tree TR.
15 | % PJ,CJ are Nx1 and Mx1 arrays of query- and obj.-indices
16 | % to compare against each other. These lists represent a
17 | % "localised" O(N*M) "tile" of pairwise comparisons. PK,CK
18 | % are lists of matching objects. A1,...,AN are a set of
19 | % optional user-defined arguments that are passed to the
20 | % kernel function FN internally. Optional arguments can be
21 | % passed to FN via calls to QUERYSET(..., A1,...AN).
22 | %
23 | % See also MAPVERT, MAPRECT, MAKETREE
24 |
25 | % Darren Engwirda : 2017 --
26 | % Email : de2363@columbia.edu
27 | % Last updated : 07/10/2017
28 |
29 | qi = []; qp = []; qj = [];
30 |
31 | %---------------------------------------------- basic checks
32 | if (nargin <= +2)
33 | error('queryset:incorrectNumInputs', ...
34 | 'Incorrect number of inputs.');
35 | end
36 |
37 | %---------------------------------------------- empty inputs
38 | if (isempty(tr)), return; end
39 |
40 | %---------------------------------------------- basic checks
41 | if (~isempty(tr) && ~isstruct(tr) )
42 | error('queryset:incorrectInputClass', ...
43 | 'Incorrect input class.') ;
44 | end
45 | if (~isempty(tm) && ~isstruct(tm) )
46 | error('queryset:incorrectInputClass', ...
47 | 'Incorrect input class.') ;
48 | end
49 |
50 | %---------------------------------- check existing aabb-tree
51 | if (~isfield(tm,'ii') || ...
52 | ~isfield(tm,'ll') )
53 | error('queryset:incorrectAABBstruct', ...
54 | 'Invalid aabb-maps obj.') ;
55 | end
56 | %---------------------------------- check existing aabb-tree
57 | if (~isfield(tr,'xx') || ...
58 | ~isfield(tr,'ii') || ...
59 | ~isfield(tr,'ll') )
60 | error('queryset:incorrectAABBstruct', ...
61 | 'Invalid aabb-tree obj.') ;
62 | end
63 |
64 | %------------------------------ spatial query over tree-node
65 | ic = cell(size(tm.ii,1),1) ;
66 | jc = cell(size(tm.ii,1),1) ;
67 |
68 | for ip = 1 : size(tm.ii,1)
69 | %-------------------------- extract items/query per tile
70 | ni = tm.ii(ip,1) ; % node (in tree)
71 |
72 | %-------------------------- do O(n*m) search within tile
73 | [qi,qj] = feval(fn, ...
74 | tm.ll{ip,1}, ... % query in tile
75 | tr.ll{ni,1}, ... % items in tile
76 | varargin {:} ) ;
77 |
78 | %-------------------------- push loc. item-query matches
79 | ic{ip} = qi(:) ;
80 | jc{ip} = qj(:) ;
81 | end
82 |
83 | %-------------------------------- concat matches into arrays
84 | qi = vertcat(ic{:});
85 | qj = vertcat(jc{:});
86 |
87 | if (isempty(qj)),return; end
88 |
89 | %-------------------------------- form sparse-style indexing
90 | [qi,ix] = sort (qi) ;
91 | qj = qj(ix);
92 | ix = find(diff(qi));
93 |
94 | ni = length (qi) ;
95 |
96 | qi = qi([ix;ni]) ;
97 |
98 | nj = length (qj) ;
99 | ni = length (qi) ;
100 |
101 | %------------------------------ each list is IP(I,1):IP(I,2)
102 | qp = zeros(ni,2) ;
103 | qp(:,1) = [+1; ix+1] ;
104 | qp(:,2) = [ix; nj+0] ;
105 |
106 | end
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/scantree.m:
--------------------------------------------------------------------------------
1 | function [tm,im] = scantree(tr,pi,fn)
2 | %SCANTREE find the tree-to-item mappings.
3 | % [TM,IM] = SCANTREE(TR,PI,FN) is a low-level routine that
4 | % returns the tree-to-item and item-to-tree mappings for a
5 | % given aabb-tree TR and a query collection PI. The funct-
6 | % ion [KI,KJ] = FN(PJ,NI,NJ) is called internally to part-
7 | % ition the sub-collection PJ between the aabb-tree nodes
8 | % NI,NJ, where:
9 | %
10 | % * KI(II) = TRUE if the II-th item intersects NI, and
11 | % * KJ(II) = TRUE if the II-th item intersects NJ.
12 | %
13 | % The tree-to-item mapping TM is a structure representing
14 | % the intersection of the items PI with the tree TR. TM.II
15 | % is an M-by-1 array of tree indices and TM.LL is an
16 | % M-by-1 cell array of item lists. Specifically, items in
17 | % the list TM.LL{JJ} intersect with the node TM.II(JJ).
18 | %
19 | % The item-to-tree mapping IM is a structure representing
20 | % the inverse mapping. IM.II is an N-by-1 array of item
21 | % indices and IM.LL is an N-by-1 cell array of node lists.
22 | % Specifically, nodes in the list IM.LL{JJ} intersect with
23 | % the item IM.II(JJ).
24 | %
25 | % See also QUERYSET, MAPVERT, MAPRECT, MAKETREE
26 |
27 | % Darren Engwirda : 2014 --
28 | % Email : de2363@columbia.edu
29 | % Last updated : 06/04/2017
30 |
31 | tm.ii = [] ; tm.ll = {} ;
32 | im.ii = [] ; im.ll = {} ;
33 |
34 | %------------------------------ quick return on empty inputs
35 | if (isempty(pi)), return; end
36 | if (isempty(tr)), return; end
37 |
38 | %---------------------------------------------- basic checks
39 | if (~isa(tr, 'struct') || ...
40 | ~isa(pi,'numeric') || ...
41 | ~isa(fn,'function_handle') )
42 | error('scantree:incorrectInputClass', ...
43 | 'Invalid input class.') ;
44 | end
45 | %---------------------------------------------- basic checks
46 | if ( ~isfield(tr,'xx') || ...
47 | ~isfield(tr,'ii') || ...
48 | ~isfield(tr,'ll') )
49 | error('scantree:incorrectAABBstruct', ...
50 | 'Incorrect aabb-tree.') ;
51 | end
52 |
53 | %----------------------------------- alloc. output/workspace
54 | tm.ii = zeros(size(tr.ii,1),1);
55 | tm.ll = cell (size(tr.ii,1),1);
56 |
57 | ss = zeros(size(tr.ii,1),1);
58 | sl = cell (size(tr.ii,1),1);
59 | sl{1} = (1:size(pi,1))';
60 |
61 | tf = ~cellfun('isempty',tr.ll);
62 |
63 | %---------- descend tree from root, push items amongst nodes
64 |
65 | ss(1) = +1; ns = +1; no = +1;
66 | while (ns ~= +0)
67 | %---------------------------------- _pop node from stack
68 | ni = ss(ns); ns = ns - 1;
69 |
70 | if (tf(ni))
71 | %-- push onto tree-item mapping -- non-empty node NI
72 | %-- contains items LL
73 | tm.ii(no) = ni ;
74 | tm.ll{no} ...
75 | = sl{ns+1} ;
76 | no = no + 1 ;
77 | end
78 |
79 | if (tr.ii(ni,+2)~=+0)
80 | %--------------------- partition amongst child nodes
81 | c1 = ...
82 | tr.ii(ni,2) + 0 ;
83 | c2 = ...
84 | tr.ii(ni,2) + 1 ;
85 |
86 | %--------------------- user-defined partitions of LL
87 | [j1,j2] = feval( ...
88 | fn,pi(sl{ns+1},:), ...
89 | tr.xx(c1,:),tr.xx(c2,:)) ;
90 |
91 | %--------------------- lists of items per child node
92 | l1 = sl{ns+1}(j1) ;
93 | l2 = sl{ns+1}(j2) ;
94 |
95 | if (~isempty(l1))
96 | %--------------------- push nonempty node onto stack
97 | ns = ns + 1 ;
98 | ss(ns) = c1 ;
99 | sl{ns} = l1 ;
100 | end
101 | if (~isempty(l2))
102 | %--------------------- push nonempty node onto stack
103 | ns = ns + 1 ;
104 | ss(ns) = c2 ;
105 | sl{ns} = l2 ;
106 | end
107 |
108 | end
109 |
110 | end
111 | %----------------------------------------------- trim alloc.
112 | tm.ii(no:end) = [];
113 | tm.ll(no:end) = [];
114 |
115 | %----------------------- compute inverse map only if desired
116 | if (nargout==+1), return; end
117 |
118 | %----------------------- accumulate pair'd tree-item matches
119 | ic = cell(no-1,+1); jc = tm.ll;
120 | for ip = +1 : no-1
121 | ni = tm.ii(ip);
122 | ic{ip} = ...
123 | ni * ones(length(jc{ip}),1);
124 | end
125 | ii = vertcat(ic{:});
126 | ni = size(ii,1) ;
127 | jj = vertcat(jc{:});
128 | nj = size(jj,1) ;
129 |
130 | if (isempty(jj)), return; end
131 |
132 | im.ll = cell (size(pi,1),1) ;
133 |
134 | %---------------------------------- invert ordering via sort
135 | [jj,jx] = sort(jj) ;
136 | ii = ii(jx);
137 | jx = find(diff(jj)~=+0);
138 | im.ii = [jj(jx);jj(nj)];
139 | jx = [+0;jx;ni];
140 |
141 | %----------------------- distribute single item-tree matches
142 | for ip = +1 : size(im.ii,1)
143 | im.ll{ip} = ...
144 | ii(jx(ip+0)+1:jx(ip+1)+0);
145 | end
146 |
147 | end
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/maketree.m:
--------------------------------------------------------------------------------
1 | function [tr] = maketree(rp,varargin)
2 | %MAKETREE assemble an AABB search tree for a collection of
3 | %(hyper-)rectangles.
4 | % [TR] = MAKETREE(RP) returns an axis-aligned bounding-box
5 | % (AABB) tree for a collection of d-rectangles embedded in
6 | % R^d. The rectangles are defined as an NR-by-NDIM*2 array
7 | % of min/max coordinates RP = [PMIN,PMAX], where PMIN =
8 | % [A1,A2,...,ANDIM] and PMAX = [B1,B2,...,BNDIM] are the
9 | % minimum/maximum coordinates associated with each rectan-
10 | % gle.
11 | %
12 | % The resulting tree is returned as a structure containing
13 | % an NT-by-NDIM*2 array of tree-node coordinates TR.XX =
14 | % [PMIN,PMAX], an NT-by-2 array of tree-node indexing
15 | % TR.II = [PI,CI], and an NT-by-1 cell array of node lists
16 | % TR.LL. PI,CI are the parent/child pointers associated
17 | % with each node in the tree, and TR.LL{II} is the list of
18 | % rectangles associated with the II-th node.
19 | %
20 | % MAKETREE forms a binary search tree, such that each
21 | % "leaf" node in the tree encloses a maximum number of re-
22 | % ctangles. The tree is formed by recursively subdividing
23 | % the bounding-box of the collection. At each division, a
24 | % simple heuristic is used to determine a splitting axis
25 | % and to position an axis-aligned splitting (hyper-)plane.
26 | % The associated collection of rectangles is partitioned
27 | % between two new child nodes. The dimensions of each node
28 | % in the tree are selected to provide a minimal enclosure
29 | % of the rectangles in its associated sub-tree. Tree nodes
30 | % may overlap as a result.
31 | %
32 | % [...] = MAKETREE(RP,OP) also passes an additional user-
33 | % defined options structure. OP.NOBJ = {32} is the maximum
34 | % allowable number of rectangles per tree-node. OP.LONG =
35 | % {.75} is a relative length tolerance for "long" rectang-
36 | % les, such that any rectangles with RMAX(IX)-RMIN(IX) >=
37 | % OP.LONG * (NMAX(IX)-NMIN(IX)) remain in the parent node.
38 | % Here RMIN,RMAX are the coordinates of the rectangle,
39 | % NMIN,NMAX are the coordinates of the enclosing node in
40 | % the tree, and IX is the splitting axis. Nodes that beco-
41 | % me "full" of "long" items may exceed their maximum rect-
42 | % angle capacity. OP.VTOL = {.55} is a "volume" splitting
43 | % criteria, designed to continue subdivision while the net
44 | % node volume is reducing. Specifically, a node is split
45 | % if V1+V2 <= OP.VTOL*VP, where VP is the d-dim. "volume"
46 | % of the parent node, and V1,V2 are the volumes associated
47 | % with its children.
48 | %
49 | % See also DRAWTREE, QUERYSET, MAPVERT, MAPRECT
50 |
51 | % Please see the following for additional information:
52 | %
53 | % Darren Engwirda, "Locally-optimal Delaunay-refinement &
54 | % optimisation-based mesh generation". Ph.D. Thesis, Scho-
55 | % ol of Mathematics and Statistics, Univ. of Sydney, 2014:
56 | % http://hdl.handle.net/2123/13148
57 |
58 | % Darren Engwirda : 2014 --
59 | % Email : de2363@columbia.edu
60 | % Last updated : 08/10/2017
61 |
62 | tr.xx = []; tr.ii = []; tr.ll = {}; op = [];
63 |
64 | %------------------------------ quick return on empty inputs
65 | if (isempty(rp)), return; end
66 |
67 | %---------------------------------------------- basic checks
68 | if (~isnumeric(rp))
69 | error('maketree:incorrectInputClass', ...
70 | 'Incorrect input class.');
71 | end
72 |
73 | %---------------------------------------------- basic checks
74 | if (nargin < +1 || nargin > +2)
75 | error('maketree:incorrectNoOfInputs', ...
76 | 'Incorrect number of inputs.');
77 | end
78 | if (ndims(rp) ~= +2 || ...
79 | mod(size(rp,2),2)~= +0)
80 | error('maketree:incorrectDimensions', ...
81 | 'Incorrect input dimensions.');
82 | end
83 |
84 | %------------------------------- extract user-defined inputs
85 | if (nargin >= +2), op = varargin{1}; end
86 |
87 | isoctave = exist( ...
88 | 'OCTAVE_VERSION','builtin') > 0;
89 |
90 | if (isoctave)
91 |
92 | %-- faster for OCTAVE with large tree block size; slower
93 | %-- loop execution...
94 |
95 | NOBJ = +1024 ;
96 |
97 | else
98 |
99 | %-- faster for MATLAB with small tree block size; better
100 | %-- loop execution...
101 |
102 | NOBJ = + 32 ;
103 |
104 | end
105 |
106 | %--------------------------------------- user-defined inputs
107 | if (~isstruct(op))
108 |
109 | op.nobj = NOBJ ;
110 | op.long = .75;
111 | op.vtol = .55;
112 |
113 | else
114 |
115 | %-------------------------------- bound population count
116 | if (isfield(op,'nobj'))
117 | if (op.nobj <= +0 )
118 | error('Invalid options.') ;
119 | end
120 | else
121 | op.nobj = NOBJ ;
122 | end
123 |
124 | %-------------------------------- bound "long" tolerance
125 | if (isfield(op,'long'))
126 | if (op.long < +.0 || op.long > +1.)
127 | error('Invalid options.') ;
128 | end
129 | else
130 | op.long = .75;
131 | end
132 |
133 | %-------------------------------- bound "long" tolerance
134 | if (isfield(op,'vtol'))
135 | if (op.vtol < +.0 || op.vtol > +1.)
136 | error('Invalid options.') ;
137 | end
138 | else
139 | op.vtol = .55;
140 | end
141 |
142 | end
143 |
144 | %---------------------------------- dimensions of rectangles
145 | nd = size(rp,2) / +2 ;
146 | ni = size(rp,1) ;
147 |
148 | %------------------------------------------ alloc. workspace
149 | xl = zeros(ni*1,1*nd);
150 | xr = zeros(ni*1,1*nd);
151 | ii = zeros(ni*1,2);
152 | ll = cell (ni*1,1);
153 | ss = zeros(ni*1,1);
154 |
155 | %------------------------------------ min & max coord. masks
156 | lv = false(size(rp,2),1);
157 | rv = false(size(rp,2),1);
158 | lv((1:nd)+nd*+0) = true ;
159 | rv((1:nd)+nd*+1) = true ;
160 |
161 | %----------------------------------------- inflate rectangle
162 | r0 = min(rp(:,lv),[],1) ;
163 | r1 = max(rp(:,rv),[],1) ;
164 |
165 | rd = repmat(r1-r0,ni,1) ;
166 |
167 | rp(:,lv) = ...
168 | rp(:,lv) - rd * eps^.8;
169 | rp(:,rv) = ...
170 | rp(:,rv) + rd * eps^.8;
171 |
172 | %----------------------------------------- rectangle centres
173 | rc = rp(:,lv)+rp(:,rv);
174 | rc = rc * .5 ;
175 | %----------------------------------------- rectangle lengths
176 | rd = rp(:,rv)-rp(:,lv);
177 |
178 | %------------------------------ root contains all rectangles
179 | ll{1} = (+1:ni)' ;
180 | %------------------------------------ indexing for root node
181 | ii(1,1) = +0 ;
182 | ii(1,2) = +0 ;
183 | %------------------------------ root contains all rectangles
184 | xl(1,:) = min(rp(:,lv),[],1);
185 | xr(1,:) = max(rp(:,rv),[],1);
186 |
187 | %-- main loop : divide nodes until all constraints satisfied
188 | ss(+1) = +1; ns = +1; nn = +1;
189 | while (ns ~= +0)
190 | %----------------------------------- pop node from stack
191 | ni = ss(ns) ;
192 | ns = ns - 1 ;
193 | %----------------------------------- push child indexing
194 | n1 = nn + 1 ;
195 | n2 = nn + 2 ;
196 |
197 | %--------------------------- set of rectangles in parent
198 | li = ll{ni} ;
199 | %--------------------------- split plane on longest axis
200 | dd = xr(ni,:) ...
201 | - xl(ni,:) ;
202 | [dd,ia] = sort(dd);
203 |
204 | for id = nd : -1 : +1
205 | %--------------------------- push rectangles to children
206 | ax = ia (id) ;
207 | mx = dd (id) ;
208 |
209 | il = rd(li,ax) > ...
210 | op.long * mx ;
211 | lp = li( il) ; % "long" rectangles
212 | ls = li(~il) ; % "short" rectangles
213 |
214 | if (length(lp) < ...
215 | 0.5*length(ls)&& ...
216 | length(lp) < ...
217 | 0.5 * op.nobj)
218 | break ;
219 | end
220 | end
221 |
222 | if (isempty(ls) )
223 | %-------------------------------- partition empty, done!
224 | continue ;
225 | end
226 |
227 | % select the split position: take the mean of the set of
228 | % (non-"long") rectangle centres along axis AX
229 | %-------------------------------------------------------
230 | sp = sum(rc(ls,ax))/length(ls);
231 |
232 | %---------------------------- partition based on centres
233 | i2 = rc(ls,ax)>sp ;
234 | l1 = ls(~i2) ; % "left" rectangles
235 | l2 = ls( i2) ; % "right" rectangles
236 |
237 | if (isempty(l1) || ...
238 | isempty(l2) )
239 | %-------------------------------- partition empty, done!
240 | continue ;
241 | end
242 |
243 | %-------------------------------- finalise node position
244 | xl(n1,:) = ...
245 | min(rp(l1,lv),[],1) ;
246 | xr(n1,:) = ...
247 | max(rp(l1,rv),[],1) ;
248 | xl(n2,:) = ...
249 | min(rp(l2,lv),[],1) ;
250 | xr(n2,:) = ...
251 | max(rp(l2,rv),[],1) ;
252 |
253 | %--------------------------- push child nodes onto stack
254 | if (length(li) <= op.nobj )
255 |
256 | vi = prod(xr(ni,:) ... % upper d-dim "vol."
257 | - xl(ni,:) ) ;
258 | v1 = prod(xr(n1,:) ... % lower d-dim "vol."
259 | - xl(n1,:) ) ;
260 | v2 = prod(xr(n2,:) ...
261 | - xl(n2,:) ) ;
262 |
263 | if (v1+v2 < op.vtol*vi)
264 |
265 | %-------------------------------- parent--child indexing
266 | ii(n1,1) = ni ;
267 | ii(n2,1) = ni ;
268 | ii(ni,2) = n1 ;
269 |
270 | %-------------------------------- finalise list indexing
271 | ll{ni,1} = lp ;
272 | ll{n1,1} = l1 ;
273 | ll{n2,1} = l2 ;
274 |
275 | ss(ns+1) = n1 ;
276 | ss(ns+2) = n2 ;
277 | ns = ns+2; nn = nn+2;
278 |
279 | end
280 |
281 | else
282 | %-------------------------------- parent--child indexing
283 | ii(n1,1) = ni ;
284 | ii(n2,1) = ni ;
285 | ii(ni,2) = n1 ;
286 |
287 | %-------------------------------- finalise list indexing
288 | ll{ni,1} = lp ;
289 | ll{n1,1} = l1 ;
290 | ll{n2,1} = l2 ;
291 |
292 | ss(ns+1) = n1 ;
293 | ss(ns+2) = n2 ;
294 | ns = ns+2; nn = nn+2;
295 |
296 | end
297 |
298 | end
299 | %----------------------------------------------- trim alloc.
300 | xl = xl(1:nn,:) ;
301 | xr = xr(1:nn,:) ;
302 | ii = ii(1:nn,:) ;
303 | ll = ll(1:nn,:) ;
304 |
305 | %----------------------------------------------- pack struct
306 | tr.xx =[xl, xr] ;
307 | tr.ii = ii ;
308 | tr.ll = ll ;
309 |
310 | end
311 |
312 |
313 |
314 |
--------------------------------------------------------------------------------
/aabbdemo.m:
--------------------------------------------------------------------------------
1 | function aabbdemo(varargin)
2 | %AABBDEMO run a series of aabb-tree demos.
3 | % AABBDEMO(II) runs the II-th demo, where +1 <= II <= +3.
4 | % Demos illustrate the functionallity of the MAKETREE
5 | % routine -- performing spatial queries on collections of
6 | % bounding-boxes.
7 | %
8 | % See also MAKETREE, DRAWTREE, QUERYSET, MAPVERT, MAPRECT
9 |
10 | % Darren Engwirda : 2014 --
11 | % Email : darren.engwirda@columbia.edu
12 | % Last updated : 09/03/2018
13 |
14 | if (nargin>=1)
15 | id = varargin{1};
16 | else
17 | id = +1;
18 | end
19 |
20 | %------------------------------------------------- call demo
21 | switch (id)
22 | case 1, demo1;
23 | case 2, demo2;
24 | case 3, demo3;
25 | otherwise
26 | error('aabbdemo:invalidInput','Invalid demo selection.');
27 | end
28 |
29 | end
30 |
31 | function demo1
32 | %-----------------------------------------------------------
33 | fprintf(1,[...
34 | ' AABBTREE offers d-dimensional aabb-tree construction &\n',...
35 | ' search for collections of spatial objects. These trees\n',...
36 | ' are useful when seeking to implement efficient spatial\n',...
37 | ' queries -- determining intersections between collecti-\n',...
38 | ' ons of spatial objects. \n\n',...
39 | ' Given a collection of spatial objects, an aabb-tree p-\n',...
40 | ' artitions the bounding-boxes of the elements in the c-\n',...
41 | ' ollection (the aabb''s) into a "tree" (hierarchy) of \n',...
42 | ' rectangular "nodes". In contrast to other types of ge-\n',...
43 | ' ometric trees (quadtrees, kd-trees, etc) the nodes in \n',...
44 | ' an aabb-tree enclose aabb''s -- not points -- and may \n',...
45 | ' overlap as a result. Objects in the collection are co-\n',...
46 | ' ntained in a single node only. \n\n']);
47 |
48 | filename = mfilename('fullpath');
49 | filepath = fileparts( filename );
50 |
51 | addpath([filepath,'/mesh-file']);
52 |
53 | [geom] = loadmsh([filepath,'/test-data/airfoil.msh']);
54 |
55 | pp = geom.point.coord(:,1:2);
56 | tt = geom.tria3.index(:,1:3);
57 |
58 | bi = pp(tt(:,1),:); bj = pp(tt(:,1),:);
59 | for ii = 2 : size(tt,2)
60 | bi = min(bi,pp(tt(:,ii),:)) ;
61 | bj = max(bj,pp(tt(:,ii),:)) ;
62 | end
63 |
64 | tr = maketree([bi,bj]) ;
65 |
66 | fc = [.95,.95,.55];
67 | ec = [.25,.25,.25];
68 |
69 | figure;
70 | subplot(1,2,1); hold on;
71 | patch('faces',tt,'vertices',pp,'facecolor',fc,...
72 | 'edgecolor',ec,'facealpha',+.3);
73 | axis image off;
74 | set(gca,'units','normalized','position',[0.01,0.05,.48,.90]);
75 | subplot(1,2,2); hold on;
76 | drawtree(tr);
77 | axis image off;
78 | set(gca,'units','normalized','position',[0.51,0.05,.48,.90]);
79 |
80 | end
81 |
82 | function demo2
83 | %-----------------------------------------------------------
84 | fprintf(1,[...
85 | ' AABBTREE is a d-dimensional library, storing objects \n',...
86 | ' and performing search operations in R^d. AABBTREE sim-\n',...
87 | ' ply requires an description of the d-dimensional boun-\n',...
88 | ' ding-boxes of a given collection. It is not limited to\n',...
89 | ' simplexes (triangles, tetrahedrons, etc).\n\n']);
90 |
91 | filename = mfilename('fullpath');
92 | filepath = fileparts( filename );
93 |
94 | addpath([filepath,'/mesh-file']);
95 |
96 | [geom] = loadmsh([filepath,'/test-data/veins.msh']);
97 |
98 | pp = geom.point.coord(:,1:3);
99 | tt = geom.tria3.index(:,1:3);
100 |
101 | bi = pp(tt(:,1),:); bj = pp(tt(:,1),:);
102 | for ii = 2 : size(tt,2)
103 | bi = min(bi,pp(tt(:,ii),:)) ;
104 | bj = max(bj,pp(tt(:,ii),:)) ;
105 | end
106 |
107 | op.vtol = .67;
108 |
109 | tr = maketree([bi,bj],op) ;
110 |
111 | fc = [.95,.95,.55];
112 | ec = [.25,.25,.25];
113 |
114 | figure;
115 | subplot(1,2,1); hold on;
116 | patch('faces',tt,'vertices',pp,'facecolor',fc,...
117 | 'edgecolor',ec,'facealpha',+1.);
118 | axis image off;
119 | set(gca,'units','normalized','position',[0.01,0.05,.48,.90]);
120 | view(80,15);
121 | light; camlight;
122 | subplot(1,2,2); hold on;
123 | drawtree(tr);
124 | axis image off;
125 | view(80,15);
126 | light; camlight;
127 | set(gca,'units','normalized','position',[0.51,0.05,.48,.90]);
128 |
129 | end
130 |
131 | function demo3
132 | %-----------------------------------------------------------
133 | fprintf(1,[...
134 | ' AABBTREE facilitates efficient spatial queries through\n',...
135 | ' "localisation" -- reducing a large O(M*N) comparison \n',...
136 | ' to a sequence of small O(m*n) operations, where m<+0) ;
252 |
253 | %-- the points in II intersect with >= 1 circle
254 | ni = length (pj) ;
255 | ii = pj([ix;ni]) ;
256 |
257 | nj = length (cj) ;
258 | ni = length (ii) ;
259 |
260 | %-- the points in II intersect with the circles
261 | %-- CJ(IP(K,1):IP(K,2)) {for point II(K)}.
262 | ip = zeros(ni,2) ;
263 | ip(:,1) = [+1; ix+1] ;
264 | ip(:,2) = [ix; nj+0] ;
265 |
266 | end
267 |
268 | function [ii,ip,cj,tr] = fastfindcirc(pc,pi)
269 | %FASTFINDCIRC find the points enclosed by a set of circles.
270 | % [II,IP,CJ] = FASTFINDCIRC(PC,PI) computes the pairwise
271 | % point-circle intersections between the points PI and
272 | % the circles PC. PC(:,1:2) are the circle centres and
273 | % PC(:,3) are the circle radii.
274 | % [II,IP,CJ] is the set of intersections in compressed
275 | % "sparse-style" indexing. Each point II(K) intersects
276 | % with the list of circles CJ(IP(K,1):IP(K,2)).
277 | %
278 | % This is the "fast" aabb-indexed variant.
279 |
280 | nd = size (pc,2)-1;
281 |
282 | %-- compute the set of aabb's for circ.
283 | bb = zeros(size(pc,1),nd*+2);
284 | for id = +1 : nd
285 | bb(:,id+nd*0) = ...
286 | pc(:,id)-pc(:,nd+1) ;
287 | bb(:,id+nd*1) = ...
288 | pc(:,id)+pc(:,nd+1) ;
289 | end
290 | %-- compute aabb-tree for set of aabb's
291 |
292 | op.nobj = +64;
293 |
294 | tr = maketree(bb,op);
295 |
296 | %-- compute tree-to-vert. indexing maps
297 | tm = mapvert (tr,pi);
298 |
299 | %-- the points in II intersect with >= 1 circle
300 | %-- the points in II intersect with the circles
301 | %-- CJ(IP(K,1):IP(K,2)) {for point II(K)}.
302 | [ii,ip,cj] = ...
303 | queryset(tr,tm,@incircle,pi,[pc(:,1:2),pc(:,3).^2]) ;
304 |
305 | end
306 |
307 | function [pj,cj] = incircle(ip,ic,pi,pc)
308 | %INCIRCLE pairwise point-circle comparison kernel function.
309 | % [PJ,CJ] = INCIRCLE(IP,IC,PI,PC) compute the pairwise in-
310 | % tersections between the points PI(IP,:) and the circles
311 | % PC(IC,:). PC(:,3) are the squared circle radii.
312 | % [PJ,CJ] are pairs of intersections, such that the point
313 | % PJ(K) intersects with the circle CJ(K).
314 |
315 | li = cell(length(ic),1);
316 | lj = cell(length(ic),1);
317 |
318 | pt = pi(ip,:) ;
319 |
320 | for ii = +1 : length(ic)
321 |
322 | di = (pt(:,1)-pc(ic(ii),1)).^2 + ...
323 | (pt(:,2)-pc(ic(ii),2)).^2 ;
324 |
325 | li{ii} = find(di<=pc(ic(ii),3));
326 | lj{ii} = ...
327 | ii * ones(length(li{ii}),1);
328 |
329 | end
330 |
331 | pj = ip(vertcat(li{:})) ;
332 | cj = ic(vertcat(lj{:})) ;
333 |
334 | end
335 |
336 | function [pc] = randcirc(nc,nd,rr)
337 | %RANDCIRC make a set of NC randomised d-circles in R^ND with
338 | %mean radius RR.
339 |
340 | pc = [rand(nc,nd), rr*(rand(nc,1))];
341 |
342 | end
343 |
344 | function drawcirc(pc)
345 | %DRAWCIRC draw a set of NC d-circles.
346 |
347 | fc = [.95,.95,.55];
348 | ec = [.25,.25,.25];
349 |
350 | switch (size(pc,2))
351 | case 3
352 | %--------------------------------------------------- circles
353 |
354 | tt = linspace(+.0,+2.*pi,24);
355 | xx = cos(tt)';
356 | yy = sin(tt)';
357 | nt = length(tt);
358 |
359 | ee = [(1:nt-1)',(2:nt-0)'];
360 |
361 | xc = cell(size(pc,1),1);
362 | yc = cell(size(pc,1),1);
363 | jc = cell(size(pc,1),1);
364 |
365 | for ic = +1 : size(pc,1)
366 |
367 | xc{ic} = pc(ic,3)*xx + pc(ic,1);
368 | yc{ic} = pc(ic,3)*yy + pc(ic,2);
369 |
370 | jc{ic} = ee + (ic-1)*nt;
371 | end
372 |
373 | pp =[vertcat(xc{:}), ...
374 | vertcat(yc{:})] ;
375 | ff = vertcat(jc{:});
376 |
377 | case 4
378 | %--------------------------------------------------- spheres
379 | %%!!todo:
380 |
381 | end
382 |
383 | patch('faces',ff,'vertices',pp,'facecolor',fc,...
384 | 'edgecolor',ec,'facealpha',+.3);
385 |
386 | end
387 |
388 |
--------------------------------------------------------------------------------
/mesh-file/loadmsh.m:
--------------------------------------------------------------------------------
1 | function [mesh] = loadmsh(name)
2 | %LOADMSH load a *.MSH file for JIGSAW.
3 | %
4 | % MESH = LOADMSH(NAME);
5 | %
6 | % The following are optionally read from "NAME.MSH". Enti-
7 | % ties are loaded if they are present in the file:
8 | %
9 | % .IF. MESH.MSHID == 'EUCLIDEAN-MESH':
10 | % -----------------------------------
11 | %
12 | % MESH.POINT.COORD - [NPxND+1] array of point coordinates,
13 | % where ND is the number of spatial dimenions.
14 | % COORD(K,ND+1) is an ID tag for the K-TH point.
15 | %
16 | % MESH.POINT.POWER - [NPx 1] array of vertex "weights",
17 | % associated with the dual "power" tessellation.
18 | %
19 | % MESH.EDGE2.INDEX - [N2x 3] array of indexing for EDGE-2
20 | % elements, where INDEX(K,1:2) is an array of
21 | % "point-indices" associated with the K-TH edge, and
22 | % INDEX(K,3) is an ID tag for the K-TH edge.
23 | %
24 | % MESH.TRIA3.INDEX - [N3x 4] array of indexing for TRIA-3
25 | % elements, where INDEX(K,1:3) is an array of
26 | % "point-indices" associated with the K-TH tria, and
27 | % INDEX(K,4) is an ID tag for the K-TH tria.
28 | %
29 | % MESH.QUAD4.INDEX - [N4x 5] array of indexing for QUAD-4
30 | % elements, where INDEX(K,1:4) is an array of
31 | % "point-indices" associated with the K-TH quad, and
32 | % INDEX(K,5) is an ID tag for the K-TH quad.
33 | %
34 | % MESH.TRIA4.INDEX - [M4x 5] array of indexing for TRIA-4
35 | % elements, where INDEX(K,1:4) is an array of
36 | % "point-indices" associated with the K-TH tria, and
37 | % INDEX(K,5) is an ID tag for the K-TH tria.
38 | %
39 | % MESH.HEXA8.INDEX - [M8x 9] array of indexing for HEXA-8
40 | % elements, where INDEX(K,1:8) is an array of
41 | % "point-indices" associated with the K-TH hexa, and
42 | % INDEX(K,9) is an ID tag for the K-TH hexa.
43 | %
44 | % MESH.WEDG6.INDEX - [M6x 7] array of indexing for WEDG-6
45 | % elements, where INDEX(K,1:6) is an array of
46 | % "point-indices" associated with the K-TH wedg, and
47 | % INDEX(K,7) is an ID tag for the K-TH wedg.
48 | %
49 | % MESH.PYRA5.INDEX - [M5x 6] array of indexing for PYRA-5
50 | % elements, where INDEX(K,1:5) is an array of
51 | % "point-indices" associated with the K-TH pyra, and
52 | % INDEX(K,6) is an ID tag for the K-TH pyra.
53 | %
54 | % MESH.BOUND.INDEX - [NBx 3] array of "boundary" indexing
55 | % in the domain, indicating how elements in the
56 | % geometry are associated with various enclosed areas
57 | % /volumes, herein known as "parts". INDEX(:,1) is an
58 | % array of "part" ID's, INDEX(:,2) is an array of
59 | % element numbering and INDEX(:,3) is an array of
60 | % element "tags", describing which element "kind" is
61 | % numbered via INDEX(:,2). Element tags are defined
62 | % via a series of constants instantiated in LIBDATA.
63 | % In the default case, where BOUND is not specified,
64 | % all elements in the geometry are assumed to define
65 | % the boundaries of enclosed "parts".
66 | %
67 | % MESH.VALUE - [NPxNV] array of "values" associated with
68 | % the vertices of the mesh.
69 | %
70 | % MESH.SLOPE - [NPx 1] array of "slopes" associated with
71 | % the vertices of the mesh. Slope values define the
72 | % gradient-limits ||dh/dx|| used by the Eikonal solver
73 | % MARCHE.
74 | %
75 | % .IF. MESH.MSHID == 'ELLIPSOID-MESH':
76 | % -----------------------------------
77 | %
78 | % MESH.RADII - [ 3x 1] array of principle ellipsoid radii.
79 | %
80 | % Additionally, entities described in the 'EUCLIDEAN-MESH'
81 | % data-type are optionally loaded.
82 | %
83 | % .IF. MESH.MSHID == 'EUCLIDEAN-GRID':
84 | % .OR. MESH.MSHID == 'ELLIPSOID-GRID':
85 | % -----------------------------------
86 | %
87 | % MESH.POINT.COORD - [NDx1] cell array of grid coordinates
88 | % where ND is the number of spatial dimenions. Each
89 | % array COORD{ID} should be a vector of grid coord.'s,
90 | % increasing or decreasing monotonically.
91 | %
92 | % MESH.VALUE - [NMxNV] array of "values" associated with
93 | % the vertices of the grid, where NM is the product of
94 | % the dimensions of the grid. NV values are associated
95 | % with each vertex.
96 | %
97 | % MESH.SLOPE - [NMx 1] array of "slopes" associated with
98 | % the vertices of the grid, where NM is the product of
99 | % the dimensions of the grid. Slope values define the
100 | % gradient-limits ||dh/dx|| used by the Eikonal solver
101 | % MARCHE.
102 | %
103 | % See also JIGSAW, SAVEMSH
104 | %
105 |
106 | %-----------------------------------------------------------
107 | % Darren Engwirda
108 | % github.com/dengwirda/jigsaw-matlab
109 | % 20-Aug-2019
110 | % darren.engwirda@columbia.edu
111 | %-----------------------------------------------------------
112 | %
113 |
114 | mesh = [] ;
115 |
116 | try
117 |
118 | ffid = fopen(name,'r') ;
119 |
120 | real = '%f;' ;
121 | ints = '%i;' ;
122 |
123 | kind = 'EUCLIDEAN-MESH';
124 |
125 | if (ffid < +0)
126 | error(['File not found: ', name]) ;
127 | end
128 |
129 | nver = +0 ;
130 | ndim = +0 ;
131 |
132 | while (true)
133 |
134 | %-- read next line from file
135 | lstr = fgetl(ffid);
136 |
137 | if (ischar(lstr) )
138 |
139 | if (length(lstr) > +0 && lstr(1) ~= '#')
140 |
141 | %-- tokenise line about '=' character
142 | tstr = regexp(lower(lstr),'=','split');
143 |
144 | if (length(tstr) ~= +2)
145 | warning(['Invalid tag: ',lstr]);
146 | continue;
147 | end
148 |
149 | switch (strtrim(tstr{1}))
150 | case 'mshid'
151 |
152 | %-- read "MSHID" data
153 |
154 | stag = regexp(tstr{2},';','split');
155 |
156 | nver = str2double(stag{1}) ;
157 |
158 | if (length(stag) >= +2)
159 | kind = ...
160 | strtrim(upper(stag{2}));
161 | end
162 |
163 | case 'ndims'
164 |
165 | %-- read "NDIMS" data
166 |
167 | ndim = str2double(tstr{2}) ;
168 |
169 | case 'radii'
170 |
171 | %-- read "RADII" data
172 |
173 | stag = regexp(tstr{2},';','split');
174 |
175 | if (length(stag) == +3)
176 |
177 | mesh.radii = [ ...
178 | str2double(stag{1}), ...
179 | str2double(stag{2}), ...
180 | str2double(stag{3}) ...
181 | ] ;
182 |
183 | else
184 |
185 | warning(['Invalid RADII: ', lstr]);
186 | continue;
187 |
188 | end
189 |
190 | case 'point'
191 |
192 | %-- read "POINT" data
193 |
194 | nnum = str2double(tstr{2}) ;
195 |
196 | numr = nnum*(ndim+1);
197 |
198 | data = ...
199 | fscanf(ffid,[repmat(real,1,ndim),'%i'],numr);
200 |
201 | if (ndim == +2)
202 | mesh.point.coord = [ ...
203 | data(1:3:end), ...
204 | data(2:3:end), ...
205 | data(3:3:end)] ;
206 | end
207 | if (ndim == +3)
208 | mesh.point.coord = [ ...
209 | data(1:4:end), ...
210 | data(2:4:end), ...
211 | data(3:4:end), ...
212 | data(4:4:end)] ;
213 | end
214 |
215 | case 'coord'
216 |
217 | %-- read "COORD" data
218 |
219 | stag = regexp(tstr{2},';','split');
220 |
221 | idim = str2double(stag{1}) ;
222 | cnum = str2double(stag{2}) ;
223 |
224 | ndim = max(ndim,idim);
225 |
226 | data = fscanf(ffid,'%f',cnum) ;
227 |
228 | mesh.point.coord{idim}= data ;
229 |
230 | case 'edge2'
231 |
232 | %-- read "EDGE2" data
233 |
234 | nnum = str2double(tstr{2}) ;
235 |
236 | numr = nnum * 3;
237 |
238 | data = ...
239 | fscanf(ffid,[repmat(ints,1,2),'%i'],numr);
240 |
241 | mesh.edge2.index = [ ...
242 | data(1:3:end), ...
243 | data(2:3:end), ...
244 | data(3:3:end)] ;
245 |
246 | mesh.edge2.index(:,1:2) = ...
247 | mesh.edge2.index(:,1:2) + 1;
248 |
249 | case 'tria3'
250 |
251 | %-- read "TRIA3" data
252 |
253 | nnum = str2double(tstr{2}) ;
254 |
255 | numr = nnum * 4;
256 |
257 | data = ...
258 | fscanf(ffid,[repmat(ints,1,3),'%i'],numr);
259 |
260 | mesh.tria3.index = [ ...
261 | data(1:4:end), ...
262 | data(2:4:end), ...
263 | data(3:4:end), ...
264 | data(4:4:end)] ;
265 |
266 | mesh.tria3.index(:,1:3) = ...
267 | mesh.tria3.index(:,1:3) + 1;
268 |
269 | case 'quad4'
270 |
271 | %-- read "QUAD4" data
272 |
273 | nnum = str2double(tstr{2}) ;
274 |
275 | numr = nnum * 5;
276 |
277 | data = ...
278 | fscanf(ffid,[repmat(ints,1,4),'%i'],numr);
279 |
280 | mesh.quad4.index = [ ...
281 | data(1:5:end), ...
282 | data(2:5:end), ...
283 | data(3:5:end), ...
284 | data(4:5:end), ...
285 | data(5:5:end)] ;
286 |
287 | mesh.quad4.index(:,1:4) = ...
288 | mesh.quad4.index(:,1:4) + 1;
289 |
290 | case 'tria4'
291 |
292 | %-- read "TRIA4" data
293 |
294 | nnum = str2double(tstr{2}) ;
295 |
296 | numr = nnum * 5;
297 |
298 | data = ...
299 | fscanf(ffid,[repmat(ints,1,4),'%i'],numr);
300 |
301 | mesh.tria4.index = [ ...
302 | data(1:5:end), ...
303 | data(2:5:end), ...
304 | data(3:5:end), ...
305 | data(4:5:end), ...
306 | data(5:5:end)] ;
307 |
308 | mesh.tria4.index(:,1:4) = ...
309 | mesh.tria4.index(:,1:4) + 1;
310 |
311 | case 'hexa8'
312 |
313 | %-- read "HEXA8" data
314 |
315 | nnum = str2double(tstr{2}) ;
316 |
317 | numr = nnum * 9;
318 |
319 | data = ...
320 | fscanf(ffid,[repmat(ints,1,8),'%i'],numr);
321 |
322 | mesh.hexa8.index = [ ...
323 | data(1:9:end), ...
324 | data(2:9:end), ...
325 | data(3:9:end), ...
326 | data(4:9:end), ...
327 | data(5:9:end), ...
328 | data(6:9:end), ...
329 | data(7:9:end), ...
330 | data(8:9:end), ...
331 | data(9:9:end)] ;
332 |
333 | mesh.hexa8.index(:,1:8) = ...
334 | mesh.hexa8.index(:,1:8) + 1;
335 |
336 | case 'wedg6'
337 |
338 | %-- read "WEDG6" data
339 |
340 | nnum = str2double(tstr{2}) ;
341 |
342 | numr = nnum * 7;
343 |
344 | data = ...
345 | fscanf(ffid,[repmat(ints,1,6),'%i'],numr);
346 |
347 | mesh.wedg6.index = [ ...
348 | data(1:7:end), ...
349 | data(2:7:end), ...
350 | data(3:7:end), ...
351 | data(4:7:end), ...
352 | data(5:7:end), ...
353 | data(6:7:end), ...
354 | data(7:7:end)] ;
355 |
356 | mesh.wedg6.index(:,1:6) = ...
357 | mesh.wedg6.index(:,1:6) + 1;
358 |
359 | case 'pyra5'
360 |
361 | %-- read "PYRA5" data
362 |
363 | nnum = str2double(tstr{2}) ;
364 |
365 | numr = nnum * 6;
366 |
367 | data = ...
368 | fscanf(ffid,[repmat(ints,1,5),'%i'],numr);
369 |
370 | mesh.pyra5.index = [ ...
371 | data(1:6:end), ...
372 | data(2:6:end), ...
373 | data(3:6:end), ...
374 | data(4:6:end), ...
375 | data(5:6:end), ...
376 | data(6:6:end)] ;
377 |
378 | mesh.pyra5.index(:,1:5) = ...
379 | mesh.pyra5.index(:,1:5) + 1;
380 |
381 | case 'bound'
382 |
383 | %-- read "BOUND" data
384 |
385 | nnum = str2double(tstr{2}) ;
386 |
387 | numr = nnum * 3;
388 |
389 | data = ...
390 | fscanf(ffid,[repmat(ints,1,2),'%i'],numr);
391 |
392 | mesh.bound.index = [ ...
393 | data(1:3:end), ...
394 | data(2:3:end), ...
395 | data(3:3:end)] ;
396 |
397 | mesh.bound.index(:,2:2) = ...
398 | mesh.bound.index(:,2:2) + 1;
399 |
400 | case 'value'
401 |
402 | %-- read "VALUE" data
403 |
404 | stag = regexp(tstr{2},';','split');
405 |
406 | if (length(stag) == +2)
407 |
408 | nnum = str2double(stag{1}) ;
409 | vnum = str2double(stag{2}) ;
410 |
411 | else
412 |
413 | warning(['Invalid VALUE: ', lstr]);
414 | continue;
415 |
416 | end
417 |
418 | numr = nnum * (vnum+1) ;
419 |
420 | fstr = repmat(real,1,vnum) ;
421 |
422 | data = fscanf( ...
423 | ffid,fstr(1:end-1),numr) ;
424 |
425 | mesh.value = zeros(nnum, vnum);
426 |
427 | for vpos = +1 : vnum
428 |
429 | mesh.value(:,vpos) = ...
430 | data(vpos:vnum:end) ;
431 |
432 | end
433 |
434 | case 'power'
435 |
436 | %-- read "POWER" data
437 |
438 | stag = regexp(tstr{2},';','split');
439 |
440 | if (length(stag) == +2)
441 |
442 | nnum = str2double(stag{1}) ;
443 | pnum = str2double(stag{2}) ;
444 |
445 | else
446 |
447 | warning(['Invalid POWER: ', lstr]);
448 | continue;
449 |
450 | end
451 |
452 | numr = nnum * (pnum+0) ;
453 |
454 | fstr = repmat(real,1,pnum) ;
455 |
456 | data = fscanf( ...
457 | ffid,fstr(1:end-1),numr) ;
458 |
459 | mesh.point.power = ...
460 | zeros(nnum, pnum) ;
461 |
462 | for ppos = +1 : pnum
463 |
464 | mesh.point.power(:,ppos) = ...
465 | data(ppos:pnum:end) ;
466 |
467 | end
468 |
469 | case 'slope'
470 |
471 | %-- read "SLOPE" data
472 |
473 | stag = regexp(tstr{2},';','split');
474 |
475 | if (length(stag) == +2)
476 |
477 | nnum = str2double(stag{1}) ;
478 | vnum = str2double(stag{2}) ;
479 |
480 | else
481 |
482 | warning(['Invalid SLOPE: ', lstr]);
483 | continue;
484 |
485 | end
486 |
487 | numr = nnum * (vnum+0) ;
488 |
489 | fstr = repmat(real,1,vnum) ;
490 |
491 | data = fscanf( ...
492 | ffid,fstr(1:end-1),numr) ;
493 |
494 | mesh.slope = ...
495 | zeros(nnum, vnum) ;
496 |
497 | for ppos = +1 : vnum
498 |
499 | mesh.slope(:,ppos) = ...
500 | data(ppos:vnum:end) ;
501 |
502 | end
503 |
504 |
505 |
506 | end
507 |
508 | end
509 |
510 | else
511 | %-- if(~ischar(lstr)) //i.e. end-of-file
512 | break;
513 | end
514 |
515 | end
516 |
517 | mesh.mshID = kind ;
518 | mesh.fileV = nver ;
519 |
520 | if (ndim > +0)
521 | switch (lower(mesh.mshID))
522 | case{'euclidean-grid', ...
523 | 'ellipsoid-grid'}
524 | if (inspect(mesh,'value') && ...
525 | inspect(mesh,'point') )
526 |
527 | if (ndim == +2)
528 |
529 | %-- reshape data to 2-dim. array
530 | mesh.value = reshape( ...
531 | mesh.value, ...
532 | length(mesh.point.coord{2}), ...
533 | length(mesh.point.coord{1}), ...
534 | [] ) ;
535 |
536 | elseif (ndim == +3)
537 |
538 | %-- reshape data to 3-dim. array
539 | mesh.value = reshape( ...
540 | mesh.value, ...
541 | length(mesh.point.coord{2}), ...
542 | length(mesh.point.coord{1}), ...
543 | length(mesh.point.coord{3}), ...
544 | [] ) ;
545 |
546 | end
547 |
548 | end
549 |
550 | if (inspect(mesh,'slope') && ...
551 | inspect(mesh,'point') )
552 |
553 | if (ndim == +2)
554 |
555 | %-- reshape data to 2-dim. array
556 | mesh.slope = reshape( ...
557 | mesh.slope, ...
558 | length(mesh.point.coord{2}), ...
559 | length(mesh.point.coord{1}), ...
560 | [] ) ;
561 |
562 | elseif (ndim == +3)
563 |
564 | %-- reshape data to 3-dim. array
565 | mesh.slope = reshape( ...
566 | mesh.slope, ...
567 | length(mesh.point.coord{2}), ...
568 | length(mesh.point.coord{1}), ...
569 | length(mesh.point.coord{3}), ...
570 | [] ) ;
571 |
572 | end
573 |
574 | end
575 | end
576 | end
577 |
578 | fclose(ffid) ;
579 |
580 | catch err
581 |
582 | %-- ensure that we close the file regardless!
583 | if (ffid>-1)
584 | fclose(ffid) ;
585 | end
586 |
587 | rethrow(err) ;
588 |
589 | end
590 |
591 | end
592 |
593 |
594 |
595 |
--------------------------------------------------------------------------------
/mesh-file/certify.m:
--------------------------------------------------------------------------------
1 | function [flag] = certify(mesh)
2 | %CERTIFY error checking for JIGSAW mesh objects.
3 |
4 | %-----------------------------------------------------------
5 | % Darren Engwirda
6 | % github.com/dengwirda/jigsaw-matlab
7 | % 07-Aug-2019
8 | % darren.engwirda@columbia.edu
9 | %-----------------------------------------------------------
10 | %
11 |
12 | np = +0 ; flag = -1;
13 |
14 | if (~isstruct(mesh))
15 | error('certify:incorrectInputClass', ...
16 | 'Incorrect input class.') ;
17 | end
18 |
19 | if (inspect(mesh,'point'))
20 | if (~isempty (mesh.point.coord))
21 | if (isnumeric(mesh.point.coord))
22 | %----------------------------------------- check MESH coords
23 | np = size(mesh.point.coord,1) ;
24 |
25 | if (ndims(mesh.point.coord) ~= 2)
26 | error('certify:incorrectDimensions', ...
27 | 'Invalid POINT.COORD dimensions.') ;
28 | end
29 | if ( size(mesh.point.coord,2)< 3)
30 | error('certify:incorrectDimensions', ...
31 | 'Invalid POINT.COORD dimensions.') ;
32 | end
33 |
34 | if (any(isinf(mesh.point.coord)))
35 | error('certify:invalidMeshPosition', ...
36 | 'Invalid POINT.COORD values.') ;
37 | end
38 | if (any(isnan(mesh.point.coord)))
39 | error('certify:invalidMeshPosition', ...
40 | 'Invalid POINT.COORD values.') ;
41 | end
42 |
43 | if (isfield(mesh,'mshID'))
44 | if (strcmpi(mesh.mshID,'euclidean-grid'))
45 | error('certify:incompatiblemshID', ...
46 | 'Incompatible msh-ID flag.') ;
47 | end
48 | if (strcmpi(mesh.mshID,'ellipsoid-grid'))
49 | error('certify:incompatiblemshID', ...
50 | 'Incompatible msh-ID flag.') ;
51 | end
52 | end
53 |
54 | elseif(iscell(mesh.point.coord))
55 | %----------------------------------------- check GRID coords
56 | if (~isvector(mesh.point.coord))
57 | error('certify:incorrectDimensions', ...
58 | 'Invalid POINT.COORD dimensions.') ;
59 | end
60 |
61 | for ii = +1:length(mesh.point.coord)
62 |
63 | if (~isvector(mesh.point.coord{ii}))
64 | error('certify:incorrectDimensions', ...
65 | 'Invalid POINT.COORD dimensions.') ;
66 | end
67 | if (any(isinf(mesh.point.coord{ii})))
68 | error('certify:invalidMeshPosition', ...
69 | 'Invalid POINT.COORD values.') ;
70 | end
71 | if (any(isnan(mesh.point.coord{ii})))
72 | error('certify:invalidMeshPosition', ...
73 | 'Invalid POINT.COORD values.') ;
74 | end
75 |
76 | end
77 |
78 | if (isfield(mesh,'mshID'))
79 | if (strcmpi(mesh.mshID,'euclidean-mesh'))
80 | error('certify:incompatiblemshID', ...
81 | 'Incompatible msh-ID flag.') ;
82 | end
83 | if (strcmpi(mesh.mshID,'ellipsoid-mesh'))
84 | error('certify:incompatiblemshID', ...
85 | 'Incompatible msh-ID flag.') ;
86 | end
87 | end
88 |
89 | else
90 | %----------------------------------------- wrong POINT class
91 | error('certify:incorrectInputClass', ...
92 | 'Invalid POINT.COORD type.') ;
93 |
94 | end
95 | end
96 | end
97 |
98 | if (inspect(mesh,'radii'))
99 | %----------------------------------------- check RADII value
100 | if (~isempty (mesh.radii))
101 | if (~isnumeric(mesh.radii))
102 | error('certify:incorrectInputClass', ...
103 | 'Invalid RADII class.') ;
104 | end
105 | if (~isvector(mesh.radii))
106 | error('certify:incorrectDimensions', ...
107 | 'Invalid RADII dimensions.') ;
108 | end
109 | if (length(mesh.radii) ~= +1 && ...
110 | length(mesh.radii) ~= +3 )
111 | error('certify:incorrectDimensions', ...
112 | 'Invalid RADII dimensions.') ;
113 | end
114 | if (any(isinf(mesh.radii)))
115 | error('certify:invalidRadiiEntries', ...
116 | 'Invalid RADII entries.') ;
117 | end
118 | if (any(isnan(mesh.radii)))
119 | error('certify:invalidRadiiEntries', ...
120 | 'Invalid RADII entries.') ;
121 | end
122 | end
123 | end
124 |
125 | if (inspect(mesh,'value'))
126 | %----------------------------------------- check VALUE value
127 | if (~isempty (mesh.value))
128 | if (isnumeric(mesh.point.coord))
129 | %----------------------------------------- for MESH obj kind
130 |
131 | if (ndims(mesh.value) ~= 2)
132 | error('certify:incorrectDimensions', ...
133 | 'Invalid VALUE dimensions.') ;
134 | end
135 | if ( size(mesh.value,1) ~= np)
136 | error('certify:incorrectDimensions', ...
137 | 'Invalid VALUE dimensions.') ;
138 | end
139 |
140 | if (any(isinf(mesh.value)))
141 | error('certify:invalidValueEntries', ...
142 | 'Invalid VALUE entries.') ;
143 | end
144 | if (any(isnan(mesh.value)))
145 | error('certify:invalidValueEntries', ...
146 | 'Invalid VALUE entries.') ;
147 | end
148 |
149 | elseif(iscell(mesh.point.coord))
150 | %----------------------------------------- for GRID obj kind
151 |
152 | if (length(mesh.point.coord) ~= ...
153 | ndims (mesh.value))
154 | error('certify:incorrectDimensions', ...
155 | 'Invalid VALUE dimensions.') ;
156 | end
157 |
158 | if (length(mesh.point.coord) == 2)
159 |
160 | if (isvector(mesh.value))
161 | if (length(mesh.point.coord{2}) ...
162 | *length(mesh.point.coord{1}) ...
163 | ~= numel(mesh.value))
164 | error('certify:incorrectDimensions', ...
165 | 'Invalid VALUE dimensions.') ;
166 | end
167 | else
168 | if (length(mesh.point.coord{2}) ...
169 | ~= size(mesh.value,1) || ...
170 | length(mesh.point.coord{1}) ...
171 | ~= size(mesh.value,2) )
172 | error('certify:incorrectDimensions', ...
173 | 'Invalid VALUE dimensions.') ;
174 | end
175 | end
176 |
177 | end
178 |
179 | if (length(mesh.point.coord) == 3)
180 |
181 | if (isvector(mesh.value))
182 | if (length(mesh.point.coord{2}) ...
183 | *length(mesh.point.coord{1}) ...
184 | *length(mesh.point.coord{3}) ...
185 | ~= numel(mesh.value))
186 | error('certify:incorrectDimensions', ...
187 | 'Invalid VALUE dimensions.') ;
188 | end
189 | else
190 | if (length(mesh.point.coord{2}) ...
191 | ~= size(mesh.value,1) || ...
192 | length(mesh.point.coord{1}) ...
193 | ~= size(mesh.value,2) || ...
194 | length(mesh.point.coord{3}) ...
195 | ~= size(mesh.value,3) )
196 | error('certify:incorrectDimensions', ...
197 | 'Invalid VALUE dimensions.') ;
198 | end
199 | end
200 |
201 | end
202 |
203 | if (any(isinf(mesh.value)))
204 | error('certify:invalidValueEntries', ...
205 | 'Invalid VALUE entries.') ;
206 | end
207 | if (any(isnan(mesh.value)))
208 | error('certify:invalidValueEntries', ...
209 | 'Invalid VALUE entries.') ;
210 | end
211 |
212 | else
213 | %----------------------------------------- wrong VALUE class
214 | error('certify:incorrectInputClass', ...
215 | 'Invalid VALUE class.') ;
216 |
217 | end
218 | end
219 | end
220 |
221 | if (inspect(mesh,'slope'))
222 | %----------------------------------------- check SLOPE value
223 | if (~isempty (mesh.slope))
224 | if (isnumeric(mesh.point.coord))
225 | %----------------------------------------- for MESH obj kind
226 |
227 | if (ndims(mesh.slope) ~= 2)
228 | error('certify:incorrectDimensions', ...
229 | 'Invalid SLOPE dimensions.') ;
230 | end
231 | if ( size(mesh.slope,1) ~= np)
232 | error('certify:incorrectDimensions', ...
233 | 'Invalid SLOPE dimensions.') ;
234 | end
235 |
236 | if (any(isinf(mesh.slope)))
237 | error('certify:invalidValueEntries', ...
238 | 'Invalid SLOPE entries.') ;
239 | end
240 | if (any(isnan(mesh.slope)))
241 | error('certify:invalidValueEntries', ...
242 | 'Invalid SLOPE entries.') ;
243 | end
244 |
245 | elseif(iscell(mesh.point.coord))
246 | %----------------------------------------- for GRID obj kind
247 |
248 | if (length(mesh.point.coord) ~= ...
249 | ndims (mesh.slope))
250 | error('certify:incorrectDimensions', ...
251 | 'Invalid SLOPE dimensions.') ;
252 | end
253 |
254 | if (length(mesh.point.coord) == 2)
255 |
256 | if (isvector(mesh.slope))
257 | if (length(mesh.point.coord{2}) ...
258 | *length(mesh.point.coord{1}) ...
259 | ~= numel(mesh.slope))
260 | error('certify:incorrectDimensions', ...
261 | 'Invalid SLOPE dimensions.') ;
262 | end
263 | else
264 | if (length(mesh.point.coord{2}) ...
265 | ~= size(mesh.slope,1) || ...
266 | length(mesh.point.coord{1}) ...
267 | ~= size(mesh.slope,2) )
268 | error('certify:incorrectDimensions', ...
269 | 'Invalid SLOPE dimensions.') ;
270 | end
271 | end
272 |
273 | end
274 |
275 | if (length(mesh.point.coord) == 3)
276 |
277 | if (isvector(mesh.slope))
278 | if (length(mesh.point.coord{2}) ...
279 | *length(mesh.point.coord{1}) ...
280 | *length(mesh.point.coord{3}) ...
281 | ~= numel(mesh.slope))
282 | error('certify:incorrectDimensions', ...
283 | 'Invalid SLOPE dimensions.') ;
284 | end
285 | else
286 | if (length(mesh.point.coord{2}) ...
287 | ~= size(mesh.slope,1) || ...
288 | length(mesh.point.coord{1}) ...
289 | ~= size(mesh.slope,2) || ...
290 | length(mesh.point.coord{3}) ...
291 | ~= size(mesh.slope,3) )
292 | error('certify:incorrectDimensions', ...
293 | 'Invalid SLOPE dimensions.') ;
294 | end
295 | end
296 |
297 | end
298 |
299 | if (any(isinf(mesh.slope)))
300 | error('certify:invalidValueEntries', ...
301 | 'Invalid SLOPE entries.') ;
302 | end
303 | if (any(isnan(mesh.slope)))
304 | error('certify:invalidValueEntries', ...
305 | 'Invalid SLOPE entries.') ;
306 | end
307 |
308 | else
309 | %----------------------------------------- wrong VALUE class
310 | error('certify:incorrectInputClass', ...
311 | 'Invalid SLOPE class.') ;
312 |
313 | end
314 | end
315 | end
316 |
317 | if (inspect(mesh,'edge2'))
318 | %----------------------------------------- check EDGE2 index
319 | if (~isempty (mesh.edge2.index))
320 | if (~isnumeric(mesh.edge2.index))
321 | error('certify:incorrectInputClass', ...
322 | 'Invalid EDGE2.INDEX type.') ;
323 | end
324 | if (ndims(mesh.edge2.index) ~= 2)
325 | error('certify:incorrectDimensions', ...
326 | 'Invalid EDGE2.INDEX dimensions.') ;
327 | end
328 | if (size(mesh.edge2.index,2)~= 3)
329 | error('certify:incorrectDimensions', ...
330 | 'Invalid EDGE2.INDEX dimensions.') ;
331 | end
332 | if (any(isinf(mesh.edge2.index)))
333 | error('certify:invalidMeshIndexing', ...
334 | 'Invalid EDGE2.INDEX indexing.') ;
335 | end
336 | if (any(isnan(mesh.edge2.index)))
337 | error('certify:invalidMeshIndexing', ...
338 | 'Invalid EDGE2.INDEX indexing.') ;
339 | end
340 | if (min(min( ...
341 | mesh.edge2.index(:,1:2))) < +1 || ...
342 | max(max( ...
343 | mesh.edge2.index(:,1:2))) > np )
344 | error('certify:invalidMeshIndexing', ...
345 | 'Invalid EDGE2.INDEX indexing.') ;
346 | end
347 | end
348 | end
349 |
350 | if (inspect(mesh,'tria3'))
351 | %----------------------------------------- check TRIA3 index
352 | if (~isempty (mesh.tria3.index))
353 | if (~isnumeric(mesh.tria3.index))
354 | error('certify:incorrectInputClass', ...
355 | 'Invalid TRIA3.INDEX type.') ;
356 | end
357 | if (ndims(mesh.tria3.index) ~= 2)
358 | error('certify:incorrectDimensions', ...
359 | 'Invalid TRIA3.INDEX dimensions.') ;
360 | end
361 | if (size(mesh.tria3.index,2)~= 4)
362 | error('certify:incorrectDimensions', ...
363 | 'Invalid TRIA3.INDEX dimensions.') ;
364 | end
365 | if (any(isinf(mesh.tria3.index)))
366 | error('certify:invalidMeshIndexing', ...
367 | 'Invalid TRIA3.INDEX indexing.') ;
368 | end
369 | if (any(isnan(mesh.tria3.index)))
370 | error('certify:invalidMeshIndexing', ...
371 | 'Invalid TRIA3.INDEX indexing.') ;
372 | end
373 | if (min(min( ...
374 | mesh.tria3.index(:,1:3))) < +1 || ...
375 | max(max( ...
376 | mesh.tria3.index(:,1:3))) > np )
377 | error('certify:invalidMeshIndexing', ...
378 | 'Invalid TRIA3.INDEX indexing.') ;
379 | end
380 | end
381 | end
382 |
383 | if (inspect(mesh,'quad4'))
384 | %----------------------------------------- check QUAD4 index
385 | if (~isempty (mesh.quad4.index))
386 | if (~isnumeric(mesh.quad4.index))
387 | error('certify:incorrectInputClass', ...
388 | 'Invalid QUAD4.INDEX type.') ;
389 | end
390 | if (ndims(mesh.quad4.index) ~= 2)
391 | error('certify:incorrectDimensions', ...
392 | 'Invalid QUAD4.INDEX dimensions.') ;
393 | end
394 | if (size(mesh.quad4.index,2)~= 5)
395 | error('certify:incorrectDimensions', ...
396 | 'Invalid QUAD4.INDEX dimensions.') ;
397 | end
398 | if (any(isinf(mesh.quad4.index)))
399 | error('certify:invalidMeshIndexing', ...
400 | 'Invalid QUAD4.INDEX indexing.') ;
401 | end
402 | if (any(isnan(mesh.quad4.index)))
403 | error('certify:invalidMeshIndexing', ...
404 | 'Invalid QUAD4.INDEX indexing.') ;
405 | end
406 | if (min(min( ...
407 | mesh.quad4.index(:,1:4))) < +1 || ...
408 | max(max( ...
409 | mesh.quad4.index(:,1:4))) > np )
410 | error('certify:invalidMeshIndexing', ...
411 | 'Invalid QUAD4.INDEX indexing.') ;
412 | end
413 | end
414 | end
415 |
416 | if (inspect(mesh,'tria4'))
417 | %----------------------------------------- check TRIA4 index
418 | if (~isempty (mesh.tria4.index))
419 | if (~isnumeric(mesh.tria4.index))
420 | error('certify:incorrectInputClass', ...
421 | 'Invalid TRIA4.INDEX type.') ;
422 | end
423 | if (ndims(mesh.tria4.index) ~= 2)
424 | error('certify:incorrectDimensions', ...
425 | 'Invalid TRIA4.INDEX dimensions.') ;
426 | end
427 | if (size(mesh.tria4.index,2)~= 5)
428 | error('certify:incorrectDimensions', ...
429 | 'Invalid TRIA4.INDEX dimensions.') ;
430 | end
431 | if (any(isinf(mesh.tria4.index)))
432 | error('certify:invalidMeshIndexing', ...
433 | 'Invalid TRIA4.INDEX indexing.') ;
434 | end
435 | if (any(isnan(mesh.tria4.index)))
436 | error('certify:invalidMeshIndexing', ...
437 | 'Invalid TRIA4.INDEX indexing.') ;
438 | end
439 | if (min(min( ...
440 | mesh.tria4.index(:,1:4))) < +1 || ...
441 | max(max( ...
442 | mesh.tria4.index(:,1:4))) > np )
443 | error('certify:invalidMeshIndexing', ...
444 | 'Invalid TRIA4.INDEX indexing.') ;
445 | end
446 | end
447 | end
448 |
449 | if (inspect(mesh,'hexa8'))
450 | %----------------------------------------- check HEXA8 index
451 | if (~isempty (mesh.hexa8.index))
452 | if (~isnumeric(mesh.hexa8.index))
453 | error('certify:incorrectInputClass', ...
454 | 'Invalid HEXA8.INDEX type.') ;
455 | end
456 | if (ndims(mesh.hexa8.index) ~= 2)
457 | error('certify:incorrectDimensions', ...
458 | 'Invalid HEXA8.INDEX dimensions.') ;
459 | end
460 | if (size(mesh.hexa8.index,2)~= 9)
461 | error('certify:incorrectDimensions', ...
462 | 'Invalid HEXA8.INDEX dimensions.') ;
463 | end
464 | if (any(isinf(mesh.hexa8.index)))
465 | error('certify:invalidMeshIndexing', ...
466 | 'Invalid HEXA8.INDEX indexing.') ;
467 | end
468 | if (any(isnan(mesh.hexa8.index)))
469 | error('certify:invalidMeshIndexing', ...
470 | 'Invalid HEXA8.INDEX indexing.') ;
471 | end
472 | if (min(min( ...
473 | mesh.hexa8.index(:,1:8))) < +1 || ...
474 | max(max( ...
475 | mesh.hexa8.index(:,1:8))) > np )
476 | error('certify:invalidMeshIndexing', ...
477 | 'Invalid HEXA8.INDEX indexing.') ;
478 | end
479 | end
480 | end
481 |
482 | if (inspect(mesh,'wedg6'))
483 | %----------------------------------------- check WEDG6 index
484 | if (~isempty (mesh.wedg6.index))
485 | if (~isnumeric(mesh.wedg6.index))
486 | error('certify:incorrectInputClass', ...
487 | 'Invalid WEDG6.INDEX type.') ;
488 | end
489 | if (ndims(mesh.wedg6.index) ~= 2)
490 | error('certify:incorrectDimensions', ...
491 | 'Invalid WEDG6.INDEX dimensions.') ;
492 | end
493 | if (size(mesh.wedg6.index,2)~= 7)
494 | error('certify:incorrectDimensions', ...
495 | 'Invalid WEDG6.INDEX dimensions.') ;
496 | end
497 | if (any(isinf(mesh.wedg6.index)))
498 | error('certify:invalidMeshIndexing', ...
499 | 'Invalid WEDG6.INDEX indexing.') ;
500 | end
501 | if (any(isnan(mesh.wedg6.index)))
502 | error('certify:invalidMeshIndexing', ...
503 | 'Invalid WEDG6.INDEX indexing.') ;
504 | end
505 | if (min(min( ...
506 | mesh.wedg6.index(:,1:6))) < +1 || ...
507 | max(max( ...
508 | mesh.wedg6.index(:,1:6))) > np )
509 | error('certify:invalidMeshIndexing', ...
510 | 'Invalid WEDG6.INDEX indexing.') ;
511 | end
512 | end
513 | end
514 |
515 | if (inspect(mesh,'pyra5'))
516 | %----------------------------------------- check PYRA5 index
517 | if (~isempty (mesh.pyra5.index))
518 | if (~isnumeric(mesh.pyra5.index))
519 | error('certify:incorrectInputClass', ...
520 | 'Invalid PYRA5.INDEX type.') ;
521 | end
522 | if (ndims(mesh.pyra5.index) ~= 2)
523 | error('certify:incorrectDimensions', ...
524 | 'Invalid PYRA5.INDEX dimensions.') ;
525 | end
526 | if (size(mesh.pyra5.index,2)~= 6)
527 | error('certify:incorrectDimensions', ...
528 | 'Invalid PYRA5.INDEX dimensions.') ;
529 | end
530 | if (any(isinf(mesh.pyra5.index)))
531 | error('certify:invalidMeshIndexing', ...
532 | 'Invalid PYRA5.INDEX indexing.') ;
533 | end
534 | if (any(isnan(mesh.pyra5.index)))
535 | error('certify:invalidMeshIndexing', ...
536 | 'Invalid PYRA5.INDEX indexing.') ;
537 | end
538 | if (min(min( ...
539 | mesh.pyra5.index(:,1:5))) < +1 || ...
540 | max(max( ...
541 | mesh.pyra5.index(:,1:5))) > np )
542 | error('certify:invalidMeshIndexing', ...
543 | 'Invalid PYRA5.INDEX indexing.') ;
544 | end
545 | end
546 | end
547 |
548 | if (inspect(mesh,'bound'))
549 | %----------------------------------------- check BOUND index
550 | if (~isempty (mesh.bound.index))
551 | if (~isnumeric(mesh.bound.index))
552 | error('certify:incorrectInputClass', ...
553 | 'Invalid BOUND.INDEX type.') ;
554 | end
555 | if (ndims(mesh.bound.index) ~= 2)
556 | error('certify:incorrectDimensions', ...
557 | 'Invalid BOUND.INDEX dimensions.') ;
558 | end
559 | if (size(mesh.bound.index,2)~= 3)
560 | error('certify:incorrectDimensions', ...
561 | 'Invalid BOUND.INDEX dimensions.') ;
562 | end
563 | if (any(isinf(mesh.bound.index)))
564 | error('certify:invalidMeshIndexing', ...
565 | 'Invalid BOUND.INDEX indexing.') ;
566 | end
567 | if (any(isnan(mesh.bound.index)))
568 | error('certify:invalidMeshIndexing', ...
569 | 'Invalid BOUND.INDEX indexing.') ;
570 | end
571 | if (min(min( ...
572 | mesh.bound.index(:,2:2))) < +1 )
573 | error('certify:invalidMeshIndexing', ...
574 | 'Invalid BOUND.INDEX indexing.') ;
575 | end
576 | end
577 | end
578 |
579 | %----------------------------------------- ok if we get here
580 | flag = +1 ;
581 |
582 | end
583 |
584 |
585 |
586 |
--------------------------------------------------------------------------------
/mesh-file/savemsh.m:
--------------------------------------------------------------------------------
1 | function savemsh(name,mesh)
2 | %SAVEMSH save a *.MSH file for JIGSAW.
3 | %
4 | % SAVEMSH(NAME,MESH);
5 | %
6 | % The following are optionally written to "NAME.MSH". Ent-
7 | % ities are written if they are present in MESH:
8 | %
9 | % .IF. MESH.MSHID == 'EUCLIDEAN-MESH':
10 | % -----------------------------------
11 | %
12 | % MESH.POINT.COORD - [NPxND+1] array of point coordinates,
13 | % where ND is the number of spatial dimenions.
14 | % COORD(K,ND+1) is an ID tag for the K-TH point.
15 | %
16 | % MESH.POINT.POWER - [NPx 1] array of vertex "weights",
17 | % associated with the dual "power" tessellation.
18 | %
19 | % MESH.EDGE2.INDEX - [N2x 3] array of indexing for EDGE-2
20 | % elements, where INDEX(K,1:2) is an array of
21 | % "point-indices" associated with the K-TH edge, and
22 | % INDEX(K,3) is an ID tag for the K-TH edge.
23 | %
24 | % MESH.TRIA3.INDEX - [N3x 4] array of indexing for TRIA-3
25 | % elements, where INDEX(K,1:3) is an array of
26 | % "point-indices" associated with the K-TH tria, and
27 | % INDEX(K,4) is an ID tag for the K-TH tria.
28 | %
29 | % MESH.QUAD4.INDEX - [N4x 5] array of indexing for QUAD-4
30 | % elements, where INDEX(K,1:4) is an array of
31 | % "point-indices" associated with the K-TH quad, and
32 | % INDEX(K,5) is an ID tag for the K-TH quad.
33 | %
34 | % MESH.TRIA4.INDEX - [M4x 5] array of indexing for TRIA-4
35 | % elements, where INDEX(K,1:4) is an array of
36 | % "point-indices" associated with the K-TH tria, and
37 | % INDEX(K,5) is an ID tag for the K-TH tria.
38 | %
39 | % MESH.HEXA8.INDEX - [M8x 9] array of indexing for HEXA-8
40 | % elements, where INDEX(K,1:8) is an array of
41 | % "point-indices" associated with the K-TH hexa, and
42 | % INDEX(K,9) is an ID tag for the K-TH hexa.
43 | %
44 | % MESH.WEDG6.INDEX - [M6x 7] array of indexing for WEDG-6
45 | % elements, where INDEX(K,1:6) is an array of
46 | % "point-indices" associated with the K-TH wedg, and
47 | % INDEX(K,7) is an ID tag for the K-TH wedg.
48 | %
49 | % MESH.PYRA5.INDEX - [M5x 6] array of indexing for PYRA-5
50 | % elements, where INDEX(K,1:5) is an array of
51 | % "point-indices" associated with the K-TH pyra, and
52 | % INDEX(K,6) is an ID tag for the K-TH pyra.
53 | %
54 | % MESH.BOUND.INDEX - [NBx 3] array of "boundary" indexing
55 | % in the domain, indicating how elements in the
56 | % geometry are associated with various enclosed areas
57 | % /volumes, herein known as "parts". INDEX(:,1) is an
58 | % array of "part" ID's, INDEX(:,2) is an array of
59 | % element numbering and INDEX(:,3) is an array of
60 | % element "tags", describing which element "kind" is
61 | % numbered via INDEX(:,2). Element tags are defined
62 | % via a series of constants instantiated in LIBDATA.
63 | % In the default case, where BOUND is not specified,
64 | % all elements in the geometry are assumed to define
65 | % the boundaries of enclosed "parts".
66 | %
67 | % MESH.VALUE - [NPxNV] array of "values" associated with
68 | % the vertices of the mesh. NV values are associated
69 | % with each vertex.
70 | %
71 | % MESH.SLOPE - [NPx 1] array of "slopes" associated with
72 | % the vertices of the mesh. Slope values define the
73 | % gradient-limits ||dh/dx|| used by the Eikonal solver
74 | % MARCHE.
75 | %
76 | % .IF. MESH.MSHID == 'ELLIPSOID-MESH':
77 | % -----------------------------------
78 | %
79 | % MESH.RADII - [ 3x 1] array of principle ellipsoid radii.
80 | %
81 | % Additionally, entities described in the 'EUCLIDEAN-MESH'
82 | % data-type are optionally written.
83 | %
84 | % .IF. MESH.MSHID == 'EUCLIDEAN-GRID':
85 | % .OR. MESH.MSHID == 'ELLIPSOID-GRID':
86 | % -----------------------------------
87 | %
88 | % MESH.POINT.COORD - [NDx1] cell array of grid coordinates
89 | % where ND is the number of spatial dimenions. Each
90 | % array COORD{ID} should be a vector of grid coord.'s,
91 | % increasing or decreasing monotonically.
92 | %
93 | % MESH.VALUE - [NMxNV] array of "values" associated with
94 | % the vertices of the grid, where NM is the product of
95 | % the dimensions of the grid. NV values are associated
96 | % with each vertex.
97 | %
98 | % MESH.SLOPE - [NMx 1] array of "slopes" associated with
99 | % the vertices of the grid, where NM is the product of
100 | % the dimensions of the grid. Slope values define the
101 | % gradient-limits ||dh/dx|| used by the Eikonal solver
102 | % MARCHE.
103 | %
104 | % See also JIGSAW, LOADMSH
105 | %
106 |
107 | %-----------------------------------------------------------
108 | % Darren Engwirda
109 | % github.com/dengwirda/jigsaw-matlab
110 | % 20-Aug-2019
111 | % darren.engwirda@columbia.edu
112 | %-----------------------------------------------------------
113 | %
114 |
115 | [ok] = certify(mesh);
116 |
117 | if (~ischar (name))
118 | error('NAME must be a valid file-name!') ;
119 | end
120 | if (~isstruct(mesh))
121 | error('MESH must be a valid structure!') ;
122 | end
123 |
124 | [path,file,fext] = fileparts(name) ;
125 |
126 | if(~strcmp(lower(fext),'.msh'))
127 | name = [name,'.msh'];
128 | end
129 |
130 | try
131 | %-- try to write data to file
132 |
133 | ffid = fopen(name, 'w') ;
134 |
135 | nver = +3;
136 |
137 | if (exist('OCTAVE_VERSION','builtin') > 0)
138 | fprintf(ffid,[ ...
139 | '# %s.msh; created by JIGSAW''s OCTAVE interface\n'],file) ;
140 | else
141 | fprintf(ffid,[ ...
142 | '# %s.msh; created by JIGSAW''s MATLAB interface\n'],file) ;
143 | end
144 |
145 | if (isfield(mesh,'mshID'))
146 | mshID = mesh.mshID ;
147 | else
148 | mshID = 'EUCLIDEAN-MESH';
149 | end
150 |
151 | switch (upper(mshID))
152 |
153 | case 'EUCLIDEAN-MESH'
154 | save_mesh_format( ...
155 | ffid,nver,mesh,'EUCLIDEAN-MESH') ;
156 | case 'EUCLIDEAN-GRID'
157 | save_grid_format( ...
158 | ffid,nver,mesh,'EUCLIDEAN-GRID') ;
159 | case 'EUCLIDEAN-DUAL'
160 | %save_dual_format( ...
161 | % ffid,nver,mesh,'EUCLIDEAN-DUAL') ;
162 |
163 | case 'ELLIPSOID-MESH'
164 | save_mesh_format( ...
165 | ffid,nver,mesh,'ELLIPSOID-MESH') ;
166 | case 'ELLIPSOID-GRID'
167 | save_grid_format( ...
168 | ffid,nver,mesh,'ELLIPSOID-GRID') ;
169 | case 'ELLIPSOID-DUAL'
170 | %save_dual_format( ...
171 | % ffid,nver,mesh,'ELLIPSOID-DUAL') ;
172 |
173 | otherwise
174 | error('Invalid mshID!') ;
175 |
176 | end
177 |
178 | fclose(ffid);
179 |
180 | catch err
181 |
182 | %-- ensure that we close the file regardless!
183 | if (ffid>-1)
184 | fclose(ffid) ;
185 | end
186 | rethrow(err) ;
187 |
188 | end
189 |
190 | end
191 |
192 | function save_mesh_format(ffid,nver,mesh,kind)
193 | %SAVE-MESH-FORMAT save mesh data in unstructured-mesh format
194 |
195 | switch (upper(kind))
196 | case 'EUCLIDEAN-MESH'
197 | fprintf( ...
198 | ffid,'MSHID=%u;EUCLIDEAN-MESH\n',nver) ;
199 |
200 | case 'ELLIPSOID-MESH'
201 | fprintf( ...
202 | ffid,'MSHID=%u;ELLIPSOID-MESH\n',nver) ;
203 |
204 | end
205 |
206 | npts = +0 ;
207 |
208 | if (isfield(mesh,'radii') && ...
209 | ~isempty(mesh.radii) )
210 |
211 | %------------------------------------ write "RADII" data
212 |
213 | if (~isnumeric(mesh.radii))
214 | error('Incorrect input types');
215 | end
216 | if (ndims(mesh.radii) ~= 2)
217 | error('Incorrect dimensions!');
218 | end
219 | if (numel(mesh.radii) ~= 3)
220 | mesh.radii = mesh.radii(1) * ones(+3,+1);
221 | end
222 |
223 | fprintf(ffid,'RADII=%f;%f;%f\n',mesh.radii');
224 |
225 | end
226 |
227 | if (isfield(mesh,'point') && ...
228 | isfield(mesh.point,'coord') && ...
229 | ~isempty(mesh.point.coord) )
230 |
231 | %------------------------------------ write "POINT" data
232 |
233 | if (~isnumeric(mesh.point.coord))
234 | error('Incorrect input type!');
235 | end
236 | if (ndims(mesh.point.coord) ~= 2)
237 | error('Incorrect dimensions!');
238 | end
239 |
240 | ndim = size(mesh.point.coord,2)-1 ;
241 | npts = size(mesh.point.coord,1)-0 ;
242 | fprintf(ffid,['NDIMS=%u','\n'],ndim);
243 |
244 | fprintf(ffid, ...
245 | ['POINT=%u','\n'],size(mesh.point.coord,1));
246 |
247 | if (isa(mesh.point.coord,'double'))
248 | vstr = sprintf('%%1.%ug;',+16);
249 | else
250 | vstr = sprintf('%%1.%ug;',+ 8);
251 | end
252 |
253 | fprintf(ffid, ...
254 | [repmat(vstr,1,ndim),'%i\n'],mesh.point.coord');
255 |
256 | end
257 |
258 | if (isfield(mesh,'point') && ...
259 | isfield(mesh.point,'power') && ...
260 | ~isempty(mesh.point.power) )
261 |
262 | %------------------------------------ write "POWER" data
263 |
264 | if (~isnumeric(mesh.point.power))
265 | error('Incorrect input type!');
266 | end
267 | if (ndims(mesh.point.power) ~= 2)
268 | error('Incorrect dimensions!');
269 | end
270 |
271 | npwr = size(mesh.point.power,2) - 0 ;
272 | nrow = size(mesh.point.power,1) - 0 ;
273 |
274 | if (isa(mesh.point.power,'double'))
275 | vstr = sprintf('%%1.%ug;',+16) ;
276 | else
277 | vstr = sprintf('%%1.%ug;',+ 8) ;
278 | end
279 | vstr = repmat(vstr,+1,npwr) ;
280 |
281 | fprintf(ffid,['POWER=%u;%u','\n'],[nrow,npwr]);
282 | fprintf(ffid, ...
283 | [vstr(+1:end-1), '\n'], mesh.point.power');
284 |
285 | end
286 |
287 | if (isfield(mesh,'value'))
288 |
289 | %------------------------------------ write "VALUE" data
290 |
291 | if (~isnumeric(mesh.value))
292 | error('Incorrect input type!');
293 | end
294 | if (ndims(mesh.value) ~= 2)
295 | error('Incorrect dimensions!');
296 | end
297 | if (size(mesh.value,1) ~= npts)
298 | error('Incorrect dimensions!');
299 | end
300 |
301 | nrow = size(mesh.value,1);
302 | nval = size(mesh.value,2);
303 |
304 | if (isa(mesh.value, 'double'))
305 | vstr = sprintf('%%1.%ug;',+16) ;
306 | elseif (isa(mesh.value, 'single'))
307 | vstr = sprintf('%%1.%ug;',+ 8) ;
308 | elseif (isa(mesh.value,'integer'))
309 | vstr = '%d;' ;
310 | else
311 | error('Incorrect input type!');
312 | end
313 | vstr = repmat(vstr,+1,nval) ;
314 |
315 | fprintf(ffid,['VALUE=%u;%u','\n'],[nrow,nval]);
316 | fprintf(ffid,[vstr(1:end-1),'\n'],mesh.value');
317 |
318 | end
319 |
320 | if (isfield(mesh,'slope'))
321 |
322 | %------------------------------------ write "SLOPE" data
323 |
324 | if (~isnumeric(mesh.slope))
325 | error('Incorrect input type!');
326 | end
327 | if (ndims(mesh.slope) ~= 2)
328 | error('Incorrect dimensions!');
329 | end
330 | if (size(mesh.slope,1) ~= npts && ...
331 | size(mesh.slope,1) ~= +1 )
332 | error('Incorrect dimensions!');
333 | end
334 |
335 | nrow = size(mesh.slope,1);
336 | nval = size(mesh.slope,2);
337 |
338 | if (isa(mesh.slope, 'double'))
339 | vstr = sprintf('%%1.%ug;',+16) ;
340 | elseif (isa(mesh.slope, 'single'))
341 | vstr = sprintf('%%1.%ug;',+ 8) ;
342 | elseif (isa(mesh.slope,'integer'))
343 | vstr = '%d;' ;
344 | else
345 | error('Incorrect input type!');
346 | end
347 | vstr = repmat(vstr,+1,nval) ;
348 |
349 | fprintf(ffid,['SLOPE=%u;%u','\n'],[nrow,nval]);
350 | fprintf(ffid,[vstr(1:end-1),'\n'],mesh.slope');
351 |
352 | end
353 |
354 | if (isfield(mesh,'edge2') && ...
355 | isfield(mesh.edge2,'index') && ...
356 | ~isempty(mesh.edge2.index) )
357 |
358 | %------------------------------------ write "EDGE2" data
359 |
360 | if (~isnumeric(mesh.edge2.index))
361 | error('Incorrect input type!');
362 | end
363 | if (ndims(mesh.edge2.index) ~= 2)
364 | error('Incorrect dimensions!');
365 | end
366 |
367 | index = mesh.edge2.index;
368 |
369 | if (min(min(index(:,1:2))) < +1 || ...
370 | max(max(index(:,1:2))) > npts)
371 | error('Invalid EDGE-2 indexing!') ;
372 | end
373 |
374 | index(:,1:2) = ...
375 | index(:,1:2)-1 ; % zero-indexing!
376 |
377 | fprintf( ...
378 | ffid,['EDGE2=%u','\n'],size(index,1)) ;
379 | fprintf(ffid, ...
380 | [repmat('%u;',1,2),'%i','\n'],index') ;
381 |
382 | end
383 |
384 | if (isfield(mesh,'tria3') && ...
385 | isfield(mesh.tria3,'index') && ...
386 | ~isempty(mesh.tria3.index) )
387 |
388 | %------------------------------------ write "TRIA3" data
389 |
390 | if (~isnumeric(mesh.tria3.index))
391 | error('Incorrect input type!');
392 | end
393 | if (ndims(mesh.tria3.index) ~= 2)
394 | error('Incorrect dimensions!');
395 | end
396 |
397 | index = mesh.tria3.index;
398 |
399 | if (min(min(index(:,1:3))) < +1 || ...
400 | max(max(index(:,1:3))) > npts)
401 | error('Invalid TRIA-3 indexing!') ;
402 | end
403 |
404 | index(:,1:3) = ...
405 | index(:,1:3)-1 ; % zero-indexing!
406 |
407 | fprintf( ...
408 | ffid,['TRIA3=%u','\n'],size(index,1)) ;
409 |
410 | fprintf(ffid, ...
411 | [repmat('%u;',1,3),'%i','\n'],index') ;
412 |
413 | end
414 |
415 | if (isfield(mesh,'quad4') && ...
416 | isfield(mesh.quad4,'index') && ...
417 | ~isempty(mesh.quad4.index) )
418 |
419 | %------------------------------------ write "QUAD4" data
420 |
421 | if (~isnumeric(mesh.quad4.index))
422 | error('Incorrect input type!');
423 | end
424 | if (ndims(mesh.quad4.index) ~= 2)
425 | error('Incorrect dimensions!');
426 | end
427 |
428 | index = mesh.quad4.index;
429 |
430 | if (min(min(index(:,1:4))) < +1 || ...
431 | max(max(index(:,1:4))) > npts)
432 | error('Invalid QUAD-4 indexing!') ;
433 | end
434 |
435 | index(:,1:4) = ...
436 | index(:,1:4)-1 ; % zero-indexing!
437 |
438 | fprintf( ...
439 | ffid,['QUAD4=%u','\n'],size(index,1)) ;
440 |
441 | fprintf(ffid, ...
442 | [repmat('%u;',1,4),'%i','\n'],index') ;
443 |
444 | end
445 |
446 | if (isfield(mesh,'tria4') && ...
447 | isfield(mesh.tria4,'index') && ...
448 | ~isempty(mesh.tria4.index) )
449 |
450 | %------------------------------------ write "TRIA4" data
451 |
452 | if (~isnumeric(mesh.tria4.index))
453 | error('Incorrect input type!');
454 | end
455 | if (ndims(mesh.tria4.index) ~= 2)
456 | error('Incorrect dimensions!');
457 | end
458 |
459 | index = mesh.tria4.index;
460 |
461 | if (min(min(index(:,1:4))) < +1 || ...
462 | max(max(index(:,1:4))) > npts)
463 | error('Invalid TRIA-4 indexing!') ;
464 | end
465 |
466 | index(:,1:4) = ...
467 | index(:,1:4)-1 ; % zero-indexing!
468 |
469 | fprintf( ...
470 | ffid,['TRIA4=%u','\n'],size(index,1)) ;
471 |
472 | fprintf(ffid, ...
473 | [repmat('%u;',1,4),'%i','\n'],index') ;
474 |
475 | end
476 |
477 | if (isfield(mesh,'hexa8') && ...
478 | isfield(mesh.hexa8,'index') && ...
479 | ~isempty(mesh.hexa8.index) )
480 |
481 | %------------------------------------ write "HEXA8" data
482 |
483 | if (~isnumeric(mesh.hexa8.index))
484 | error('Incorrect input type!');
485 | end
486 | if (ndims(mesh.hexa8.index) ~= 2)
487 | error('Incorrect dimensions!');
488 | end
489 |
490 | index = mesh.hexa8.index;
491 |
492 | if (min(min(index(:,1:8))) < +1 || ...
493 | max(max(index(:,1:8))) > npts)
494 | error('Invalid HEXA-8 indexing!') ;
495 | end
496 |
497 | index(:,1:8) = ...
498 | index(:,1:8)-1 ; % zero-indexing!
499 |
500 | fprintf( ...
501 | ffid,['HEXA8=%u','\n'],size(index,1)) ;
502 |
503 | fprintf(ffid, ...
504 | [repmat('%u;',1,8),'%i','\n'],index') ;
505 |
506 | end
507 |
508 | if (isfield(mesh,'wedg6') && ...
509 | isfield(mesh.wedg6,'index') && ...
510 | ~isempty(mesh.wedg6.index) )
511 |
512 | %------------------------------------ write "WEDG6" data
513 |
514 | if (~isnumeric(mesh.wedg6.index))
515 | error('Incorrect input type!');
516 | end
517 | if (ndims(mesh.wedg6.index) ~= 2)
518 | error('Incorrect dimensions!');
519 | end
520 |
521 | index = mesh.wedg6.index ;
522 |
523 | if (min(min(index(:,1:6))) < +1 || ...
524 | max(max(index(:,1:6))) > npts)
525 | error('Invalid WEDG-6 indexing!') ;
526 | end
527 |
528 | index(:,1:6) = ...
529 | index(:,1:6)-1 ; % zero-indexing!
530 |
531 | fprintf( ...
532 | ffid,['WEDG6=%u','\n'],size(index,1)) ;
533 |
534 | fprintf(ffid, ...
535 | [repmat('%u;',1,6),'%i','\n'],index') ;
536 |
537 | end
538 |
539 | if (isfield(mesh,'pyra5') && ...
540 | isfield(mesh.pyra5,'index') && ...
541 | ~isempty(mesh.pyra5.index) )
542 |
543 | %------------------------------------ write "PYRA5" data
544 |
545 | if (~isnumeric(mesh.pyra5.index))
546 | error('Incorrect input type!');
547 | end
548 | if (ndims(mesh.pyra5.index) ~= 2)
549 | error('Incorrect dimensions!');
550 | end
551 |
552 | index = mesh.pyra5.index;
553 |
554 | if (min(min(index(:,1:5))) < +1 || ...
555 | max(max(index(:,1:5))) > npts)
556 | error('Invalid PYRA-5 indexing!') ;
557 | end
558 |
559 | index(:,1:5) = ...
560 | index(:,1:5)-1 ; % zero-indexing!
561 |
562 | fprintf( ...
563 | ffid,['PYRA5=%u','\n'],size(index,1)) ;
564 |
565 | fprintf(ffid, ...
566 | [repmat('%u;',1,6),'%i','\n'],index') ;
567 |
568 | end
569 |
570 | if (isfield(mesh,'bound') && ...
571 | isfield(mesh.bound,'index') && ...
572 | ~isempty(mesh.bound.index) )
573 |
574 | %------------------------------------ write "BOUND" data
575 |
576 | if (~isnumeric(mesh.bound.index))
577 | error('Incorrect input type!');
578 | end
579 | if (ndims(mesh.bound.index) ~= 2)
580 | error('Incorrect dimensions!');
581 | end
582 |
583 | index = mesh.bound.index ;
584 | index(:,2:2) = ...
585 | index(:,2:2)-1 ; % zero-indexing!
586 |
587 | fprintf( ...
588 | ffid,['BOUND=%u','\n'],size(index,1)) ;
589 |
590 | fprintf(ffid, ...
591 | [repmat('%u;',1,2),'%u','\n'],index') ;
592 |
593 | end
594 |
595 | end
596 |
597 | function save_grid_format(ffid,nver,mesh,kind)
598 | %SAVE-GRID-FORMAT save mesh class in rectilinear-grid format
599 |
600 | switch (upper(kind))
601 | case 'EUCLIDEAN-GRID'
602 | fprintf( ...
603 | ffid,'MSHID=%u;EUCLIDEAN-GRID\n',nver) ;
604 |
605 | case 'ELLIPSOID-GRID'
606 | fprintf( ...
607 | ffid,'MSHID=%u;ELLIPSOID-GRID\n',nver) ;
608 |
609 | end
610 |
611 | dims = [] ;
612 |
613 | if (isfield(mesh,'radii') && ...
614 | ~isempty(mesh.radii) )
615 |
616 | %------------------------------------ write "RADII" data
617 |
618 | if (~isnumeric(mesh.radii))
619 | error('Incorrect input types');
620 | end
621 | if (ndims(mesh.radii) ~= 2)
622 | error('Incorrect dimensions!');
623 | end
624 | if (numel(mesh.radii) ~= 3)
625 | mesh.radii = mesh.radii(1) * ones(+3,+1);
626 | end
627 |
628 | fprintf(ffid,'RADII=%f;%f;%f\n',mesh.radii');
629 |
630 | end
631 |
632 | if (isfield(mesh,'point') && ...
633 | isfield(mesh.point,'coord') && ...
634 | ~isempty(mesh.point.coord) )
635 |
636 | %------------------------------------ write "COORD" data
637 |
638 | if(~iscell(mesh.point.coord) )
639 | error('Incorrect input types') ;
640 | end
641 | if ( numel(mesh.point.coord) ~= ...
642 | length(mesh.point.coord) )
643 | error('Incorrect dimensions!') ;
644 | end
645 |
646 | ndim = length(mesh.point.coord);
647 | dims = zeros(1,ndim);
648 | iord = [2,1,+3:ndim];
649 |
650 | fprintf(ffid, ...
651 | ['NDIMS=%u \n'],length(mesh.point.coord));
652 |
653 | for idim = +1 : length(mesh.point.coord)
654 |
655 | if ( numel(mesh.point.coord{idim}) ~= ...
656 | length(mesh.point.coord{idim}) )
657 | error('Incorrect dimensions!') ;
658 | end
659 |
660 | dims(iord(idim)) = ...
661 | length(mesh.point.coord{idim}) ;
662 |
663 | if (isa(mesh.point.coord{idim}, 'double'))
664 | vstr = sprintf('%%1.%ug\n',+16);
665 | else
666 | vstr = sprintf('%%1.%ug\n',+ 8);
667 | end
668 |
669 | fprintf(ffid,...
670 | 'COORD=%u;%u\n', [idim,dims(iord(idim))]);
671 |
672 | fprintf(ffid,vstr,mesh.point.coord{idim});
673 |
674 | end
675 |
676 | end
677 |
678 | if (isfield(mesh,'value'))
679 |
680 | %------------------------------------ write "VALUE" data
681 |
682 | if (~isnumeric(mesh.value))
683 | error('Incorrect input types') ;
684 | end
685 | if (ndims(mesh.value) ~= length(dims)+0 && ...
686 | ndims(mesh.value) ~= length(dims)+1 )
687 | error('Incorrect dimensions!') ;
688 | end
689 |
690 | if (ndims(mesh.value) == length(dims))
691 | nval = size(mesh.value);
692 | nval = [nval, +1] ;
693 | else
694 | nval = size(mesh.value);
695 | end
696 |
697 | if (isvector(mesh.value))
698 | if (prod(nval(1:end-1)) ~= prod(dims))
699 | error('Incorrect dimensions!') ;
700 | end
701 | else
702 | if (~all(nval(1:end-1) == dims))
703 | error('Incorrect dimensions!') ;
704 | end
705 | end
706 |
707 | if (isa(mesh.value, 'double'))
708 | vstr = sprintf('%%1.%ug;',+16) ;
709 | elseif (isa(mesh.value, 'single'))
710 | vstr = sprintf('%%1.%ug;',+ 8) ;
711 | elseif (isa(mesh.value,'integer'))
712 | vstr = '%d;' ;
713 | else
714 | error('Incorrect input type!');
715 | end
716 | vstr = repmat(vstr,+1,nval(end)) ;
717 |
718 | vals = ...
719 | reshape(mesh.value,[],nval(end)) ;
720 |
721 | fprintf(ffid, ...
722 | 'VALUE=%u;%u\n',[prod(dims),nval(end)]);
723 |
724 | fprintf(ffid,[vstr(+1:end-1),'\n'],vals');
725 |
726 | end
727 |
728 | if (isfield(mesh,'slope'))
729 |
730 | %------------------------------------ write "SLOPE" data
731 |
732 | if (~isnumeric(mesh.slope))
733 | error('Incorrect input types') ;
734 | end
735 | if (ndims(mesh.slope) ~= length(dims)+0 && ...
736 | ndims(mesh.slope) ~= length(dims)+1 )
737 | error('Incorrect dimensions!') ;
738 | end
739 |
740 | if (ndims(mesh.slope) == length(dims))
741 | nval = size(mesh.slope);
742 | nval = [nval, +1] ;
743 | else
744 | nval = size(mesh.slope);
745 | end
746 |
747 | if (isvector(mesh.slope))
748 | if (prod(nval(1:end-1)) ~= prod(dims))
749 | error('Incorrect dimensions!') ;
750 | end
751 | else
752 | if (~all(nval(1:end-1) == dims))
753 | error('Incorrect dimensions!') ;
754 | end
755 | end
756 |
757 | if (isa(mesh.slope, 'double'))
758 | vstr = sprintf('%%1.%ug;',+16) ;
759 | elseif (isa(mesh.slope, 'single'))
760 | vstr = sprintf('%%1.%ug;',+ 8) ;
761 | elseif (isa(mesh.slope,'integer'))
762 | vstr = '%d;' ;
763 | else
764 | error('Incorrect input type!');
765 | end
766 | vstr = repmat(vstr,+1,nval(end)) ;
767 |
768 | vals = ...
769 | reshape(mesh.slope,[],nval(end)) ;
770 |
771 | fprintf(ffid, ...
772 | 'SLOPE=%u;%u\n',[prod(dims),nval(end)]);
773 |
774 | fprintf(ffid,[vstr(+1:end-1),'\n'],vals');
775 |
776 | end
777 |
778 | end
779 |
780 |
781 |
782 |
--------------------------------------------------------------------------------