├── 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 | --------------------------------------------------------------------------------