├── .gitignore ├── ReadMe.md ├── mesh2d ├── LICENSE ├── README.md ├── aabb-tree │ ├── drawtree.m │ ├── findball.m │ ├── findline.m │ ├── findtria.m │ ├── lineline.m │ ├── linenear.m │ ├── maketree.m │ ├── maprect.m │ ├── mapvert.m │ ├── queryset.m │ └── scantree.m ├── bfsgeo2.m ├── bfstri2.m ├── cdtbal1.m ├── cdtbal2.m ├── cfmtri2.m ├── compile.m ├── deltri2.m ├── drawscr.m ├── fixgeo2.m ├── getnan2.m ├── idxtri2.m ├── inpoly2.m ├── inpoly2_oct.cpp ├── isfeat2.m ├── lfshfn2.m ├── limgrad.m ├── limhfn2.m ├── mesh-file │ ├── loadmsh.m │ └── savemsh.m ├── minlen2.m ├── poly-data │ ├── airfoil.msh │ ├── islands.msh │ ├── lake-1-small.png │ ├── lake-2-small.png │ ├── lake.msh │ ├── river.msh │ └── wavy-channel.msh ├── refine2.m ├── relhfn2.m ├── setset2.m ├── smooth2.m ├── triang2.m ├── triarea.m ├── tribal2.m ├── tricon2.m ├── trideg2.m ├── tridemo.m ├── tridiv2.m ├── trihfn2.m ├── triread.m └── triscr2.m └── src ├── GenerateBasicData.m ├── RateStickEvolve.m ├── Reinitialize2D.m ├── ThreeCirclesEvolve.m ├── UShapedAnalytical.m ├── UShapedEvolve.m ├── buildConnection.m ├── buildMatrixA.m ├── buildOutgoingEdges.m ├── calcGradient.m ├── config.m ├── dcircle.m ├── ddiff.m ├── distLineSeg2Point.m ├── distance2curve.m ├── drawResult.m ├── drectangle.m ├── dunion.m ├── findContourInTriangle.m ├── findZeroOnEdge.m ├── generateTriangleNorms.m ├── rayLineSegmentIntersection.m ├── resultData └── ReadMe.txt ├── triangleOutNorm.m └── tricontour.m /.gitignore: -------------------------------------------------------------------------------- 1 | *.mat 2 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # Unstructured Level Set Method 2 | Robust 3D Level Set Method for Evolving Fronts on Complex Unstructured Meshes 3 | 4 | # Introduction 5 | 6 | This repository contains demonstration code of our robust 3D level set method on complex unstructured meshes. 7 | 8 | The main advantage of our implementation is its ability to handle discontinuous propagation speed, sharp geometric feature, and the geometries which start from the boundary of mesh. 9 | 10 | # Usage 11 | 12 | 0. Download the code and `cd` to the downloaded directory in `MATLAB`. 13 | 1. In `src/config.m` edit the simulation configurations such as mesh scale. 14 | 2. Run `src/GenerateBasicData.m`, which generates a series of `*.mat` files which contains the generated unstructured mesh. 15 | 3. Run any of the `src/*Evolve.m` files, which perform the actual level set computation for corresponding problems. The results will be stored in `src/resultData` directory. 16 | 4. Run script `drawResult.m`, which draws the result of each example. Note that each example is corresponding to a boolean switch called `draw*` in this file. You may have to set the specific switch to `true` in order to see corresponding results. 17 | 5. `Reinitialize2D.m` contains an independent example, which is not described in our article. You can run the file directly (after running src/GenerateBasicData.m) to see the output figure. 18 | 6. Other files are auxiliary functions, of which the file names are quite self-explanatory. 19 | 20 | 21 | # Acknowledgment 22 | 23 | + We have used [MESH2D](https://github.com/dengwirda/mesh2d) to generate the required mesh. 24 | + The [distance2curve](https://ww2.mathworks.cn/matlabcentral/fileexchange/34869-distance2curve) function is written by [John D'Errico](https://ww2.mathworks.cn/matlabcentral/profile/authors/869215-john-d-errico). 25 | + Functions `dunion`, `ddiff`, `dcircle`, `drectangle` are borrowed from [DistMesh](http://persson.berkeley.edu/distmesh/) toolkit. 26 | + Function [tricontour](https://ww2.mathworks.cn/matlabcentral/fileexchange/10408-contours-for-triangular-grids) is written by [Darren Engwirda](https://ww2.mathworks.cn/matlabcentral/profile/authors/870403-darren-engwirda). 27 | 28 | # Citation 29 | 30 | If any of the provided code does help in your research, please cite the following paper: 31 | 32 | [1] Wei, R., Bao, F., Liu, Y., Hui, W., 2018. Robust Three-Dimensional Level-Set Method for Evolving Fronts on Complex Unstructured Meshes. Mathematical Problems in Engineering 2018, 1–15. https://doi.org/10.1155/2018/2730829 33 | 34 | BibTex item: 35 | 36 | @article{weiRobustThreeDimensionalLevelSet2018, 37 | title = {Robust {{Three}}-{{Dimensional Level}}-{{Set Method}} for {{Evolving Fronts}} on {{Complex Unstructured Meshes}}}, 38 | volume = {2018}, 39 | copyright = {All rights reserved}, 40 | issn = {1024-123X, 1563-5147}, 41 | doi = {10.1155/2018/2730829}, 42 | abstract = {With a purpose to evolve the surfaces of complex geometries in their normal direction at arbitrarily defined velocities, we have developed a robust level-set approach which runs on three-dimensional unstructured meshes. The approach is built on the basis of an innovative spatial discretization and corresponding gradient-estimating approach. The numerical consistency of the estimating method is mathematically proven. A correction technology is utilized to improve accuracy near sharp geometric features. Validation tests show that the proposed approach is able to accurately handle geometries containing sharp features, computation regions having irregular shapes, discontinuous speed fields, and topological changes. Results of the test problems fit well with the reference results produced by analytical or other numerical methods and converge to reference results as the meshes refine. Compared to level-set method implementations on Cartesian meshes, the proposed approach makes it easier to describe jump boundary conditions and to perform coupling simulations.}, 43 | language = {en}, 44 | journal = {Mathematical Problems in Engineering}, 45 | author = {Wei, Ran and Bao, Futing and Liu, Yang and Hui, Weihua}, 46 | month = sep, 47 | year = {2018}, 48 | pages = {1-15} 49 | } 50 | -------------------------------------------------------------------------------- /mesh2d/README.md: -------------------------------------------------------------------------------- 1 | ## `MESH2D: Delaunay-based mesh generation in MATLAB` 2 | 3 | `MESH2D` is a `MATLAB` / `OCTAVE`-based unstructured mesh-generator for two-dimensional polygonal geometries, providing a range of relatively simple, yet effective two-dimensional meshing algorithms. `MESH2D` includes variations on the "classical" Delaunay refinement technique, a new "Frontal"-Delaunay refinement scheme, a non-linear mesh optimisation method, and auxiliary mesh and geometry pre- and post-processing facilities. 4 | 5 |

6 |         7 | 8 |

9 | 10 | Algorithms implemented in `MESH2D` are "provably-good" - ensuring convergence, geometrical and topological correctness, and providing guarantees on algorithm termination and worst-case element quality bounds. Support for user-defined "mesh-spacing" functions and "multi-part" geometry definitions is also provided, allowing `MESH2D` to handle a wide range of complex domain types and user-defined constraints. `MESH2D` typically generates very high-quality output, appropriate for a variety of finite-volume/element type applications. 11 | 12 | `MESH2D` is a simplified version of my `JIGSAW` mesh-generation algorithm (a `C++` code). `MESH2D` aims to provide a straightforward `MATLAB` / `OCTAVE` implementation of these Delaunay-based triangulation and mesh optimisation techniques. 13 | 14 | ## `Code Structure` 15 | 16 | `MESH2D` is a pure `MATLAB` / `OCATVE` package, consisting of a core library + associated utilities: 17 | 18 | MESH2D:: 19 | ├── MAIN-DIR. -- core MESH2D library functions. See REFINE2, SMOOTH2 and TRIDEMO, etc. 20 | ├── aabb-tree -- support for fast spatial indexing, via tree-based data-structures. 21 | ├── mesh-file -- support for mesh file text serialisation. 22 | └── poly-data -- geometry data for example problems, image cache, etc. 23 | 24 | ## `Starting Out` 25 | 26 | After downloading and unzipping the current repository, navigate to the installation directory within `MATLAB` / `OCTAVE` and run the set of examples contained in `tridemo.m`: 27 | ``` 28 | tridemo( 0); % a very simple example to get everything started. 29 | tridemo( 1); % investigate the impact of the "radius-edge" threshold. 30 | tridemo( 2); % Frontal-Delaunay vs. Delaunay-refinement refinement. 31 | tridemo( 3); % explore impact of user-defined mesh-size constraints. 32 | tridemo( 4); % explore impact of "hill-climbing" mesh optimisations. 33 | tridemo( 5); % assemble triangulations for "multi-part" geometries. 34 | tridemo( 6); % assemble triangulations with "internal" constraints. 35 | tridemo( 7); % investigate the use of "quadtree"-type refinement. 36 | tridemo( 8); % explore use of custom, user-defined mesh-size functions. 37 | tridemo( 9); % larger-scale problem, mesh refinement + optimisation. 38 | tridemo(10); % medium-scale problem, mesh refinement + optimisation. 39 | ``` 40 | 41 | For `OCTAVE` users, performance can be improved by compiling elements of the `MESH2D` library. Running `compile.m` within the `MESH2D` installation directory will complete the build process (note: requires a `-dev` installation of `OCTAVE`). 42 | 43 | ## `References!` 44 | 45 | If you make use of `MESH2D` please include a reference to the following! `MESH2D` is designed to provide a simple and easy-to-understand implementation of Delaunay-based mesh-generation techniques. For a much more advanced, and fully three-dimensional mesh-generation library, see the `JIGSAW` package. `MESH2D` makes use of the `AABBTREE` and `FINDTRIA` packages to compute efficient spatial queries and intersection tests. 46 | 47 | `[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. 48 | 49 | `[2]` - Darren Engwirda, Unstructured mesh methods for the Navier-Stokes equations, Honours Thesis, School of Aerospace, Mechanical and Mechatronic Engineering, The University of Sydney, November 2005. 50 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/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 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/findball.m: -------------------------------------------------------------------------------- 1 | function [bp,bj,tr] = findball(bb,pp,varargin) 2 | %FINDBALL spatial queries for collections of d-balls. 3 | % [BP,BI] = FINDBALL(BB,PI) finds the set of d-dim. balls 4 | % that intersect with a given spatial query. Balls are sp- 5 | % ecified as a set of centres BB(:,1:ND) and (squared) 6 | % radii BB(:,ND+1), where ND is the number of dimensions. 7 | % 8 | % A set of intersecting balls is returned for each query 9 | % point in PI, such that the II-th point is associated 10 | % with the balls BI(BP(II,1):BP(II,2)). Unenclosed points 11 | % have BP(II,1) == 0. 12 | % 13 | % [BP,BI,TR] = FINDBALL(BB,PI) additionally returns the 14 | % supporting aabb-tree used internally to compute the que- 15 | % ry. If the underlying collection BB is static, the tree 16 | % TR may be passed to subsequent calls, via [BP,BI,TR] = 17 | % FINDBALL(BB,PI,TR). This syntax may lead to improved pe- 18 | % rformance, especially when the number of balls is large 19 | % w.r.t. the number of query points. Note that in such ca- 20 | % ses the distribution of underlying balls is NOT permitt- 21 | % ed to change between calls, or erroneous results may be 22 | % returned. Additional parameters used to control the cre- 23 | % ation of the underlying aabb-tree may also be passed via 24 | % [...] = FINDBALL(BB,PI,TR,OP). See MAKETREE for additio- 25 | % nal information. 26 | % 27 | % See also MAKETREE, FINDTRIA 28 | 29 | % Please see the following for additional information: 30 | % 31 | % Darren Engwirda, "Locally-optimal Delaunay-refinement & 32 | % optimisation-based mesh generation". Ph.D. Thesis, Scho- 33 | % ol of Mathematics and Statistics, Univ. of Sydney, 2014: 34 | % http://hdl.handle.net/2123/13148 35 | 36 | % Darren Engwirda : 2017 -- 37 | % Email : de2363@columbia.edu 38 | % Last updated : 27/04/2017 39 | 40 | bp = []; bj = []; tr = []; op = []; 41 | 42 | %---------------------------------------------- basic checks 43 | if (nargin < +2 || nargin > +4) 44 | error('findball:incorrectNumInputs', ... 45 | 'Incorrect number of inputs.'); 46 | end 47 | 48 | if (nargin >= +3), tr = varargin{1}; end 49 | if (nargin >= +4), op = varargin{2}; end 50 | 51 | %------------------------------ quick return on empty inputs 52 | if (isempty(bb)), return; end 53 | 54 | %---------------------------------------------- basic checks 55 | if (~isnumeric(bb) || ~isnumeric(pp)) 56 | error('findball:incorrectInputClass', ... 57 | 'Incorrect input class.') ; 58 | end 59 | 60 | if (ndims(bb) ~= +2 || size(bb,2) < +3 ) 61 | error('findball:incorrectDimensions', ... 62 | 'Incorrect input dimensions.'); 63 | end 64 | if (ndims(pp) ~= +2 || ... 65 | size(bb,2) ~= size(pp,2)+1) 66 | error('findball:incorrectDimensions', ... 67 | 'Incorrect input dimensions.'); 68 | end 69 | 70 | if (~isempty(tr) && ~isstruct(tr) ) 71 | error('findball:incorrectInputClass', ... 72 | 'Incorrect input class.') ; 73 | end 74 | if (~isempty(op) && ~isstruct(op) ) 75 | error('findball:incorrectInputClass', ... 76 | 'Incorrect input class.') ; 77 | end 78 | 79 | if (isempty(tr)) 80 | %------------------------------ compute aabb-tree for d-ball 81 | nd = size(pp,2) ; 82 | rs = sqrt(bb(:,nd+1)); 83 | rs = rs(:,ones(1,nd)); 84 | ab =[bb(:,1:nd)-rs, ... % compute aabb-tree 85 | bb(:,1:nd)+rs]; 86 | 87 | tr = maketree(ab,op) ; 88 | end 89 | 90 | %------------------------------ compute tree-to-vert mapping 91 | tm = mapvert (tr,pp); 92 | 93 | %------------------------------ compute vert-to-ball queries 94 | [bi,ip,bj] = ... 95 | queryset (tr,tm,@ballkern,pp,bb) ; 96 | 97 | %------------------------------ re-index onto full obj. list 98 | bp = zeros(size(pp,1),2) ; 99 | bp( :,2) = -1 ; 100 | 101 | if (isempty(bi)), return ; end 102 | 103 | bp(bi,:) = ip ; 104 | 105 | end 106 | 107 | function [ip,ib] = ballkern(pk,bk,pp,bb) 108 | %BALLKERN d-dim. ball-vert intersection kernel routine. 109 | 110 | mp = length(pk); 111 | mb = length(bk); 112 | 113 | nd = size(pp,2); 114 | 115 | %-------------------------- push ball/vert onto n*m tile 116 | pk = pk.' ; 117 | 118 | pk = pk(ones(mb,1),:); 119 | pk = pk(:); 120 | bk = bk(:,ones(1,mp)); 121 | bk = bk(:); 122 | 123 | %-------------------------- compute O(n*m) loc. distance 124 | dd = ... 125 | sum((pp(pk,+1:nd)-bb(bk,+1:nd)).^2,2); 126 | 127 | in = dd<=bb(bk,nd+1) ; 128 | 129 | ip = pk(in) ; 130 | ib = bk(in) ; 131 | 132 | end 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/findline.m: -------------------------------------------------------------------------------- 1 | function [lp,lj,tr] = findline(pa,pb,pp,varargin) 2 | %FINDLINE "point-on-line" queries in d-dimensional space. 3 | % [LP,LI] = FINDLINE(PA,PB,PI) finds the set of d-dimensi- 4 | % onal line-segments that intersect with a given spatial 5 | % query. Lines are specified as a set of endpoints [PA,PB] 6 | % where both PA and PB are NL-by-ND arrays of coordinates, 7 | % where ND is the number of dimensions. 8 | % 9 | % A set of intersecting lines is returned for each query 10 | % point in PI, such that the II-th point is associated 11 | % with the lines LI(LP(II,1):LP(II,2)). Unenclosed points 12 | % have LP(II,1) == 0. 13 | % 14 | % [LP,LI,TR] = FINDLINE(PA,PB,PI) additionally returns the 15 | % supporting aabb-tree used internally to compute the que- 16 | % ry. If the underlying collection [PA,PB] is static, the 17 | % tree TR may be passed to subsequent calls, via 18 | % [LP,LI,TR] = FINDLINE(PA,PB,PI,TR). This syntax may lead 19 | % to improved performance, especially when the number of 20 | % lines is large w.r.t. the number of query points. Note 21 | % that in such cases the distribution of underlying lines 22 | % is NOT permitted to change between calls, or erroneous 23 | % results may be returned. Additional parameters used to 24 | % govern the creation of the underlying aabb-tree may be 25 | % passed via [...] = FINDLINE(...,TR,OP). See MAKETREE for 26 | % additional information. 27 | % 28 | % See also MAKETREE, FINDTRIA, FINDBALL, LINELINE 29 | 30 | % Please see the following for additional information: 31 | % 32 | % Darren Engwirda, "Locally-optimal Delaunay-refinement & 33 | % optimisation-based mesh generation". Ph.D. Thesis, Scho- 34 | % ol of Mathematics and Statistics, Univ. of Sydney, 2014: 35 | % http://hdl.handle.net/2123/13148 36 | 37 | %----------------------------------------------------------- 38 | % Darren Engwirda : 2017 -- 39 | % Email : de2363@columbia.edu 40 | % Last updated : 02/08/2017 41 | %----------------------------------------------------------- 42 | 43 | lp = []; lj = []; tr = []; op = []; 44 | 45 | %---------------------------------------------- basic checks 46 | if (nargin < +3 || nargin > +5) 47 | error('findline:incorrectNumInputs', ... 48 | 'Incorrect number of inputs.'); 49 | end 50 | 51 | if (nargin >= +4), tr = varargin{1}; end 52 | if (nargin >= +5), op = varargin{2}; end 53 | 54 | %------------------------------ quick return on empty inputs 55 | if (isempty(pa)), return; end 56 | if (isempty(pb)), return; end 57 | 58 | %---------------------------------------------- basic checks 59 | if (~isnumeric(pa) || ~isnumeric(pb) || ... 60 | ~isnumeric(pp) ) 61 | error('findline:incorrectInputClass', ... 62 | 'Incorrect input class.') ; 63 | end 64 | 65 | if (ndims(pa) ~= +2 || size(pa,2) < +2 || ... 66 | ndims(pb) ~= +2 || size(pb,2) < +2 ) 67 | error('findline:incorrectDimensions', ... 68 | 'Incorrect input dimensions.'); 69 | end 70 | if (ndims(pp) ~= +2 || ... 71 | size(pa,1) ~= size(pb,1) || ... 72 | size(pa,2) ~= size(pp,2) || ... 73 | size(pb,2) ~= size(pp,2) ) 74 | error('findline:incorrectDimensions', ... 75 | 'Incorrect input dimensions.'); 76 | end 77 | 78 | if (~isempty(tr) && ~isstruct(tr) ) 79 | error('findline:incorrectInputClass', ... 80 | 'Incorrect input class.') ; 81 | end 82 | if (~isempty(op) && ~isstruct(op) ) 83 | error('findline:incorrectInputClass', ... 84 | 'Incorrect input class.') ; 85 | end 86 | 87 | if (isempty(tr)) 88 | %------------------------------ compute aabb-tree for d-line 89 | nd = size(pp,2) ; 90 | nl = size(pa,1) ; 91 | 92 | ab = zeros(nl,nd*2); % compute aabb-tree 93 | for ax = +1:nd 94 | ab(:,ax+nd*0) = min(pa(:,ax), ... 95 | pb(:,ax)) ; 96 | ab(:,ax+nd*1) = max(pa(:,ax), ... 97 | pb(:,ax)) ; 98 | end 99 | tr = maketree(ab,op) ; 100 | end 101 | 102 | %------------------------------ compute tree-to-vert mapping 103 | tm = mapvert (tr,pp); 104 | 105 | %------------------------------ compute intersection rel-tol 106 | p0 = min([pa; pb],[],+1) ; 107 | p1 = max([pa; pb],[],+1) ; 108 | 109 | zt = max(p1-p0) * eps^.8 ; 110 | 111 | %------------------------------ compute vert-to-line queries 112 | [li,ip,lj] = queryset( ... 113 | tr,tm,@linekern,pp,pa,pb,zt) ; 114 | 115 | %------------------------------ re-index onto full obj. list 116 | lp = zeros(size(pp,1),2) ; 117 | lp( :,2) = -1 ; 118 | 119 | if (isempty(li)), return ; end 120 | 121 | lp(li,:) = ip ; 122 | 123 | end 124 | 125 | function [ip,il] = linekern(pk,lk,pp,pa,pb,zt) 126 | %LINEKERN d-dim. node//line intersection kernel routine. 127 | 128 | mp = length(pk); 129 | ml = length(lk); 130 | 131 | %-------------------------- push line/vert onto n*m tile 132 | pk = pk.' ; 133 | 134 | pk = pk(ones(ml,1),:); 135 | pk = pk(:); 136 | lk = lk(:,ones(1,mp)); 137 | lk = lk(:); 138 | 139 | %-------------------------- compute O(n*m) intersections 140 | mm = (pa(lk,:)+pb(lk,:)) * +.5 ; 141 | DD = (pb(lk,:)-pa(lk,:)) * +.5 ; 142 | 143 | mp = mm-pp(pk,:) ; 144 | 145 | tt =-sum(mp.*DD,2)./ ... 146 | sum(DD.*DD,2) ; 147 | tt = max(min(tt,+1.),-1.); 148 | 149 | nd = size(pp,2); 150 | 151 | qq = mm + repmat(tt,1,nd) .* DD; 152 | 153 | on = ... 154 | sum((pp(pk,:)-qq).^2,2) <= zt^2; 155 | 156 | ip = pk(on) ; 157 | il = lk(on) ; 158 | 159 | end 160 | 161 | 162 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/findtria.m: -------------------------------------------------------------------------------- 1 | function [tp,tj,tr] = findtria(pp,tt,pj,varargin) 2 | %FINDTRIA spatial queries for collections of d-simplexes. 3 | % [TP,TI] = FINDTRIA(PP,TT,PJ) finds the set of simple- 4 | % xes that intersect with a given spatial query. Simplexes 5 | % are specified via the vertex array PP = [X1,X2,...,XN] 6 | % and the indexing array TT = [T1,T2,...,TM], such that 7 | % the vertex positions for the II-th simplex are the poin- 8 | % ts [PP(TT(II,1),:),PP(TT(II,2),:),...,PP(TT(II,M),:)]. 9 | % 10 | % Simplexes are NOT required to form a conforming triangu- 11 | % lation. Specifically, non-delaunay, non-convex and even 12 | % overlapping configurations are supported. Multiple matc- 13 | % hes may be returned if the collection is overlapping. 14 | % 15 | % A set of intersecting simplexes is returned for each 16 | % query point in PI, such that the II-th point is associa- 17 | % ted with the simplexes TI(TP(II,1):TP(II,2)). Unenclosed 18 | % points have TP(II,1)==+0. 19 | % 20 | % In general, query points may be matched to multiple sim- 21 | % plexes, but in cases when single matches are guaranteed, 22 | % or if only a single match is desired, the following ret- 23 | % urns a singly-matched indexing array that is consistent 24 | % with MATLAB's existing point-location routines: 25 | % 26 | % [tp,tj] = findtria(pp,tt,pj) ; 27 | % ti = nan(size(tp,1),1); 28 | % in = tp(:,1) > +0; 29 | % ti(in) = tj(tp(in,+1)); 30 | % 31 | % [TP,TI,TR] = FINDTRIA(PP,TT,PI) additionally returns the 32 | % supporting aabb-tree used internally to compute the que- 33 | % ry. If the underlying collection [PP,TT] is static, the 34 | % tree TR may be passed to subsequent calls, via [...] = 35 | % FINDTRIA(PP,TT,PJ,TR). This syntax may lead to improved 36 | % performance, especially when the number of simplexes 37 | % is large w.r.t. the number of query points. Note that in 38 | % such cases the underlying simplexes are NOT permitted to 39 | % change between calls, or erroneous results may be retur- 40 | % ned. Additional parameters used to control the creation 41 | % of the underlying aabb-tree may also be passed via [...] 42 | % = FINDTRIA(PP,TT,PI,TR,OP). See MAKETREE for additional 43 | % information. 44 | % 45 | % See also MAKETREE, QUERYSET 46 | 47 | % Please see the following for additional information: 48 | % 49 | % Darren Engwirda, "Locally-optimal Delaunay-refinement & 50 | % optimisation-based mesh generation". Ph.D. Thesis, Scho- 51 | % ol of Mathematics and Statistics, Univ. of Sydney, 2014: 52 | % http://hdl.handle.net/2123/13148 53 | 54 | % Darren Engwirda : 2014 -- 55 | % Email : de2363@columbia.edu 56 | % Last updated : 10/03/2018 57 | 58 | tp = []; tj = []; tr = []; op = []; 59 | 60 | %---------------------------------------------- basic checks 61 | if (nargin < +3 || nargin > +6) 62 | error('queryset:incorrectNumInputs', ... 63 | 'Incorrect number of inputs.'); 64 | end 65 | 66 | %------------------------------- fast return on empty inputs 67 | if (isempty(pj)), return; end 68 | 69 | %------------------------------- extract user-defined inputs 70 | if (nargin >= +4), tr = varargin{1}; end 71 | if (nargin >= +5), op = varargin{2}; end 72 | 73 | %---------------------------------------------- basic checks 74 | if (~isnumeric(pp) || ~isnumeric(tt) || ... 75 | ~isnumeric(pj) ) 76 | error('findtria:incorrectInputClass', ... 77 | 'Incorrect input class.') ; 78 | end 79 | 80 | %---------------------------------------------- basic checks 81 | if (ndims(pp) ~= +2 || size(pp,2) < +2 ... 82 | || size(pp,2) > size(tt,2)) 83 | error('findtria:incorrectDimensions', ... 84 | 'Incorrect input dimensions.'); 85 | end 86 | if (ndims(tt) ~= +2 || size(tt,2) < +3) 87 | error('findtria:incorrectDimensions', ... 88 | 'Incorrect input dimensions.'); 89 | end 90 | 91 | %------------------------------- test query array for VERT's 92 | if (ndims(pj) ~= +2 || ... 93 | size(pj,2) ~= size(pp,2) ) 94 | error('findtria:incorrectDimensions', ... 95 | 'Incorrect input dimensions.'); 96 | end 97 | 98 | %---------------------------------------------- basic checks 99 | if (~isempty(tr) && ~isstruct(tr) ) 100 | error('findtria:incorrectInputClass', ... 101 | 'Incorrect input class.') ; 102 | end 103 | if (~isempty(op) && ~isstruct(op) ) 104 | error('findtria:incorrectInputClass', ... 105 | 'Incorrect input class.') ; 106 | end 107 | 108 | if (isempty(tr)) 109 | %------------------------------ compute aabb's for triangles 110 | bi = pp(tt(:,1),:); bj = pp(tt(:,1),:); 111 | for ii = 2 : size(tt,2) 112 | bi = min(bi,pp(tt(:,ii),:)) ; 113 | bj = max(bj,pp(tt(:,ii),:)) ; 114 | end 115 | bb = [bi,bj]; 116 | 117 | tr = maketree(bb,op); % compute aabb-tree 118 | end 119 | 120 | %------------------------------ compute tree-to-vert mapping 121 | tm = mapvert (tr,pj); 122 | 123 | %------------------------------ compute vert-to-tria queries 124 | x0 = min(pp,[],1); 125 | x1 = max(pp,[],1); 126 | rt = prod(x1 - x0) * eps^.8 ; 127 | 128 | [ti,ip,tj] = ... 129 | queryset(tr,tm,@triakern,pj,pp,tt,rt) ; 130 | 131 | %------------------------------ re-index onto full obj. list 132 | tp = zeros(size(pj,1),2); 133 | tp( :,2) = -1 ; 134 | 135 | if (isempty(ti)), return; end 136 | 137 | tp(ti,:) = ip ; 138 | 139 | end 140 | 141 | function [ip,it] = triakern(pk,tk,pi,pp,tt,rt) 142 | %TESTPTS compute the "point-tria" matches within a tile. 143 | 144 | mp = length(pk); mt = length(tk); 145 | 146 | switch (size(tt,2)) 147 | case 3 148 | %------------------------------------ pts in 2-simplexes 149 | pk = pk.' ; 150 | pk = pk(ones(mt,1),:); 151 | pk = pk(:); 152 | tk = tk(:,ones(1,mp)); 153 | tk = tk(:); 154 | 155 | in = intria2( ... 156 | pp,tt(tk,:),pi(pk,:),rt); 157 | 158 | ip = pk(in) ; 159 | it = tk(in) ; 160 | 161 | case 4 162 | %------------------------------------ pts in 3-simplexes 163 | pk = pk.' ; 164 | pk = pk(ones(mt,1),:); 165 | pk = pk(:); 166 | tk = tk(:,ones(1,mp)); 167 | tk = tk(:); 168 | 169 | in = intria3( ... 170 | pp,tt(tk,:),pi(pk,:),rt); 171 | 172 | ip = pk(in) ; 173 | it = tk(in) ; 174 | 175 | otherwise 176 | %------------------------------------ pts in d-simplexes 177 | [il,jl] = intrian( ... 178 | pp,tt(tk,:),pi(pk,:)); 179 | 180 | ip = pk(il(:)); 181 | it = tk(jl(:)); 182 | 183 | end 184 | 185 | end 186 | 187 | function [in] = intria2(pp,tt,pi,rt) 188 | %INTRIA2 returns TRUE for points enclosed by 2-simplexes. 189 | 190 | t1 = tt(:,1); t2 = tt(:,2) ; 191 | t3 = tt(:,3); 192 | 193 | vi = pp(t1,:) - pi ; 194 | vj = pp(t2,:) - pi ; 195 | vk = pp(t3,:) - pi ; 196 | 197 | %------------------------------- compute sub-volume about PI 198 | aa = zeros(size(tt,1),3) ; 199 | aa(:,1) =(vi(:,1).*vj(:,2) - ... 200 | vj(:,1).*vi(:,2) ) ; 201 | aa(:,2) =(vj(:,1).*vk(:,2) - ... 202 | vk(:,1).*vj(:,2) ) ; 203 | aa(:,3) =(vk(:,1).*vi(:,2) - ... 204 | vi(:,1).*vk(:,2) ) ; 205 | 206 | %------------------------------- PI is internal if same sign 207 | rt = rt ^ 2 ; 208 | in = aa(:,1).*aa(:,2) >= -rt ... 209 | & aa(:,2).*aa(:,3) >= -rt ... 210 | & aa(:,3).*aa(:,1) >= -rt ; 211 | 212 | end 213 | 214 | function [in] = intria3(pp,tt,pi,rt) 215 | %INTRIA3 returns TRUE for points enclosed by 3-simplexes. 216 | 217 | t1 = tt(:,1); t2 = tt(:,2) ; 218 | t3 = tt(:,3); t4 = tt(:,4) ; 219 | 220 | v1 = pi - pp(t1,:) ; 221 | v2 = pi - pp(t2,:) ; 222 | v3 = pi - pp(t3,:) ; 223 | v4 = pi - pp(t4,:) ; 224 | 225 | %------------------------------- compute sub-volume about PI 226 | aa = zeros(size(tt,1),4) ; 227 | aa(:,1) = ... 228 | +v1(:,1).*(v2(:,2).*v3(:,3) - ... 229 | v2(:,3).*v3(:,2) ) ... 230 | -v1(:,2).*(v2(:,1).*v3(:,3) - ... 231 | v2(:,3).*v3(:,1) ) ... 232 | +v1(:,3).*(v2(:,1).*v3(:,2) - ... 233 | v2(:,2).*v3(:,1) ) ; 234 | aa(:,2) = ... 235 | +v1(:,1).*(v4(:,2).*v2(:,3) - ... 236 | v4(:,3).*v2(:,2) ) ... 237 | -v1(:,2).*(v4(:,1).*v2(:,3) - ... 238 | v4(:,3).*v2(:,1) ) ... 239 | +v1(:,3).*(v4(:,1).*v2(:,2) - ... 240 | v4(:,2).*v2(:,1) ) ; 241 | aa(:,3) = ... 242 | +v2(:,1).*(v4(:,2).*v3(:,3) - ... 243 | v4(:,3).*v3(:,2) ) ... 244 | -v2(:,2).*(v4(:,1).*v3(:,3) - ... 245 | v4(:,3).*v3(:,1) ) ... 246 | +v2(:,3).*(v4(:,1).*v3(:,2) - ... 247 | v4(:,2).*v3(:,1) ) ; 248 | aa(:,4) = ... 249 | +v3(:,1).*(v4(:,2).*v1(:,3) - ... 250 | v4(:,3).*v1(:,2) ) ... 251 | -v3(:,2).*(v4(:,1).*v1(:,3) - ... 252 | v4(:,3).*v1(:,1) ) ... 253 | +v3(:,3).*(v4(:,1).*v1(:,2) - ... 254 | v4(:,2).*v1(:,1) ) ; 255 | 256 | %------------------------------- PI is internal if same sign 257 | rt = rt ^ 2 ; 258 | in = aa(:,1).*aa(:,2) >= -rt ... 259 | & aa(:,1).*aa(:,3) >= -rt ... 260 | & aa(:,1).*aa(:,4) >= -rt ... 261 | & aa(:,2).*aa(:,3) >= -rt ... 262 | & aa(:,2).*aa(:,4) >= -rt ... 263 | & aa(:,3).*aa(:,4) >= -rt ; 264 | 265 | end 266 | 267 | function [ii,jj] = intrian(pp,tt,pi) 268 | %INTRIAN return a list of points and enclosing "n"-simplexes. 269 | 270 | [np,pd] = size(pi); 271 | [nt,td] = size(tt); 272 | 273 | %---------------- coefficient matrices for barycentric coord. 274 | mm = zeros(pd,pd,nt); 275 | for id = +1 : pd 276 | for jd = +1 : pd 277 | mm(id,jd,:) = pp(tt(:,jd),id) - ... 278 | pp(tt(:,td),id) ; 279 | end 280 | end 281 | 282 | %---------------- solve linear systems for barycentric coord. 283 | xx = zeros(pd,np,nt); 284 | vp = zeros(pd,np,+1); 285 | for ti = +1 : nt 286 | %---------------------------------------- form rhs coeff. 287 | for id = +1 : pd 288 | vp(id,:) = ... 289 | pi(:,id) - pp(tt(ti,td),id) ; 290 | end 291 | %---------------------------- actually faster to call LU- 292 | [ll,uu] = lu(mm(:,:,ti)); 293 | %---------------------------- and then forward/back solve 294 | xx(:,:,ti) = uu\(ll\vp); 295 | end 296 | 297 | %-------------------- PI is internal if coord. have same sign 298 | in = all(xx >= +.0-(eps^.8),1) & ... 299 | sum(xx,1) <= +1.+(eps^.8) ; 300 | 301 | %-------------------- find lists of matching points/simplexes 302 | [ii,jj] = ... 303 | find(reshape(in, [np,nt])) ; 304 | 305 | end 306 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/lineline.m: -------------------------------------------------------------------------------- 1 | function [lp,lj,tr] = lineline(pa,pb,pc,pd,varargin) 2 | %LINELINE intersection between lines in d-dimensional space. 3 | % [LP,LI] = LINELINE(PA,PB,PC,PD) finds intersections bet- 4 | % ween line segments in d-dimensions. Lines are specified 5 | % as a set of endpoints [PA,PB] and [PC,PD] where PA, PB, 6 | % PC and PD are NL-by-ND arrays of coordinates, where ND 7 | % is the number of dimensions. 8 | % 9 | % A set of intersecting lines from [PA,PB] is returned for 10 | % each query line in [PC,PB], such that the II-th query 11 | % line is associated with the lines LI(LP(II,1):LP(II,2)). 12 | % Lines without intersections have LP(II,1) == 0. 13 | % 14 | % [LP,LI,TR] = LINELINE(PA,PB,PC,PD) additionally returns 15 | % the supporting aabb-tree used internally to compute the 16 | % query. If the underlying collection [PA,PB] is static, 17 | % the tree TR may be recycled for subsequent calls, using 18 | % [LP,LI,TR] = FINDLINE(PA,PB,PC,PD,TR). This syntax may 19 | % lead to improved performance, especially when the number 20 | % of lines is large w.r.t. the number of query lines. Note 21 | % that in such cases the distribution of underlying lines 22 | % is NOT permitted to change between calls, or erroneous 23 | % results may be returned. Additional parameters used to 24 | % govern the creation of the underlying aabb-tree may be 25 | % passed via [...] = LINELINE(...,TR,OP). See MAKETREE for 26 | % additional information. 27 | % 28 | % See also MAKETREE, FINDTRIA, FINDBALL, FINDLINE 29 | 30 | % Please see the following for additional information: 31 | % 32 | % Darren Engwirda, "Locally-optimal Delaunay-refinement & 33 | % optimisation-based mesh generation". Ph.D. Thesis, Scho- 34 | % ol of Mathematics and Statistics, Univ. of Sydney, 2014: 35 | % http://hdl.handle.net/2123/13148 36 | 37 | %----------------------------------------------------------- 38 | % Darren Engwirda : 2017 -- 39 | % Email : de2363@columbia.edu 40 | % Last updated : 10/10/2017 41 | %----------------------------------------------------------- 42 | 43 | lp = []; lj = []; tr = []; op = []; 44 | 45 | %---------------------------------------------- basic checks 46 | if (nargin < +4 || nargin > +6) 47 | error('lineline:incorrectNumInputs', ... 48 | 'Incorrect number of inputs.'); 49 | end 50 | 51 | if (nargin >= +5), tr = varargin{1}; end 52 | if (nargin >= +6), op = varargin{2}; end 53 | 54 | %------------------------------ quick return on empty inputs 55 | if (isempty(pa)), return ; end 56 | if (isempty(pb)), return ; end 57 | if (isempty(pc)), return ; end 58 | if (isempty(pd)), return ; end 59 | 60 | %---------------------------------------------- basic checks 61 | if (~isnumeric(pa) || ~isnumeric(pb) || ... 62 | ~isnumeric(pc) || ~isnumeric(pd) ) 63 | error('lineline:incorrectInputClass', ... 64 | 'Incorrect input class.') ; 65 | end 66 | 67 | if (ndims(pa) ~= +2 || size(pa,2) < +2 || ... 68 | ndims(pb) ~= +2 || size(pb,2) < +2 || ... 69 | ndims(pc) ~= +2 || size(pc,2) < +2 || ... 70 | ndims(pd) ~= +2 || size(pd,2) < +2 ) 71 | error('lineline:incorrectDimensions', ... 72 | 'Incorrect input dimensions.'); 73 | end 74 | if (size(pa,1) ~= size(pb,1) || ... 75 | size(pc,1) ~= size(pd,1) ) 76 | error('lineline:incorrectDimensions', ... 77 | 'Incorrect input dimensions.'); 78 | end 79 | 80 | if (~isempty(tr) && ~isstruct(tr) ) 81 | error('lineline:incorrectInputClass', ... 82 | 'Incorrect input class.') ; 83 | end 84 | if (~isempty(op) && ~isstruct(op) ) 85 | error('lineline:incorrectInputClass', ... 86 | 'Incorrect input class.') ; 87 | end 88 | 89 | nd = size(pa,2); 90 | nl = size(pa,1); 91 | ml = size(pc,1); 92 | 93 | if (isempty(tr)) 94 | %------------------------------ compute aabb-tree for d-line 95 | ab = zeros(nl,nd*+2) ; 96 | for ax = +1:nd % compute aabb's 97 | ab(:,ax+nd*0) = min(pa(:,ax), ... 98 | pb(:,ax)) ; 99 | ab(:,ax+nd*1) = max(pa(:,ax), ... 100 | pb(:,ax)) ; 101 | end 102 | tr = maketree(ab,op) ; 103 | 104 | end 105 | 106 | %------------------------------ compute tree-to-vert mapping 107 | ab = zeros(ml,nd*+2) ; 108 | for ax = +1:nd % compute aabb's 109 | ab(:,ax+nd*0) = min(pc(:,ax), ... 110 | pd(:,ax)) ; 111 | ab(:,ax+nd*1) = max(pc(:,ax), ... 112 | pd(:,ax)) ; 113 | end 114 | tm = maprect (tr,ab) ; 115 | 116 | %------------------------------ compute line-to-line queries 117 | [li,ip,lj] = queryset( ... 118 | tr,tm,@linekern,pc,pd,pa,pb) ; 119 | 120 | %------------------------------ re-index onto full obj. list 121 | lp = zeros(size(pc,1),2) ; 122 | lp( :,2) = -1 ; 123 | 124 | if (isempty(li)), return ; end 125 | 126 | lp(li,:) = ip ; 127 | 128 | end 129 | 130 | function [i1,i2] = linekern(l1,l2,pa,pb,pc,pd) 131 | %LINEKERN d-dim. line//line intersection kernel routine. 132 | 133 | m1 = length(l1) ; 134 | m2 = length(l2) ; 135 | 136 | %-------------------------- push line/vert onto n*m tile 137 | l1 = l1.' ; 138 | 139 | l1 = l1(ones(m2,1),:) ; 140 | l1 = l1(:); 141 | l2 = l2(:,ones(1,m1)) ; 142 | l2 = l2(:); 143 | 144 | %-------------------------- compute O(n*m) intersections 145 | [ok,tp,tq] = linenear( ... 146 | pa(l1,:),pb(l1,:), ... 147 | pc(l2,:),pd(l2,:)) ; 148 | 149 | rt = +1.+eps; 150 | 151 | ix = abs(tp) <= +rt & ... 152 | abs(tq) <= +rt ; 153 | 154 | ix(~ok) = false ; 155 | 156 | i1 = l1(ix) ; 157 | i2 = l2(ix) ; 158 | 159 | end 160 | 161 | 162 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/linenear.m: -------------------------------------------------------------------------------- 1 | function [ok,tp,tq] = linenear(pa,pb,pc,pd) 2 | %LINENEAR calc. the nearest points on line segments embedded 3 | %in d-dimensions. Line paramters are bounded on [-1,+1]. 4 | 5 | %----------------------------------------------------------- 6 | % Darren Engwirda : 2017 -- 7 | % Email : de2363@columbia.edu 8 | % Last updated : 10/10/2017 9 | %----------------------------------------------------------- 10 | 11 | m1 = (pa+pb) * +.5 ; 12 | D1 = (pb-pa) * +.5 ; 13 | 14 | m2 = (pc+pd) * +.5 ; 15 | D2 = (pd-pc) * +.5 ; 16 | 17 | r1 = sum(m2.*D1,2) ... 18 | - sum(m1.*D1,2) ; 19 | r2 = sum(m1.*D2,2) ... 20 | - sum(m2.*D2,2) ; 21 | 22 | A1 = sum(D1.*D1,2) ; 23 | A2 =-sum(D1.*D2,2) ; 24 | A3 =-sum(D1.*D2,2) ; 25 | A4 = sum(D2.*D2,2) ; 26 | 27 | dd = A1.*A4 - A2.*A3 ; 28 | 29 | tp = A4.*r1 - A2.*r2 ; 30 | tq =-A3.*r1 + A1.*r2 ; 31 | 32 | rt = max(abs([A1,A2,A3,A4]),[],2); 33 | rt = rt * eps ^ .8 ; 34 | 35 | ok = abs(dd) > +rt ; 36 | 37 | tp(~ok) = +0. ; 38 | tq(~ok) = +0. ; 39 | 40 | tp(ok) = tp(ok) ./ dd(ok) ; 41 | tq(ok) = tq(ok) ./ dd(ok) ; 42 | 43 | end 44 | 45 | 46 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/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 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/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 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/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 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/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 | -------------------------------------------------------------------------------- /mesh2d/aabb-tree/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 | -------------------------------------------------------------------------------- /mesh2d/bfsgeo2.m: -------------------------------------------------------------------------------- 1 | function [node,PSLG,part] = bfsgeo2(node,PSLG,seed) 2 | %BFSGEO2 partition geometry about "seeds" via breadth-first 3 | %search. 4 | % [NODE,EDGE,PART] = BFSGEO2(NODE,EDGE,SEED) returns a set 5 | % of 2-manifold geometry partitions by expanding about a 6 | % list of "seed" points. Partitions expand in a breadth- 7 | % first sense until a polygon bounday is encountered. SEED 8 | % is an S-by-2 array of XY-coordinates to expand around, 9 | % NODE is an N-by-2 array of polygon vertices, and EDGE is 10 | % an E-by-2 array of polygon edge indexing. Each row in 11 | % EDGE represents an edge of the polygon, such that 12 | % NODE(EDGE(JJ,1),:) and NODE(EDGE(JJ,2),:) are the XY-co- 13 | % ordinates of the endpoints of the JJ-TH edge. PART is an 14 | % S-by-1 cell array of geometry partitions, where each 15 | % PART{KK} is a list of edge indices into EDGE that define 16 | % the KK-TH partition. 17 | % 18 | % This function may be useful when seeking to partition 19 | % complex, non-manifold geometry into a format that's app- 20 | % ropriate for the triangulation algorithms in REFINE2. 21 | % 22 | % See also REFINE2, FIXGEO2, BFSTRI2 23 | 24 | %----------------------------------------------------------- 25 | % Darren Engwirda : 2017 -- 26 | % Email : de2363@columbia.edu 27 | % Last updated : 11/10/2017 28 | %----------------------------------------------------------- 29 | 30 | filename = mfilename('fullpath') ; 31 | filepath = fileparts( filename ) ; 32 | 33 | addpath([filepath,'/aabb-tree']) ; 34 | 35 | %---------------------------------------------- basic checks 36 | if ( ~isnumeric(node) || ... 37 | ~isnumeric(PSLG) || ... 38 | ~isnumeric(seed) ) 39 | error('bfsgeo2:incorrectInputClass' , ... 40 | 'Incorrect input class.') ; 41 | end 42 | 43 | %---------------------------------------------- basic checks 44 | if (ndims(node) ~= +2 || ... 45 | ndims(PSLG) ~= +2 || ... 46 | ndims(seed) ~= +2 ) 47 | error('bfsgeo2:incorrectDimensions' , ... 48 | 'Incorrect input dimensions.'); 49 | end 50 | 51 | if (size(node,2)~= +2 || ... 52 | size(PSLG,2)~= +2 || ... 53 | size(seed,2)~= +2 ) 54 | error('bfsgeo2:incorrectDimensions' , ... 55 | 'Incorrect input dimensions.'); 56 | end 57 | 58 | nnod = size(node,1) ; 59 | nedg = size(PSLG,1) ; 60 | 61 | %---------------------------------------------- basic checks 62 | if (min([PSLG(:)])<+1 || ... 63 | max([PSLG(:)])>nnod) 64 | error('bfsgeo2:invalidInputs', ... 65 | 'Invalid EDGE input array.') ; 66 | end 67 | 68 | %------------------------------------------ assemble full CDT 69 | [node,PSLG,tria] = deltri2 (node,PSLG) ; 70 | 71 | %------------------------------------------ find seeds in CDT 72 | [sptr,stri] = findtria(node,tria,seed) ; 73 | 74 | okay = sptr(:,2) ... 75 | >= sptr(:,1) ; 76 | itri = stri(sptr(okay,1)); 77 | 78 | %------------------------------------------ PART for all seed 79 | part = {} ; 80 | 81 | for ipos = +1 : size (itri,1) 82 | 83 | %-------------- BFS about current tria. 84 | [mark] = ... 85 | bfstri2(PSLG,tria,itri(ipos)) ; 86 | 87 | %-------------- match tria./poly. edges 88 | edge = [ 89 | tria(mark,[1,2]) ; 90 | tria(mark,[2,3]) ; 91 | tria(mark,[3,1]) ; 92 | ] ; 93 | 94 | edge = sort(edge,+2) ; 95 | PSLG = sort(PSLG,+2) ; 96 | 97 | [same,epos] = ... 98 | setset2(edge(:,1:2),PSLG) ; 99 | 100 | %-------------- find match multiplicity 101 | epos = epos(epos>+0) ; 102 | epos = sort(epos); 103 | 104 | eidx = ... 105 | find(diff(epos)) ; 106 | 107 | eptr = ... 108 | [1;eidx+1;length(epos)+1] ; 109 | enum = ... 110 | eptr(2:end)-eptr(1:end-1) ; 111 | 112 | %---------- select singly-matched edges 113 | part{ipos} = ... 114 | epos(eptr(enum == 1)) ; 115 | 116 | end 117 | 118 | end 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /mesh2d/bfstri2.m: -------------------------------------------------------------------------------- 1 | function [seen] = bfstri2(PSLG,tria,seed) 2 | %BFSTRI2 expand about a single seed triangle via BFS. The se- 3 | %arch terminates when constraining edges are encountered. 4 | %SEEN(II) is TRUE if the II-TH triangle is found in the curr- 5 | %ent expansion. 6 | % 7 | % See also BFSGEO2, REFINE2, FIXGEO2 8 | 9 | %----------------------------------------------------------- 10 | % Darren Engwirda : 2017 -- 11 | % Email : de2363@columbia.edu 12 | % Last updated : 01/10/2017 13 | %----------------------------------------------------------- 14 | 15 | seen = []; 16 | 17 | %---------------------------------------------- basic checks 18 | if ( ~isnumeric(tria) || ... 19 | ~isnumeric(seed) ) 20 | error('bfstri2:incorrectInputClass' , ... 21 | 'Incorrect input class.') ; 22 | end 23 | 24 | %---------------------------------------------- basic checks 25 | if (ndims(tria) ~= +2 ) 26 | error('bfstri2:incorrectDimensions' , ... 27 | 'Incorrect input dimensions.'); 28 | end 29 | if (size(tria,2)~= +3 ) 30 | error('bfstri2:incorrectDimensions' , ... 31 | 'Incorrect input dimensions.'); 32 | end 33 | 34 | %---------------------------------------------- extra checks 35 | if ( ~isempty (PSLG) ) 36 | if ( ~isnumeric(PSLG) ) 37 | error('bfstri2:incorrectInputClass' , ... 38 | 'Incorrect input class.') ; 39 | end 40 | 41 | if (ndims(PSLG) ~= +2 ) 42 | error('bfstri2:incorrectDimensions' , ... 43 | 'Incorrect input dimensions.'); 44 | end 45 | if (size(PSLG,2)~= +2 ) 46 | error('bfstri2:incorrectDimensions' , ... 47 | 'Incorrect input dimensions.'); 48 | end 49 | end 50 | 51 | %----------------------------------------- form adj. indices 52 | ntri = size (tria,1); 53 | 54 | [edge,tria] = tricon2 (tria,PSLG); 55 | 56 | seed = seed(:) ; 57 | 58 | list = zeros(ntri,1); 59 | nlst = length (seed); 60 | list(1:nlst) = seed ; 61 | 62 | %----------------------------------------- do BFS iterations 63 | seen = false(ntri,1); 64 | 65 | while (nlst >= +1) 66 | 67 | %-------------- pop tria from stack top 68 | next = list(nlst); 69 | nlst = nlst-1 ; 70 | seen(next) = true; 71 | 72 | %-------------- visit 1-ring neighbours 73 | for eadj = +1 : +3 74 | 75 | epos = tria(next,eadj+3); 76 | 77 | %---------- find adjacent triangles 78 | if (edge(epos,5) == 0) 79 | 80 | if (next ~= edge(epos,3)) 81 | tadj = edge(epos,3); 82 | else 83 | tadj = edge(epos,4); 84 | end 85 | 86 | if (tadj > +0 && ~seen(tadj)) 87 | 88 | %---------- add unvisited neighbour 89 | seen(tadj) = true ; 90 | nlst = nlst+1 ; 91 | list(nlst) = tadj ; 92 | 93 | end 94 | 95 | end 96 | 97 | end 98 | 99 | end 100 | 101 | end 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /mesh2d/cdtbal1.m: -------------------------------------------------------------------------------- 1 | function [bb] = cdtbal1(pp,ee) 2 | %CDTBAL1 compute the circumballs associated with a 1-simplex 3 | %triangulation embedded in R^2. 4 | % [BB] = TRIBAL1(PP,EE) returns the circumscribing balls 5 | % associated with the 1-simplexes in [PP,TT], such that BB 6 | % = [XC,YC,RC.^2]. 7 | 8 | % Darren Engwirda : 2017 -- 9 | % Email : de2363@columbia.edu 10 | % Last updated : 24/03/2017 11 | 12 | %---------------------------------------------- basic checks 13 | if (~isnumeric(pp) || ... 14 | ~isnumeric(ee) ) 15 | error('cdtbal1:incorrectInputClass' , ... 16 | 'Incorrect input class.') ; 17 | end 18 | %---------------------------------------------- basic checks 19 | if (ndims(pp) ~= +2 || ndims(ee) ~= +2) 20 | error('cdtbal1:incorrectDimensions' , ... 21 | 'Incorrect input dimensions.'); 22 | end 23 | if (size(pp,2)~= +2 || size(ee,2) < +2) 24 | error('cdtbal1:incorrectDimensions' , ... 25 | 'Incorrect input dimensions.'); 26 | end 27 | 28 | bb = zeros(size(ee,1),3); 29 | 30 | bb(:,1:2) = (pp(ee(:,1),:)+pp(ee(:,2),:))*.50 ; 31 | bb(:, 3) = ... 32 | sum((pp(ee(:,1),:)-pp(ee(:,2),:)).^2,2)*.25 ; 33 | 34 | end 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /mesh2d/cdtbal2.m: -------------------------------------------------------------------------------- 1 | function [cc] = cdtbal2(pp,ee,tt) 2 | %CDTBAL2 compute the modified circumballs associated with a 3 | %constrained 2-simplex Delaunay triangulation in R^2. 4 | % [CC] = CDTBAL2(PP,EE,TT) returns the smallest enclosing 5 | % balls associated with the triangles in [PP,TT], such th- 6 | % at CC = [XC,YC,RC.^2]. Such balls never lie outside the 7 | % boundaries of the associated CDT. See TRICON2 for info- 8 | % mation regarding the edge array EE. 9 | 10 | % Darren Engwirda : 2017 -- 11 | % Email : de2363@columbia.edu 12 | % Last updated : 01/10/2017 13 | 14 | %---------------------------------------------- basic checks 15 | if (~isnumeric(pp) || ~isnumeric(ee) || ... 16 | ~isnumeric(tt) ) 17 | error('cdtbal2:incorrectInputClass' , ... 18 | 'Incorrect input class.') ; 19 | end 20 | 21 | %---------------------------------------------- basic checks 22 | if (ndims(pp) ~= +2 || ndims(ee) ~= +2 || ... 23 | ndims(tt) ~= +2 ) 24 | error('cdtbal2:incorrectDimensions' , ... 25 | 'Incorrect input dimensions.'); 26 | end 27 | if (size(pp,2)~= +2 || size(ee,2) < +5 || ... 28 | size(tt,2) < +6 ) 29 | error('cdtbal2:incorrectDimensions' , ... 30 | 'Incorrect input dimensions.'); 31 | end 32 | 33 | %----------------------------------------- calc. circumballs 34 | cc = tribal2(pp,tt); 35 | 36 | %------------------------ replace with face-balls if smaller 37 | cc = minfac2(cc,pp,ee,tt,1,2,3) ; 38 | cc = minfac2(cc,pp,ee,tt,2,3,1) ; 39 | cc = minfac2(cc,pp,ee,tt,3,1,2) ; 40 | 41 | end 42 | 43 | function [cc] = minfac2(cc,pp,ee,tt,ni,nj,nk) 44 | %MINFAC2 modify the set of circumballs to constrain centres 45 | %to the boundaries of the CDT. 46 | % [CM] = MINFAC2(CC,PP,EE,TT,NI,NJ,NK) returns the set of 47 | % modified circmballs CM, where any ball CC lying outside 48 | % the boundaries of the CDT [PP,EE,TT] is replaced by the 49 | % edge-centred diametric ball. [NI,NJ] are the local inde- 50 | % xes associated with an edge to test. NK is the local in- 51 | % dex of the opposite vertex. 52 | 53 | %------------------------------------------------ outer edge 54 | EF = ee(tt(:,ni+3),5) > +0 ; 55 | 56 | %------------------------------------------------ edge balls 57 | bc = (pp(tt(EF,ni),:)+pp(tt(EF,nj),:))*.50; 58 | 59 | %------------------------------------------------ edge radii 60 | br = sum((bc(:,1:2)-pp(tt(EF,ni),:)).^2,2)... 61 | + sum((bc(:,1:2)-pp(tt(EF,nj),:)).^2,2); 62 | br = br * +0.5 ; 63 | 64 | %------------------------------------------- enclosing radii 65 | ll = sum((bc(:,1:2)-pp(tt(EF,nk),:)).^2,2); 66 | 67 | %------------------------------------------- replace if min. 68 | bi = br >= ll ... 69 | & br <= cc(EF,3) ; 70 | ei = find(EF) ; 71 | ti = ei (bi) ; 72 | 73 | %------------------------------------------- replace is min. 74 | cc(ti,1:2) = bc(bi,:) ; 75 | cc(ti, 3) = br(bi,:) ; 76 | 77 | end 78 | 79 | 80 | -------------------------------------------------------------------------------- /mesh2d/cfmtri2.m: -------------------------------------------------------------------------------- 1 | function [vert,econ,tria] = cfmtri2(vert,econ) 2 | %CFMTRI2 compute a conforming 2-simplex Delaunay triangulat- 3 | %ion in the two-dimensional plane. 4 | % [VERT,CONN,TRIA]=CFMTRI2(VERT,CONN) computes the confor- 5 | % ming Delaunay trianguation, given the points VERT, and 6 | % edge constraints CONN. New points are inserted to bisect 7 | % edge constraints until all are recovered. VERT is a 8 | % V-by-2 array of XY coordinates to be triangulated, TRIA 9 | % is a T-by-3 array of vertex indexing, with each row 10 | % defining a triangle, such that VERT(TRIA(II,1),:), 11 | % VERT(TRIA(II,2),:) and VERT(TRIA(II,3),:) are the coord- 12 | % inates of the II-TH triangle. CONN is a C-by-2 array of 13 | % constraining edges, where each row defines an edge, as 14 | % per the TRIA array. 15 | % 16 | % See also DELTRI2, DELAUNAYN 17 | 18 | % Darren Engwirda : 2017 -- 19 | % Email : de2363@columbia.edu 20 | % Last updated : 07/07/2017 21 | 22 | %---------------------------------------------- basic checks 23 | if ( ~isnumeric(vert) || ... 24 | ~isnumeric(econ) ) 25 | error('cfmtri2:incorrectInputClass' , ... 26 | 'Incorrect input class.') ; 27 | end 28 | 29 | %---------------------------------------------- basic checks 30 | if (ndims(vert) ~= +2 || ndims(econ) ~= +2) 31 | error('cfmtri2:incorrectDimensions' , ... 32 | 'Incorrect input dimensions.'); 33 | end 34 | 35 | if (size(vert,2)~= +2 || size(econ,2)~= +2) 36 | error('cfmtri2:incorrectDimensions' , ... 37 | 'Incorrect input dimensions.'); 38 | end 39 | 40 | %-- the DELAUNAYN routine is *not* well-behaved numerically, 41 | %-- so explicitly re-scale the problem about [-1,-1; +1,+1]. 42 | vmax = max(vert,[],1) ; 43 | vmin = min(vert,[],1) ; 44 | 45 | vdel = vmax - vmin; 46 | vdel = mean(vdel) ; 47 | vdel = vdel * +.5 ; 48 | 49 | vmid = vmax + vmin; 50 | vmid = vmid * +.5 ; 51 | 52 | vert = vert - vmid; 53 | vert = vert / vdel; 54 | 55 | %-- keep bisecting edge constraints until they are all reco- 56 | %-- vered! 57 | while (true) 58 | 59 | %----------------- un-constrained delaunay triangulation 60 | tria = delaunay2(vert) ; 61 | 62 | nv = size(vert,+1); 63 | nt = size(tria,+1); 64 | 65 | %----------------------------- build non-unique edge-set 66 | ee = zeros(nt*3,2); 67 | ee((1:nt)+nt*0,:) = tria(:,[1,2]); 68 | ee((1:nt)+nt*1,:) = tria(:,[2,3]); 69 | ee((1:nt)+nt*2,:) = tria(:,[3,1]); 70 | 71 | %----------------- find constraints within tria-edge set 72 | [in] = setset2(econ,ee) ; 73 | 74 | %----------------------------- done when have contraints 75 | if (all(in)), break; end 76 | 77 | %----------------------------- un-recovered edge centres 78 | vm = vert(econ(~in,1),:) ... 79 | + vert(econ(~in,2),:) ; 80 | vm = vm * +.5 ; 81 | 82 | %----------------------------- un-recovered edge indexes 83 | ev = nv+(1:size(vm,1))'; 84 | en = [econ(~in,+1), ev; 85 | econ(~in,+2), ev]; 86 | 87 | %----------------------------- push new vert/edge arrays 88 | vert = [vert( :,:); vm]; 89 | econ = [econ(in,:); en]; 90 | 91 | end 92 | 93 | %--------------------------------- undo geomertic re-scaling 94 | vert = vert * vdel ; 95 | vert = vert + vmid ; 96 | 97 | end 98 | 99 | function [tria] = delaunay2(vert) 100 | %DELAUNAY2 thin wrapper for DELAUNAYN, so that we can have a 101 | % more efficient version in OCTAVE... 102 | 103 | isoctave = exist( ... 104 | 'OCTAVE_VERSION','builtin')>+0; 105 | 106 | if (isoctave) 107 | 108 | %-- call QHULL and then filter zero-volume simplexes via 109 | %-- vectorised area comparisons. 110 | 111 | %-- note silliness re. EVAL, so that MATLAB doesn't com- 112 | %-- plain re. OCTAVE '__' names. 113 | 114 | tria = eval( ... 115 | '__delaunayn__(vert)') ; 116 | 117 | ab = vert(tria(:,2),:) ... 118 | - vert(tria(:,1),:) ; 119 | ac = vert(tria(:,3),:) ... 120 | - vert(tria(:,1),:) ; 121 | 122 | aa = ab(:,1).* ac(:,2) ... 123 | - ab(:,2).* ac(:,1) ; 124 | 125 | lb = sumsq(ab,2) ; 126 | lc = sumsq(ac,2) ; 127 | 128 | ll = max (lb,lc) ; 129 | 130 | keep = abs(aa) >= ll * eps^.8 ; 131 | 132 | tria = tria(keep,:); 133 | 134 | else 135 | 136 | %-- the default call in MATLAB seems to be fast enough!! 137 | 138 | tria = ... 139 | delaunay (vert(:,1),vert(:,2)); 140 | 141 | end 142 | 143 | end 144 | 145 | 146 | -------------------------------------------------------------------------------- /mesh2d/compile.m: -------------------------------------------------------------------------------- 1 | function compile 2 | %COMPILE compile any MESH2D dependencies into MEX//OCT files 3 | %for MATLAB//OCTAVE. 4 | % 5 | % See also TRIDEMO, REFINE2, SMOOTH2 6 | 7 | %----------------------------------------------------------- 8 | % Darren Engwirda : 2017 -- 9 | % Email : de2363@columbia.edu 10 | % Last updated : 17/07/2017 11 | %----------------------------------------------------------- 12 | 13 | if (exist('OCTAVE_VERSION','builtin') > 0) 14 | 15 | fprintf(1,'\n'); 16 | 17 | fprintf(1,'OCTAVE detected: Compiling OCT files.\n'); 18 | 19 | fprintf(1,'\n'); 20 | 21 | fprintf(1,'Compiling INPOLY2...\n'); 22 | mkoctfile('inpoly2_oct.cpp'); 23 | 24 | %%!! add additional oct-file builds here... 25 | 26 | 27 | fprintf(1,'\n'); 28 | 29 | fprintf(1,'Compiling MESH2D for OCTAVE: DONE.\n'); 30 | 31 | fprintf(1,'\n'); 32 | 33 | else 34 | 35 | fprintf(1,'\n'); 36 | 37 | fprintf(1,'MATLAB detected: nothing to compile!!\n'); 38 | 39 | fprintf(1,'\n'); 40 | 41 | fprintf(1,'Compiling MESH2D for MATLAB: DONE.\n'); 42 | 43 | fprintf(1,'\n'); 44 | 45 | end 46 | 47 | end 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /mesh2d/deltri2.m: -------------------------------------------------------------------------------- 1 | function [vert,conn,tria,tnum] = deltri2(varargin) 2 | %DELTRI2 compute a constrained 2-simplex Delaunay triangula- 3 | %tion in the two-dimensional plane. 4 | % [VERT,CONN,TRIA,TNUM]=DELTRI2(VERT,CONN,NODE,PSLG,PART) 5 | % computes the Delaunay trianguation {VERT,TRIA}, the con- 6 | % straints CONN, and the "inside" status vector TNUM. VERT 7 | % is an V-by-2 array of XY coordinates to be triangulated, 8 | % TRIA is a T-by-3 array of vertex indexing, where each 9 | % row defines a triangle, such that VERT(TRIA(II,1),:), 10 | % VERT(TRIA(II,2),:) and VERT(TRIA(II,3),:) are the coord- 11 | % inates of the II-TH triangle. CONN is a C-by-2 array of 12 | % constraining edges, where each row defines an edge, as 13 | % per TRIA. The additional arguments NODE,PSLG and PART 14 | % define a (mutliply-connected) polygonal region, where 15 | % NODE is an N-by-2 array of vertices and PSLG is a P-by-2 16 | % array of edges (a piecewise-straight-line-graph), where 17 | % each row defines an edge as a pair of indices into NODE. 18 | % PART is a cell-array of polygonal "parts", where each 19 | % element PART{KK} is an array of edge indices defining a 20 | % polygonal region. PSLG(PART{KK},:) is the set of edges 21 | % in the KK-TH part. TNUM is a T-by-1 array of part index- 22 | % ing, such that TNUM(II) is the index of the part in whi- 23 | % ch the II-TH triangle resides. 24 | % 25 | % See also DELAUNAYTRIANGULATION, DELAUNAYTRI, DELAUNAYN 26 | 27 | % Darren Engwirda : 2017 -- 28 | % Email : de2363@columbia.edu 29 | % Last updated : 10/07/2017 30 | 31 | vert = []; conn = []; node = []; PSLG = []; 32 | part = {}; kind = 'constrained'; 33 | 34 | %---------------------------------------------- extract args 35 | if (nargin>=+1), vert = varargin{1}; end 36 | if (nargin>=+2), conn = varargin{2}; end 37 | if (nargin>=+3), node = varargin{3}; end 38 | if (nargin>=+4), PSLG = varargin{4}; end 39 | if (nargin>=+5), part = varargin{5}; end 40 | if (nargin>=+6), kind = varargin{6}; end 41 | 42 | %---------------------------------------------- basic checks 43 | if (~isnumeric(vert) || ~isnumeric(conn) || ... 44 | ~isnumeric(node) || ~isnumeric(PSLG) || ... 45 | ~iscell (part) || ~ischar (kind) ) 46 | error('deltri2:incorrectInputClass' , ... 47 | 'Incorrect input class.') ; 48 | end 49 | 50 | nvrt = size(vert,+1) ; nnod = size(node,+1) ; 51 | nedg = size(PSLG,+1) ; 52 | 53 | %---------------------------------------------- basic checks 54 | if (ndims(vert) ~= +2 || ndims(conn) ~= +2) 55 | error('deltri2:incorrectDimensions' , ... 56 | 'Incorrect input dimensions.'); 57 | end 58 | if (size(vert,2)~= +2 || size(conn,2)~= +2) 59 | error('deltri2:incorrectDimensions' , ... 60 | 'Incorrect input dimensions.'); 61 | end 62 | 63 | if (min([conn(:)])<+1 || max([conn(:)])>nvrt) 64 | error('deltri2:invalidInputs', ... 65 | 'Invalid CONN input array.') ; 66 | end 67 | 68 | %---------------------------------------------- basic checks 69 | if (nargin >= +3) 70 | 71 | if (ndims(node) ~= +2 || ndims(PSLG) ~= +2) 72 | error('deltri2:incorrectDimensions' , ... 73 | 'Incorrect input dimensions.'); 74 | end 75 | if (size(node,2)~= +2 || size(PSLG,2)~= +2) 76 | error('deltri2:incorrectDimensions' , ... 77 | 'Incorrect input dimensions.'); 78 | end 79 | 80 | if (min([PSLG(:)])<+1 || max([PSLG(:)])>nnod) 81 | error('deltri2:invalidInputs', ... 82 | 'Invalid EDGE input array.') ; 83 | end 84 | 85 | pmin = cellfun(@min,part); 86 | pmax = cellfun(@max,part); 87 | 88 | if (min([pmin(:)])<+1 || max([pmax(:)])>nedg) 89 | error('deltri2:invalidInputs', ... 90 | 'Invalid PART input array.') ; 91 | end 92 | 93 | end 94 | 95 | %------------------------------------ compute Delaunay tria. 96 | switch (lower(kind)) 97 | case 'constrained' 98 | 99 | if (exist( ... 100 | 'delaunayTriangulation') == +2 ) 101 | %-------------------------------- use class if available 102 | dtri = ... 103 | delaunayTriangulation(vert,conn) ; 104 | vert = dtri.Points; 105 | conn = dtri.Constraints; 106 | tria = dtri.ConnectivityList; 107 | else 108 | if (exist('DelaunayTri') == +2 ) 109 | %-------------------------------- use class if available 110 | dtri = DelaunayTri (vert,conn) ; 111 | vert = dtri.X; 112 | conn = dtri.Constraints; 113 | tria = dtri.Triangulation; 114 | else 115 | %-------------------------------- *fall-back* onto qhull 116 | [vert,conn,tria] ... 117 | = cfmtri2(vert,conn) ; 118 | end 119 | end 120 | 121 | case 'conforming' 122 | 123 | %-------------------------------- "conforming" delaunay! 124 | [vert,conn,tria] ... 125 | = cfmtri2(vert,conn) ; 126 | 127 | otherwise 128 | error('deltri2:invalidInputs', ... 129 | 'Invalid KIND selection.') ; 130 | 131 | end 132 | 133 | %------------------------------------ calc. "inside" status! 134 | tnum = zeros(size(tria,+1),+1) ; 135 | 136 | if (nargin >= +3) 137 | 138 | tmid = vert(tria(:,1),:) ... 139 | + vert(tria(:,2),:) ... 140 | + vert(tria(:,3),:) ; 141 | tmid = tmid / +3.0; 142 | 143 | for ppos = 1 : length(part) 144 | 145 | [stat] = inpoly2( ... 146 | tmid,node , ... 147 | PSLG(part{ppos},:)) ; 148 | 149 | tnum(stat) = ppos ; 150 | 151 | end 152 | 153 | %------------------------------------ keep "interior" tria's 154 | tria = tria(tnum>+0,:) ; 155 | tnum = tnum(tnum>+0,:) ; 156 | 157 | end 158 | 159 | %------------------------------------ flip for correct signs 160 | area = triarea(vert,tria) ; 161 | 162 | tria(area<0.,:) = ... 163 | tria(area<0.,[1,3,2]) ; 164 | 165 | end 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /mesh2d/fixgeo2.m: -------------------------------------------------------------------------------- 1 | function [node,PSLG,part] = fixgeo2(varargin) 2 | %FIXGEO2 attempts to "fix" issues with geometry definitions. 3 | % [NNEW,ENEW,PNEW] = FIXGEO2(NODE,EDGE,PART) returns a new 4 | % "repaired" geometry definition. Currently, the following 5 | % operations are performed: 6 | % 7 | % (1) redundant nodes are "zipped" together. 8 | % (2) redundant edges are deleted. 9 | % (3) edges are split about intersecting nodes. 10 | % (4) edges are split about intersecting edges. 11 | % 12 | % See also REFINE2, BFSGEO2 13 | 14 | %----------------------------------------------------------- 15 | % Darren Engwirda : 2017 -- 16 | % Email : de2363@columbia.edu 17 | % Last updated : 10/10/2017 18 | %----------------------------------------------------------- 19 | 20 | filename = mfilename('fullpath') ; 21 | filepath = fileparts( filename ) ; 22 | 23 | addpath([filepath,'/aabb-tree']) ; 24 | 25 | %---------------------------------------------- extract ARGS 26 | node = []; PSLG = []; part = {}; 27 | 28 | if (nargin>=+1), node = varargin{1}; end 29 | if (nargin>=+2), PSLG = varargin{2}; end 30 | if (nargin>=+3), part = varargin{3}; end 31 | 32 | if (isempty(node)), return ; end 33 | 34 | %---------------------------------------------- default EDGE 35 | nnum = size(node,1); 36 | 37 | if (isempty(PSLG)) 38 | PSLG = [(1:nnum-1)',(2:nnum-0)';nnum,1]; 39 | end 40 | 41 | %---------------------------------------------- default PART 42 | enum = size(PSLG,1); 43 | 44 | if (isempty(part)), part{1} = (1:enum)'; end 45 | 46 | %---------------------------------------------- basic checks 47 | if ( ~isnumeric(node) || ... 48 | ~isnumeric(PSLG) || ... 49 | ~iscell(part) ) 50 | error('fixgeo2:incorrectInputClass', ... 51 | 'Incorrect input class.') ; 52 | end 53 | 54 | %---------------------------------------------- basic checks 55 | if (ndims(node) ~= +2 || ... 56 | ndims(PSLG) ~= +2 ) 57 | error('fixgeo2:incorrectDimensions', ... 58 | 'Incorrect input dimensions.') ; 59 | end 60 | if (size(node,2)~= +2 || ... 61 | size(PSLG,2)~= +2 ) 62 | error('fixgeo2:incorrectDimensions', ... 63 | 'Incorrect input dimensions.') ; 64 | end 65 | 66 | %---------------------------------------------- basic checks 67 | if (min([PSLG(:)])<+1 || ... 68 | max([PSLG(:)]) > nnum) 69 | error('fixgeo2:invalidInputs', ... 70 | 'Invalid EDGE input array.') ; 71 | end 72 | 73 | pmin = cellfun(@min,part); 74 | pmax = cellfun(@max,part); 75 | 76 | if (min([pmin(:)])<+1 || ... 77 | max([pmax(:)]) > enum) 78 | error('fixgeo2:invalidInputs', ... 79 | 'Invalid PART input array.') ; 80 | end 81 | 82 | %------------------------------------- try to "fix" geometry 83 | while (true) 84 | 85 | nnum = size(node,1) ; 86 | enum = size(PSLG,1) ; 87 | 88 | %--------------------------------- prune redundant nodes 89 | [node,PSLG,part] = ... 90 | prunenode(node,PSLG,part) ; 91 | 92 | %--------------------------------- prune redundant edges 93 | [node,PSLG,part] = ... 94 | pruneedge(node,PSLG,part) ; 95 | 96 | %--------------------------------- node//edge intersect! 97 | done = false; 98 | while (~done) 99 | 100 | [node,PSLG,part,done] = ... 101 | splitnode(node,PSLG,part) ; 102 | 103 | end 104 | 105 | %--------------------------------- edge//edge intersect! 106 | done = false; 107 | while (~done) 108 | 109 | [node,PSLG,part,done] = ... 110 | splitedge(node,PSLG,part) ; 111 | 112 | end 113 | 114 | if (size(node,1) == nnum && ... 115 | size(PSLG,1) == enum ) 116 | %--------------------------------- iterate if any change 117 | break ; 118 | end 119 | 120 | end 121 | 122 | end 123 | 124 | function [node,PSLG,part,done] ... 125 | = prunenode(node,PSLG,part) 126 | %PRUNENODE "prune" redundant nodes by "zipping" those within 127 | %tolerance of each other. 128 | 129 | done = true ; 130 | 131 | %------------------------------------- calc. "zip" tolerance 132 | nmin = min(node,[],+1) ; 133 | nmax = max(node,[],+1) ; 134 | ndel = nmax - nmin; 135 | ztol = eps ^ 0.80; 136 | zlen = ztol * max(ndel); 137 | 138 | %------------------------------------- index clustered nodes 139 | ball = zeros(size(node,1),3) ; 140 | ball(:,1:2) = node(:,1:2); 141 | ball(:, 3) = zlen * zlen; 142 | 143 | [vp,vi] = ... 144 | findball(ball,node(:,1:2)) ; 145 | 146 | %------------------------------------- "zip" clustered nodes 147 | [vt,iv] = ... 148 | sort(vp(:,2) - vp(:,1)); 149 | 150 | izip = zeros(size(node,1),1) ; 151 | imap = zeros(size(node,1),1) ; 152 | 153 | for kk = size(vp,1):-1:+1 154 | ii = iv(kk); 155 | for ip = vp(ii,1) : vp(ii,2) 156 | jj = vi(ip) ; 157 | if (izip(ii) == 0 && ... 158 | izip(jj) == 0 && ... 159 | ii~= jj ) 160 | 161 | done = false ; 162 | 163 | %----------------------------- "zip" node JJ into II 164 | izip(jj) = ii ; 165 | 166 | end 167 | end 168 | end 169 | 170 | %------------------------------------- re-index nodes//edges 171 | next = +1 ; 172 | for kk = +1:+1:size(vp,1) 173 | if (izip(kk) == +0) 174 | imap(kk) = next ; 175 | next = next + 1 ; 176 | end 177 | end 178 | 179 | imap(izip ~= 0) = ... 180 | imap(izip(izip ~= 0)); 181 | 182 | PSLG = imap(PSLG) ; 183 | 184 | node = node(izip == 0,:); 185 | 186 | end 187 | 188 | function [node,PSLG,part] = pruneedge(node,PSLG,part) 189 | %PRUNEEDGE "prune" redundant topology. 190 | 191 | %------------------------------------- prune redundant topo. 192 | [ptmp,ivec,jvec] = ... 193 | unique(sort(PSLG,+2),'rows') ; 194 | 195 | PSLG = PSLG(ivec,:); 196 | 197 | for ppos = +1 : length(part) 198 | 199 | %--------------------------------- re-index part labels! 200 | part{ppos} = ... 201 | unique(jvec(part{ppos})) ; 202 | 203 | end 204 | 205 | %------------------------------------- prune collapsed topo. 206 | keep = diff(PSLG,[],2) ~= +0 ; 207 | 208 | jvec = zeros(size(PSLG,1),1) ; 209 | jvec(keep) = +1; 210 | jvec = cumsum(jvec); 211 | 212 | PSLG = PSLG(keep,:); 213 | 214 | for ppos = +1 : length(part) 215 | 216 | %--------------------------------- re-index part labels! 217 | part{ppos} = ... 218 | unique(jvec(part{ppos})) ; 219 | 220 | end 221 | 222 | end 223 | 224 | function [node,PSLG,part,done] ... 225 | = splitnode(node,PSLG,part) 226 | %SPLITNODE "split" PSLG about intersecting nodes. 227 | 228 | done = true ; 229 | 230 | mark = false(size(PSLG,1),1); 231 | ediv = zeros(size(PSLG,1),1); 232 | pair = zeros(size(PSLG,1),2); 233 | 234 | %------------------------------------- node//edge intersect! 235 | [lp,li] = findline ( ... 236 | node(PSLG(:,1),1:2), ... 237 | node(PSLG(:,2),1:2),node(:,1:2)) ; 238 | 239 | %------------------------------------- node//edge splitting! 240 | nn = +0 ; 241 | for ii = +1:+1:size(lp,1) 242 | for ip = lp(ii,1) : lp(ii,2) 243 | jj = li(ip) ; 244 | ni = PSLG(jj,1) ; 245 | nj = PSLG(jj,2) ; 246 | if (ni~=ii && ... 247 | nj~=ii && ~mark(jj)) 248 | 249 | done = false; 250 | 251 | %----------------------------- mark seen, descendent 252 | mark(jj) = true ; 253 | 254 | nn = nn + 1 ; 255 | 256 | pair(nn,1) = jj ; 257 | pair(nn,2) = ii ; 258 | 259 | end 260 | end 261 | end 262 | 263 | if (nn == +0), return ; end 264 | 265 | %------------------------------------- re-index intersection 266 | pair = pair(1:nn,:); 267 | 268 | inod = PSLG(pair(:,1),1); 269 | jnod = PSLG(pair(:,1),2); 270 | 271 | xnod = pair(:,2); 272 | 273 | ediv(pair(:,1)) = ... 274 | (+1:nn)' + size(PSLG,1); 275 | 276 | PSLG(pair(:,1),1) = inod; 277 | PSLG(pair(:,1),2) = xnod; 278 | 279 | PSLG = [PSLG; xnod,jnod]; 280 | 281 | %------------------------------------- re-index edge in part 282 | for ppos = +1:length(part) 283 | 284 | enew = ediv(part{ppos}); 285 | enew = enew(enew ~= 0) ; 286 | 287 | part{ppos} = ... 288 | [part{ppos}; enew] ; 289 | 290 | end 291 | 292 | end 293 | 294 | function [node,PSLG,part,done] ... 295 | = splitedge(node,PSLG,part) 296 | %SPLITEDGE "split" PSLG about intersecting edges. 297 | 298 | done = true ; 299 | 300 | mark = zeros(size(PSLG,1),2); 301 | pair = zeros(size(PSLG,1),2); 302 | ediv = zeros(size(PSLG,1),1); 303 | flag = zeros(size(node,1),1); 304 | 305 | %------------------------------------- edge//edge intersect! 306 | [lp,li] = lineline( ... 307 | node(PSLG(:,1),1:2), ... 308 | node(PSLG(:,2),1:2), ... 309 | node(PSLG(:,1),1:2), ... 310 | node(PSLG(:,2),1:2)) ; 311 | 312 | %------------------------------------- edge//edge splitting! 313 | nn = +0 ; 314 | %---------------------------------- parse NaN delimited data 315 | for ii = +1 : +1 : size(lp,1) 316 | 317 | flag(PSLG(ii,1)) = ii ; 318 | flag(PSLG(ii,2)) = ii ; 319 | 320 | for ip = lp(ii,1) : lp(ii,2) 321 | jj = li(ip) ; 322 | if (mark(ii) == +0 && ... 323 | mark(jj) == +0 && ... 324 | ii ~= jj) 325 | 326 | ni = PSLG(jj,1) ; 327 | nj = PSLG(jj,2) ; 328 | 329 | if (flag(ni) ~= ii && ... 330 | flag(nj) ~= ii ) 331 | 332 | done = false; 333 | 334 | %------------------------- mark seen, edge-pairs 335 | mark(ii) = +1 ; 336 | mark(jj) = +1 ; 337 | 338 | nn = nn + 1 ; 339 | 340 | pair(nn,1) = ii ; 341 | pair(nn,2) = jj ; 342 | 343 | end 344 | 345 | end 346 | end 347 | 348 | end 349 | 350 | if (nn == +0), return ; end 351 | 352 | %------------------------------------- re-index intersection 353 | pair = pair(1:nn,:) ; 354 | 355 | [okay,tval,sval] = linenear ( ... 356 | node(PSLG(pair(:,1),1),1:2), ... 357 | node(PSLG(pair(:,1),2),1:2), ... 358 | node(PSLG(pair(:,2),1),1:2), ... 359 | node(PSLG(pair(:,2),2),1:2)) ; 360 | 361 | pmid = .5 * ( ... 362 | node(PSLG(pair(:,1),2),1:2)+ ... 363 | node(PSLG(pair(:,1),1),1:2)) ; 364 | pdel = .5 * ( ... 365 | node(PSLG(pair(:,1),2),1:2)- ... 366 | node(PSLG(pair(:,1),1),1:2)) ; 367 | 368 | ppos = pmid+[tval,tval].*pdel; 369 | 370 | qmid = .5 * ( ... 371 | node(PSLG(pair(:,2),2),1:2)+ ... 372 | node(PSLG(pair(:,2),1),1:2)) ; 373 | qdel = .5 * ( ... 374 | node(PSLG(pair(:,2),2),1:2)- ... 375 | node(PSLG(pair(:,2),1),1:2)) ; 376 | 377 | qpos = qmid+[sval,sval].*qdel; 378 | 379 | inod = PSLG(pair(:,1),1); 380 | jnod = PSLG(pair(:,1),2); 381 | 382 | anod = PSLG(pair(:,2),1); 383 | bnod = PSLG(pair(:,2),2); 384 | 385 | xnod = (+1:nn)'+size(node,1) ; 386 | 387 | iedg = (+1:nn)'+size(PSLG,1) ... 388 | + 0 * size(pair,1) ; 389 | jedg = (+1:nn)'+size(PSLG,1) ... 390 | + 1 * size(pair,1) ; 391 | 392 | ediv(pair(:,1),1) = iedg; 393 | ediv(pair(:,2),1) = jedg; 394 | 395 | PSLG(pair(:,1),1) = inod; 396 | PSLG(pair(:,1),2) = xnod; 397 | 398 | PSLG(pair(:,2),1) = anod; 399 | PSLG(pair(:,2),2) = xnod; 400 | 401 | PSLG = [PSLG; xnod,jnod]; 402 | PSLG = [PSLG; xnod,bnod]; 403 | 404 | node = [node;(ppos+qpos)*.5] ; 405 | 406 | %------------------------------------- re-index edge in part 407 | for ppos = +1:length(part) 408 | 409 | enew = ediv(part{ppos}); 410 | enew = enew(enew ~= 0) ; 411 | 412 | part{ppos} = ... 413 | [part{ppos}; enew] ; 414 | 415 | end 416 | 417 | end 418 | 419 | 420 | -------------------------------------------------------------------------------- /mesh2d/getnan2.m: -------------------------------------------------------------------------------- 1 | function [node,edge] = getnan2(varargin) 2 | %GETNAN2 parse a NaN delimited polygon into a PSLG. 3 | % [NODE,EDGE] = GETNAN2(NANS,FILT) converts a set of NaN 4 | % delimited polygons to a PSLG representation. NANS is an 5 | % D-by-2 array of coordinates, with polygon vertices spec- 6 | % ified in connsecutive order, and delimited by NaN values 7 | % where breaks between polygons occur. FILT is a length 2 8 | % vector of "small-feature" filter values. Small polygons 9 | % wiith axis-aligned extents less than FILT are stripped 10 | % from the output. NODE is an N-by-2 array of coordinates 11 | % and EDGE is an E-by-2 array of edge indexing. Each row 12 | % in EDGE represents an edge of the polygon, such that 13 | % NODE(EDGE(JJ,1),:) and NODE(EDGE(JJ,2),:) are the coord- 14 | % inates of the endpoints of the JJ-TH edge. 15 | % 16 | % See also FIXGEO2, BFSGEO2, REFINE2 17 | 18 | %----------------------------------------------------------- 19 | % Darren Engwirda : 2017 -- 20 | % Email : de2363@columbia.edu 21 | % Last updated : 06/10/2017 22 | %----------------------------------------------------------- 23 | 24 | data = [] ; filt = +0. ; 25 | 26 | if (nargin>=+1), data = varargin{1}; end 27 | if (nargin>=+2), filt = varargin{2}; end 28 | 29 | %---------------------------------------------- basic checks 30 | if ( ~isnumeric(data) || ... 31 | ~isnumeric(filt) ) 32 | error('getnan2:incorrectInputClass' , ... 33 | 'Incorrect input class.') ; 34 | end 35 | 36 | %---------------------------------------------- basic checks 37 | if (ndims(data) ~= +2 ) 38 | error('getnan2:incorrectDimensions' , ... 39 | 'Incorrect input dimensions.'); 40 | end 41 | 42 | if (size(data,2)~= +2 || ... 43 | size(filt,2)>= +2 ) 44 | error('getnan2:incorrectDimensions' , ... 45 | 'Incorrect input dimensions.'); 46 | end 47 | 48 | %---------------------------------- parse NaN delimited data 49 | nvec = find(isnan(data(:,1))) ; 50 | 51 | if (isempty(nvec)) % no NaN's at all! 52 | nvec = [nvec ; size(data,1) ] ; 53 | end 54 | 55 | if (nvec(end)~=size(data,1) ) % append last poly 56 | nvec = [nvec ; size(data,1) ] ; 57 | end 58 | 59 | node = zeros(size(data,1), 2) ; 60 | edge = zeros(size(data,1), 2) ; 61 | 62 | next = +1; nout = +1; eout = +1 ; 63 | 64 | for npos = +1 : length(nvec) 65 | 66 | stop = nvec(npos) ; 67 | 68 | pnew = data(next:stop-1,1:2); 69 | 70 | pmin = min(pnew,[],1) ; 71 | pmax = max(pnew,[],1) ; 72 | 73 | pdel = pmax-pmin; 74 | 75 | if (~isempty(pnew)) 76 | if (any(pdel>filt)) 77 | %---------------------------------- push polygon onto output 78 | nnew = size(pnew,1); 79 | 80 | enew = [(1:nnew-1)', ... 81 | (2:nnew-0)'; ... 82 | nnew, +1 ] ; 83 | enew = enew+nout-1 ; 84 | 85 | mnew = size(enew,1); 86 | 87 | pvec = nout:nout+nnew-1; 88 | evec = eout:eout+mnew-1; 89 | 90 | node(pvec,:) = pnew; 91 | edge(evec,:) = enew; 92 | 93 | nout = nout + nnew ; 94 | eout = eout + mnew ; 95 | 96 | end 97 | end 98 | 99 | next = stop + 1 ; 100 | 101 | end 102 | 103 | node = node(+1:nout-1,:) ; 104 | edge = edge(+1:eout-1,:) ; 105 | 106 | end 107 | 108 | 109 | -------------------------------------------------------------------------------- /mesh2d/idxtri2.m: -------------------------------------------------------------------------------- 1 | function [tree] = idxtri2(vert,tria) 2 | %IDXTRI2 create a spatial-indexing structure for a 2-simplex 3 | %triangulation embedded in the two-dimensional plane. 4 | % [TREE] = IDXTRI2(VERT,TRIA) returns an AABB-tree design- 5 | % ed to accelerate spatial queries into the triangulation 6 | % defined by {VERT,TRIA}. VERT is a V-by-2 array of XY co- 7 | % ordinates and TRIA is a T-by-3 array of triangles. Each 8 | % row defines a triangle, such that VERT(TRIA(II,1),:), 9 | % VERT(TRIA(II,2),:) and VERT(TRIA(II,3),:) are the coord- 10 | % inates of the II-TH triangle. 11 | % 12 | % See also TRIHFN2, LFSHFN2, MAKETREE 13 | 14 | % Darren Engwirda : 2017 -- 15 | % Email : de2363@columbia.edu 16 | % Last updated : 03/07/2017 17 | 18 | filename = mfilename('fullpath'); 19 | filepath = fileparts( filename ); 20 | 21 | addpath([filepath,'/aabb-tree']); 22 | 23 | %---------------------------------------------- basic checks 24 | if ( ~isnumeric(vert) || ... 25 | ~isnumeric(tria) ) 26 | error('idxtri2:incorrectInputClass' , ... 27 | 'Incorrect input class.') ; 28 | end 29 | 30 | %---------------------------------------------- basic checks 31 | if (ndims(vert) ~= +2 || ... 32 | ndims(tria) ~= +2 ) 33 | error('idxtri2:incorrectDimensions' , ... 34 | 'Incorrect input dimensions.'); 35 | end 36 | if (size(vert,2)~= +2 || ... 37 | size(tria,2) < +3 ) 38 | error('idxtri2:incorrectDimensions' , ... 39 | 'Incorrect input dimensions.'); 40 | end 41 | 42 | nvrt = size(vert,1) ; 43 | 44 | %---------------------------------------------- basic checks 45 | if (min(min(tria(:,1:3))) < +1 || ... 46 | max(max(tria(:,1:3))) > nvrt ) 47 | error('idxtri2:invalidInputs', ... 48 | 'Invalid TRIA input array.') ; 49 | end 50 | 51 | %------------------------------ calc. AABB indexing for TRIA 52 | bmin = vert(tria(:,1),:); 53 | bmax = vert(tria(:,1),:); 54 | 55 | for ii = 2 : size(tria,2) 56 | bmin = ... 57 | min(bmin,vert(tria(:,ii), :)); 58 | bmax = ... 59 | max(bmax,vert(tria(:,ii), :)); 60 | end 61 | 62 | isoctave = exist( ... 63 | 'OCTAVE_VERSION','builtin') > +0 ; 64 | 65 | if (isoctave) 66 | 67 | %-- faster for OCTAVE with large tree block size; slower 68 | %-- loop execution... 69 | 70 | opts.nobj = +256 ; 71 | 72 | else 73 | 74 | %-- faster for MATLAB with small tree block size; better 75 | %-- loop execution... 76 | 77 | opts.nobj = + 16 ; 78 | 79 | end 80 | 81 | tree = maketree([bmin,bmax],opts); 82 | 83 | end 84 | 85 | 86 | -------------------------------------------------------------------------------- /mesh2d/inpoly2.m: -------------------------------------------------------------------------------- 1 | function [stat] = inpoly2(varargin) 2 | %INPOLY2 compute "points-in-polygon" queries. 3 | % [STAT] = INPOLY2(VERT,NODE,EDGE) returns the "inside/ou- 4 | % tside" status for a set of vertices VERT and a polygon 5 | % {NODE,EDGE} embedded in a two-dimensional plane. General 6 | % non-convex and multiply-connected polygonal regions can 7 | % be handled. VERT is an N-by-2 array of XY coordinates to 8 | % be tested. STAT is an associated N-by-1 logical array, 9 | % with STAT(II) = TRUE if VERT(II,:) is an interior point. 10 | % The polygonal region is defined as a piecewise-straight- 11 | % line-graph, where NODE is an M-by-2 array of polygon ve- 12 | % rtices and EDGE is a P-by-2 array of edge indexing. Each 13 | % row in EDGE represents an edge of the polygon, such that 14 | % NODE(EDGE(KK,1),:) and NODE(EDGE(KK,2),:) are the coord- 15 | % inates of the endpoints of the KK-TH edge. If the argum- 16 | % ent EDGE is omitted it assumed that the vertices in NODE 17 | % are connected in ascending order. 18 | % 19 | % See also INPOLYGON 20 | 21 | % This algorithm is based on a "crossing-number" test, co- 22 | % unting the number of times a line extending from each 23 | % point past the right-most region of the polygon interse- 24 | % cts with the polygonal boundary. Points with odd counts 25 | % are "inside". A simple implementation requires that each 26 | % edge intersection be checked for each point, leading to 27 | % O(N*M) complexity... 28 | % 29 | % This implementation seeks to improve these bounds: 30 | % 31 | % * Sorting the query points by y-value and determining can- 32 | % didate edge intersection sets via binary-search. Given a 33 | % configuration with N test points, M edges and an average 34 | % point-edge "overlap" of H, the overall complexity scales 35 | % like O(M*H + M*LOG(N) + N*LOG(N)), where O(N*LOG(N)) 36 | % operations are required for sorting, O(M*LOG(N)) operat- 37 | % ions required for the set of binary-searches, and O(M*H) 38 | % operations required for the intersection tests, where H 39 | % is typically small on average, such that H << N. 40 | % 41 | % * Carefully checking points against the bounding-box asso- 42 | % ciated with each polygon edge. This minimises the number 43 | % of calls to the (relatively) expensive edge intersection 44 | % test. 45 | 46 | % Darren Engwirda : 2017 -- 47 | % Email : de2363@columbia.edu 48 | % Last updated : 17/07/2017 49 | 50 | %---------------------------------------------- extract args 51 | node = []; edge = []; vert = []; 52 | 53 | if (nargin>=+1), vert = varargin{1}; end 54 | if (nargin>=+2), node = varargin{2}; end 55 | if (nargin>=+3), edge = varargin{3}; end 56 | 57 | %---------------------------------------------- default args 58 | nnod = size(node,1) ; 59 | nvrt = size(vert,1) ; 60 | 61 | if (isempty(edge)) 62 | edge = [(1:nnod-1)',(2:nnod)'; nnod,1]; 63 | end 64 | 65 | %---------------------------------------------- basic checks 66 | if ( ~isnumeric(node) || ... 67 | ~isnumeric(edge) || ... 68 | ~isnumeric(vert) ) 69 | error('inpoly2:incorrectInputClass' , ... 70 | 'Incorrect input class.') ; 71 | end 72 | 73 | %---------------------------------------------- basic checks 74 | if (ndims(node) ~= +2 || ... 75 | ndims(edge) ~= +2 || ... 76 | ndims(vert) ~= +2 ) 77 | error('inpoly2:incorrectDimensions' , ... 78 | 'Incorrect input dimensions.'); 79 | end 80 | if (size(node,2)~= +2 || ... 81 | size(edge,2)~= +2 || ... 82 | size(vert,2)~= +2 ) 83 | error('inpoly2:incorrectDimensions' , ... 84 | 'Incorrect input dimensions.'); 85 | end 86 | 87 | %---------------------------------------------- basic checks 88 | if (min([edge(:)]) < +1 || ... 89 | max([edge(:)]) > nnod) 90 | error('inpoly2:invalidInputs', ... 91 | 'Invalid EDGE input array.') ; 92 | end 93 | 94 | %-------------- flip to ensure the y-axis is the "long" axis 95 | vmin = min(vert,[],1); 96 | vmax = max(vert,[],1); 97 | ddxy = vmax - vmin ; 98 | 99 | if (ddxy(1) > ddxy(2)) 100 | vert = vert(:,[2,1]) ; 101 | node = node(:,[2,1]) ; 102 | end 103 | 104 | %----------------------------------- sort points via y-value 105 | swap = ... 106 | node(edge(:,2),2) ... 107 | < node(edge(:,1),2) ; 108 | 109 | edge(swap,[1,2]) = ... 110 | edge(swap,[2,1]) ; 111 | 112 | [~,ivec] = ... 113 | sort(vert(:,+2)) ; 114 | vert = vert (ivec,:) ; 115 | 116 | if (exist( ... 117 | 'OCTAVE_VERSION','builtin') > +0) 118 | 119 | if (exist('inpoly2_oct','file') == +3) 120 | 121 | %-- delegate to the compiled version of the code if it's 122 | %-- available 123 | 124 | [stat] = ... 125 | inpoly2_oct(vert,node,edge) ; 126 | 127 | else 128 | 129 | %-- otherwise, just call the native m-code version 130 | 131 | [stat] = ... 132 | inpoly2_loc(vert,node,edge) ; 133 | 134 | end 135 | 136 | else 137 | 138 | %-- MATLAB's JIT is generally smart enough these days to 139 | %-- run this efficiently 140 | 141 | [stat] = ... 142 | inpoly2_loc(vert,node,edge) ; 143 | 144 | end 145 | 146 | stat(ivec) = stat ; 147 | 148 | end 149 | 150 | function [stat] = inpoly2_loc(vert,node,edge) 151 | %INPOLY2_LOC the local m-code version of the crossing-number 152 | %test. Loop over edges; do a binary-search for the first ve- 153 | %rtex that intersects with the edge y-range; do crossing-nu- 154 | %mber comparisons; break when the local y-range is exceeded. 155 | 156 | nvrt = size (vert,1) ; 157 | nnod = size (node,1) ; 158 | nedg = size (edge,1) ; 159 | 160 | stat = false(nvrt,1) ; 161 | 162 | %----------------------------------- loop over polygon edges 163 | for epos = +1 : size(edge,1) 164 | 165 | inod = edge(epos,1) ; 166 | jnod = edge(epos,2) ; 167 | 168 | %------------------------------- calc. edge bounding-box 169 | yone = node(inod,2) ; 170 | ytwo = node(jnod,2) ; 171 | xone = node(inod,1) ; 172 | xtwo = node(jnod,1) ; 173 | 174 | ydel = ytwo - yone; 175 | xdel = xtwo - xone; 176 | 177 | xmin = min(xone,xtwo) ; 178 | 179 | %------------------------------- find VERT(IPOS,2)<=YONE 180 | ilow = +1 ; iupp = nvrt ; 181 | 182 | while (ilow < iupp - 1) 183 | imid = ilow ... 184 | + floor((iupp-ilow) / 2); 185 | 186 | if (vert(imid,2) < yone) 187 | ilow = imid ; 188 | else 189 | iupp = imid ; 190 | end 191 | end 192 | 193 | %------------------------------- calc. edge-intersection 194 | for jpos = ilow+1 : nvrt 195 | 196 | ypos = vert(jpos,2) ; 197 | if (ypos < ytwo) 198 | xpos = vert(jpos,1) ; 199 | if (xpos >= xmin) 200 | if ( ... 201 | ydel* (xpos-xone) < ... 202 | xdel* (ypos-yone) ) 203 | 204 | stat(jpos) = ... 205 | ~stat(jpos) ; 206 | end 207 | else 208 | stat(jpos) = ... 209 | ~stat(jpos) ; 210 | end 211 | else 212 | break ; % done -- due to the sort 213 | end 214 | 215 | end 216 | 217 | end 218 | 219 | end 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /mesh2d/inpoly2_oct.cpp: -------------------------------------------------------------------------------- 1 | 2 | // a fast pre-sorted variant of the crossing-number test for 3 | // INPOLY2.m 4 | 5 | //---------------------------------------------------------- 6 | // Darren Engwirda : 2017 -- 7 | // Email : de2363@columbia.edu 8 | // Last updated : 17/07/2017 9 | //---------------------------------------------------------- 10 | 11 | #include 12 | 13 | DEFUN_DLD (inpoly2_oct, args, , 14 | "-- INPOLY2-OCT: low-level routine called by INPOLY2 to \n" 15 | "-- compute point-in-polygon queries.") 16 | { 17 | octave_value_list rval; 18 | 19 | int const nargin = args.length () ; 20 | if (nargin != +3) 21 | { 22 | print_usage () ; 23 | return rval ; 24 | } 25 | 26 | Matrix const vert ( 27 | args(0).matrix_value ()) ; 28 | Matrix const node ( 29 | args(1).matrix_value ()) ; 30 | Matrix const edge ( 31 | args(2).matrix_value ()) ; 32 | 33 | if (error_state) return rval ; 34 | 35 | octave_idx_type const nvrt 36 | = vert.rows () ; 37 | octave_idx_type const nnod 38 | = node.rows () ; 39 | octave_idx_type const nedg 40 | = edge.rows () ; 41 | 42 | //---------------------------------- init. crossing no. bool 43 | boolMatrix stat(nvrt, 1) ; 44 | 45 | octave_idx_type vpos ; 46 | for (vpos = +0; vpos != nvrt; ++vpos) 47 | { 48 | stat(vpos) = false ; 49 | } 50 | 51 | //---------------------------------- loop over polygon edges 52 | octave_idx_type epos ; 53 | for (epos = +0; epos != nedg; ++epos) 54 | { 55 | octave_idx_type const inod 56 | = edge(epos,0) - 1 ; 57 | octave_idx_type const jnod 58 | = edge(epos,1) - 1 ; 59 | 60 | //------------------------------ calc. edge bounding-box 61 | double yone = node (inod,1) ; 62 | double ytwo = node (jnod,1) ; 63 | double xone = node (inod,0) ; 64 | double xtwo = node (jnod,0) ; 65 | 66 | double ydel = ytwo - yone ; 67 | double xdel = xtwo - xone ; 68 | 69 | double xmin = xone < xtwo 70 | ? xone : xtwo ; 71 | 72 | //------------------------------ find VERT(IPOS,2)<=YONE 73 | octave_idx_type ilow = +0 ; 74 | octave_idx_type iupp = nvrt-1; 75 | octave_idx_type imid = +0 ; 76 | 77 | while (ilow < iupp - 1) 78 | { 79 | imid = ilow 80 | + (iupp - ilow) / 2; 81 | 82 | if (vert(imid,1) < yone) 83 | { 84 | ilow = imid ; 85 | } 86 | else 87 | { 88 | iupp = imid ; 89 | } 90 | } 91 | 92 | //------------------------------ calc. edge-intersection 93 | octave_idx_type vpos = ilow+1 ; 94 | for ( ; vpos != nvrt; ++vpos) 95 | { 96 | double xpos = vert(vpos,0); 97 | double ypos = vert(vpos,1); 98 | 99 | if (ypos < ytwo) 100 | { 101 | if (xpos >= xmin) 102 | { 103 | if ( 104 | ydel* (xpos-xone) < 105 | xdel* (ypos-yone) ) 106 | { 107 | stat(vpos) = 108 | ! stat(vpos) ; 109 | } 110 | } 111 | else 112 | { 113 | stat(vpos) = 114 | ! stat(vpos) ; 115 | } 116 | } 117 | else 118 | { 119 | break ; // done -- due to the sort 120 | } 121 | } 122 | 123 | } 124 | 125 | rval(0) = stat; 126 | 127 | return rval; 128 | } 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /mesh2d/isfeat2.m: -------------------------------------------------------------------------------- 1 | function [is,bv] = isfeat2(pp,ee,tt) 2 | %ISFEAT2 return "feature" status for the triangles in a two- 3 | %dimensional constrained triangulation. 4 | % [STAT] = ISFEAT2(VERT,EDGE,TRIA) returns STAT(II) = TRUE 5 | % for any triangle including a sufficiently "sharp" angle 6 | % located at the apex of any two constrained edges. Sharp 7 | % features have angles of greater-than ACOS(+0.8) degrees. 8 | 9 | % Darren Engwirda : 2017 -- 10 | % Email : de2363@columbia.edu 11 | % Last updated : 27/01/2017 12 | 13 | %---------------------------------------------- basic checks 14 | if (~isnumeric(pp) || ~isnumeric(ee) || ... 15 | ~isnumeric(tt) ) 16 | error('isfeat2:incorrectInputClass' , ... 17 | 'Incorrect input class.') ; 18 | end 19 | 20 | %---------------------------------------------- basic checks 21 | if (ndims(pp) ~= +2 || ndims(ee) ~= +2 || ... 22 | ndims(tt) ~= +2 ) 23 | error('isfeat2:incorrectDimensions' , ... 24 | 'Incorrect input dimensions.'); 25 | end 26 | if (size(pp,2)~= +2 || size(ee,2) < +5 || ... 27 | size(tt,2) < +6 ) 28 | error('isfeat2:incorrectDimensions' , ... 29 | 'Incorrect input dimensions.'); 30 | end 31 | 32 | nnod = size(pp,1) ; nedg = size(ee,1) ; 33 | ntri = size(tt,1) ; 34 | 35 | %---------------------------------------------- basic checks 36 | if (min(min(tt(:,1:3))) < +1 || ... 37 | max(max(tt(:,1:3))) > nnod) 38 | error('isfeat2:invalidInputs', ... 39 | 'Invalid TRIA input array.') ; 40 | end 41 | if (min(min(tt(:,4:6))) < +1 || ... 42 | max(max(tt(:,4:6))) > nedg) 43 | error('isfeat2:invalidInputs', ... 44 | 'Invalid TRIA input array.') ; 45 | end 46 | 47 | if (min(min(ee(:,1:2))) < +1 || ... 48 | max(max(ee(:,1:2))) > nnod) 49 | error('isfeat2:invalidInputs', ... 50 | 'Invalid EDGE input array.') ; 51 | end 52 | if (min(min(ee(:,3:4))) < +0 || ... 53 | max(max(ee(:,3:4))) > ntri) 54 | error('isfeat2:invalidInputs', ... 55 | 'Invalid EDGE input array.') ; 56 | end 57 | 58 | %----------------------------------------- compute "feature" 59 | is = false(size(tt,1),1); 60 | bv = false(size(tt,1),3); 61 | 62 | EI = [3, 1, 2] ; 63 | EJ = [1, 2, 3] ; 64 | NI = [3, 1, 2] ; 65 | NJ = [1, 2, 3] ; 66 | NK = [2, 3, 1] ; 67 | 68 | for ii = +1 : +3 69 | 70 | %------------------------------------- common edge index 71 | ei = tt( :,EI(ii)+3); 72 | ej = tt( :,EJ(ii)+3); 73 | 74 | %------------------------------------- is boundary edge? 75 | bi = ee(ei,5) >= +1 ; 76 | bj = ee(ej,5) >= +1 ; 77 | 78 | ok = bi & bj ; 79 | 80 | if (~any(ok)), continue; end 81 | 82 | ni = tt(ok,NI(ii)+0); 83 | nj = tt(ok,NJ(ii)+0); 84 | nk = tt(ok,NK(ii)+0); 85 | 86 | %------------------------------------- adj. edge vectors 87 | vi = pp(ni,:)-pp(nj,:) ; 88 | vj = pp(nk,:)-pp(nj,:) ; 89 | 90 | %------------------------------------- adj. edge lengths 91 | li = sqrt(sum(vi.^2,2)); 92 | lj = sqrt(sum(vj.^2,2)); 93 | 94 | ll = li .* lj ; 95 | 96 | %------------------------------------- adj. dot-product! 97 | aa = sum(vi.*vj,2)./ll ; 98 | 99 | bv(ok,ii) = aa >= +.80 ; 100 | 101 | is(ok) = ... 102 | is(ok) | bv(ok,ii) ; 103 | 104 | end 105 | 106 | end 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /mesh2d/lfshfn2.m: -------------------------------------------------------------------------------- 1 | function [vert,tria,hlfs] = lfshfn2(varargin) 2 | %LFSHFN2 calc. a discrete "local-feature-size" estimate for 3 | %a polygonal domain embedded in R^2. 4 | % [VERT,TRIA,HFUN] = LFSHFN2(NODE,EDGE) returns the trian- 5 | % gulated "feature-size" estimate for the polygonal region 6 | % {NODE,EDGE}. NODE is an N-by-2 array of polygonal verti- 7 | % ces and EDGE is an E-by-2 array of edge indexing. Each 8 | % row in EDGE represents an edge of the polygon, such that 9 | % NODE(EDGE(JJ,1),:) and NODE(EDGE(JJ,2),:) are the coord- 10 | % inates of the endpoints of the JJ-TH edge. If the argum- 11 | % ent EDGE is omitted it assumed that the vertices in NODE 12 | % are connected in ascending order. 13 | % 14 | % [...] = LFSHFN2(NODE,EDGE,PART) computes a size-estimate 15 | % for a multiply-connected geometry. PART is a cell-array 16 | % of polygonal "parts", where each element PART{KK} is an 17 | % array of edge indices defining a given polygonal region. 18 | % EDGE(PART{KK}, :) is the set of edges in the KK-TH part. 19 | % 20 | % VERT is a V-by-2 array of XY coordinates, TRIA is a T-by 21 | % -3 array of triangles and HFUN is a V-by-1 array of mesh 22 | % -size values. Each row of TRIA defines a triangle, such 23 | % that VERT(TRIA(II,1),:), VERT(TRIA(II,2),:) and VERT( 24 | % TRIA(II,3),:) are the coordinates of the II-TH triangle. 25 | % 26 | % See also TRIHFN2, LIMHFN2, IDXTRI2 27 | 28 | % Darren Engwirda : 2017 -- 29 | % Email : de2363@columbia.edu 30 | % Last updated : 24/01/2017 31 | 32 | %---------------------------------------------- extract args 33 | node = []; PSLG = []; part = {}; opts = [] ; 34 | 35 | if (nargin>=+1), node = varargin{1}; end 36 | if (nargin>=+2), PSLG = varargin{2}; end 37 | if (nargin>=+3), part = varargin{3}; end 38 | if (nargin>=+4), opts = varargin{4}; end 39 | 40 | %------------------------------ build coarse background grid 41 | [opts] = makeopt(opts); 42 | 43 | [vert,conn,tria,tnum] = ... 44 | refine2(node,PSLG,part,opts) ; 45 | 46 | %------------------------------ estimate local-feature-size! 47 | hlfs = ... 48 | +inf * ones(size(vert,1),1) ; 49 | 50 | %------------------------------ calc. LFS based on edge-len. 51 | evec = vert(conn(:,2),:) - ... 52 | vert(conn(:,1),:) ; 53 | elen = sqrt(sum(evec.^2,2)) ; 54 | hlen = elen * +1. ; 55 | 56 | for epos = +1 : size(conn,+1) 57 | 58 | ivrt = conn(epos,1) ; 59 | jvrt = conn(epos,2) ; 60 | 61 | hlfs(ivrt) = min( ... 62 | hlfs(ivrt),hlen(epos)) ; 63 | hlfs(jvrt) = min( ... 64 | hlfs(jvrt),hlen(epos)) ; 65 | 66 | end 67 | 68 | %------------------------------ push gradient limits on HFUN 69 | DHDX = opts.dhdx; 70 | 71 | hlfs = ... 72 | limhfn2(vert,tria,hlfs,DHDX) ; 73 | 74 | end 75 | 76 | function [opts] = makeopt(opts) 77 | %MAKEOPT setup the options structure for LFSHFN2. 78 | 79 | if (~isfield(opts,'kind')) 80 | opts.kind = 'delaunay'; 81 | else 82 | if (~strcmpi(opts.kind,'delfront') && ... 83 | ~strcmpi(opts.kind,'delaunay') ) 84 | error( ... 85 | 'lfshfn2:invalidOption','Invalid refinement KIND.'); 86 | end 87 | end 88 | 89 | if (~isfield(opts,'rho2')) 90 | opts.rho2 = sqrt(+2.) ; 91 | else 92 | if (~isnumeric(opts.rho2)) 93 | error('lfshfn2:incorrectInputClass', ... 94 | 'Incorrect input class.'); 95 | end 96 | if (numel(opts.rho2)~= +1) 97 | error('lfshfn2:incorrectDimensions', ... 98 | 'Incorrect input dimensions.') ; 99 | end 100 | if (opts.rho2 < +1.) 101 | error('lfshfn2:invalidOptionValues', ... 102 | 'Invalid OPT.RHO2 selection.') ; 103 | end 104 | end 105 | 106 | if (~isfield(opts,'dhdx')) 107 | opts.dhdx = +0.2500 ; 108 | else 109 | if (~isnumeric(opts.dhdx)) 110 | error('lfshfn2:incorrectInputClass', ... 111 | 'Incorrect input class.'); 112 | end 113 | if (numel(opts.dhdx)~= +1) 114 | error('lfshfn2:incorrectDimensions', ... 115 | 'Incorrect input dimensions.') ; 116 | end 117 | if (opts.dhdx <= 0.) 118 | error('lfshfn2:invalidOptionValues', ... 119 | 'Invalid OPT.DHDX selection.') ; 120 | end 121 | end 122 | 123 | end 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /mesh2d/limgrad.m: -------------------------------------------------------------------------------- 1 | function [ffun,flag] = limgrad(edge,elen,ffun,dfdx,imax) 2 | %LIMGRAD impose "gradient-limits" on a function defined over 3 | %an undirected graph. 4 | % [FNEW] = LIMGRAD(EDGE,ELEN,FFUN,DFDX,ITER) computes a 5 | % "gradient-limited" function FNEW on the undirected graph 6 | % {EDGE,ELEN}, where EDGE is an NE-by-2 array of edge ind- 7 | % ices, and ELEN is an NE-by-1 array of edge lengths. 8 | % Gradients are limited over the graph edges, such that 9 | % 10 | % ABS(FNEW(N2)-FNEW(N1)) <= ELEN(II) * DFDX, 11 | % 12 | % where N1=EDGE(II,1) and N2=EDGE(II,2) are the two nodes 13 | % in the II-TH edge. An iterative algorithm is used, swee- 14 | % ping over an "active-set" of graph edges until converge- 15 | % nce is achieved. A maximum of IMAX iterations are done. 16 | % 17 | % [FNEW,FLAG] = LIMGRAD(...) also returns a boolean FLAG, 18 | % with FLAG=TRUE denoting convergence. 19 | % 20 | % See also LIMHFN2 21 | 22 | % Darren Engwirda : 2017 -- 23 | % Email : de2363@columbia.edu 24 | % Last updated : 18/04/2017 25 | 26 | %---------------------------------------------- basic checks 27 | if ( ~isnumeric(edge) || ... 28 | ~isnumeric(elen) || ... 29 | ~isnumeric(ffun) || ... 30 | ~isnumeric(dfdx) || ... 31 | ~isnumeric(imax) ) 32 | error('limgrad:incorrectInputClass' , ... 33 | 'Incorrect input class.') ; 34 | end 35 | %---------------------------------------------- basic checks 36 | if (ndims(edge) ~= +2 || ... 37 | ndims(elen) > +2 || ... 38 | ndims(ffun) > +2 || ... 39 | numel(dfdx) ~= +1 || ... 40 | numel(imax) ~= +1 ) 41 | error('limgrad:incorrectDimensions' , ... 42 | 'Incorrect input dimensions.'); 43 | end 44 | if (size(edge,2) < +2 || ... 45 | size(elen,2)~= +1 || ... 46 | size(ffun,2)~= +1 || ... 47 | size(edge,1)~= size(elen,1) ) 48 | error('limgrad:incorrectDimensions' , ... 49 | 'Incorrect input dimensions.'); 50 | end 51 | 52 | nnod = size(ffun,1) ; 53 | 54 | %---------------------------------------------- basic checks 55 | if (dfdx < +0. || imax < +0) 56 | error('limgrad:invalidInputArgument', ... 57 | 'Invalid input parameter.'); 58 | end 59 | if (min(min(edge(:,1:2))) < +1 || ... 60 | max(max(edge(:,1:2))) > nnod ) 61 | error('limgrad:invalidInputArgument', ... 62 | 'Invalid EDGE input array.') ; 63 | end 64 | 65 | %-- IVEC(NPTR(II,1):NPTR(II,2)) are edges adj. to II-TH node 66 | nvec = [edge(:,1); edge(:,2)]; 67 | ivec = [(1:size(edge,1))'; ... 68 | (1:size(edge,1))'] ; 69 | 70 | [nvec,pidx] = sort (nvec) ; 71 | ivec = ivec (pidx) ; 72 | 73 | mark = false(nnod,1) ; 74 | mark(edge(:,1)) = true ; 75 | mark(edge(:,2)) = true ; 76 | 77 | idxx = find(diff(nvec) > +0) ; 78 | 79 | nptr = zeros(nnod,2) ; 80 | nptr(:,2) = -1 ; 81 | nptr(mark,1) = [+1; idxx+1]; 82 | nptr(mark,2) = [idxx; nnod]; 83 | 84 | %----------------------------- ASET=ITER if node is "active" 85 | aset = zeros(size(ffun,1),1) ; 86 | 87 | %----------------------------- exhaustive 'til all satisfied 88 | ftol = min(ffun) * sqrt(eps) ; 89 | 90 | for iter = +1 : imax 91 | 92 | %------------------------- find "active" nodes this pass 93 | aidx = find(aset == iter - 1) ; 94 | 95 | if (isempty(aidx)), break; end 96 | 97 | %------------------------- reorder => better convergence 98 | [aval,idxx] = sort(ffun(aidx)) ; 99 | 100 | aidx = aidx(idxx); 101 | 102 | %------------------------- visit adj. edges and set DFDX 103 | for ipos = 1 : length(aidx) 104 | npos = aidx(ipos) ; 105 | for jpos = nptr(npos,1) ... 106 | : nptr(npos,2) 107 | 108 | epos = ivec(jpos,1) ; 109 | 110 | nod1 = edge(epos,1) ; 111 | nod2 = edge(epos,2) ; 112 | 113 | %----------------- calc. limits about min.-value 114 | if (ffun(nod1) > ffun(nod2)) 115 | 116 | fun1 = ffun(nod2) ... 117 | + elen(epos) * dfdx ; 118 | 119 | if (ffun(nod1) > fun1+ftol) 120 | ffun(nod1) = fun1; 121 | aset(nod1) = iter; 122 | end 123 | 124 | else 125 | 126 | fun2 = ffun(nod1) ... 127 | + elen(epos) * dfdx ; 128 | 129 | if (ffun(nod2) > fun2+ftol) 130 | ffun(nod2) = fun2; 131 | aset(nod2) = iter; 132 | end 133 | 134 | end 135 | 136 | end 137 | end 138 | 139 | end 140 | 141 | flag = (iter < imax) ; 142 | 143 | end 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /mesh2d/limhfn2.m: -------------------------------------------------------------------------------- 1 | function [hfun] = limhfn2(vert,tria,hfun,dhdx) 2 | %LIMHFN2 impose gradient limits on a discrete mesh-size fun- 3 | %ction defined over a 2-simplex triangulation. 4 | % [HFUN] = LIMHFN2(VERT,TRIA,HFUN,DHDX) returns a "gradie- 5 | % nt-limited" function HFUN, defined over a triangulation 6 | % {VERT,TRIA}. HFUN is a T-by-1 vector of function values, 7 | % VERT is a V-by-2 array of XY coordinates and TRIA is a 8 | % T-by-3 array of triangles. Each row of TRIA 9 | % defines a triangle, such that VERT(TRIA(II,1),:), VERT( 10 | % TRIA(II,2),:) and VERT(TRIA(II,3),:) are the coordinates 11 | % of the II-TH triangle. DHDX is a scalar gradient-limit. 12 | % HFUN is "limited" to control variation over the elements 13 | % in the triangulation, such that (HFUN(V2)-HFUN(V1))/LL<= 14 | % DHDX, where {V1,V2} are the vertices of a given triangle 15 | % edge and LL is the edge-length. Limits are enforced exh- 16 | % austively over all edges. 17 | % 18 | % See also TRIHFN2, LFSHFN2 19 | 20 | % This function is based on a very simplified version of: 21 | % Persson, P.O. "Mesh size functions for implicit geometr- 22 | % ies and PDE-based gradient limiting." Engineering with 23 | % Computers 22 (2006): 95-109. 24 | 25 | % Darren Engwirda : 2017 -- 26 | % Email : de2363@columbia.edu 27 | % Last updated : 18/04/2017 28 | 29 | %---------------------------------------------- basic checks 30 | if ( ~isnumeric(vert) || ... 31 | ~isnumeric(tria) || ... 32 | ~isnumeric(hfun) || ... 33 | ~isnumeric(dhdx) ) 34 | error('limhfn2:incorrectInputClass' , ... 35 | 'Incorrect input class.') ; 36 | end 37 | 38 | %---------------------------------------------- basic checks 39 | if (ndims(vert) ~= +2 || ... 40 | ndims(tria) ~= +2 || ... 41 | ndims(hfun) ~= +2 || ... 42 | numel(dhdx) ~= +1 ) 43 | error('limhfn2:incorrectDimensions' , ... 44 | 'Incorrect input dimensions.'); 45 | end 46 | if (size(vert,2)~= +2 || ... 47 | size(tria,2) < +3 || ... 48 | size(hfun,2)~= +1 || ... 49 | size(vert,1)~= size(hfun,1) ) 50 | error('limhfn2:incorrectDimensions' , ... 51 | 'Incorrect input dimensions.'); 52 | end 53 | 54 | nvrt = size(vert,1) ; 55 | 56 | %---------------------------------------------- basic checks 57 | if (min(min(tria(:,1:3))) < +1 || ... 58 | max(max(tria(:,1:3))) > nvrt ) 59 | error('limhfn2:invalidInputArgument', ... 60 | 'Invalid TRIA input array.') ; 61 | end 62 | 63 | %-------------------- impose gradient limits over mesh edges 64 | [edge,tria] = tricon2(tria); 65 | 66 | evec = vert(edge(:,2),:) - ... 67 | vert(edge(:,1),:) ; 68 | elen = sqrt(sum(evec.^2,2)) ; 69 | 70 | %-------------------- impose gradient limits over edge-graph 71 | [hfun] = limgrad( ... 72 | edge,elen,hfun,dhdx,sqrt(nvrt)) ; 73 | 74 | end 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /mesh2d/minlen2.m: -------------------------------------------------------------------------------- 1 | function [ll,ei] = minlen2(pp,tt) 2 | %MINLEN2 return the minimum length edge for each triangle in 3 | %a two-dimensional triangulation. 4 | % [ELEN,IMIN] = MINLEN2(VERT,TRIA) returns the minimum le- 5 | % ngth ELEN and local edge index IMIN for all triangles in 6 | % the triangulation {VERT,TRIA}. 7 | 8 | % Darren Engwirda : 2017 -- 9 | % Email : de2363@columbia.edu 10 | % Last updated : 16/01/2017 11 | 12 | %---------------------------------------------- basic checks 13 | if (~isnumeric(pp) || ~isnumeric(tt) ) 14 | error('minlen2:incorrectInputClass' , ... 15 | 'Incorrect input class.') ; 16 | end 17 | 18 | %---------------------------------------------- basic checks 19 | if (ndims(pp) ~= +2 || ndims(tt) ~= +2 ) 20 | error('minlen2:incorrectDimensions' , ... 21 | 'Incorrect input dimensions.'); 22 | end 23 | if (size(pp,2)~= +2 || size(tt,2) < +3 ) 24 | error('minlen2:incorrectDimensions' , ... 25 | 'Incorrect input dimensions.'); 26 | end 27 | 28 | nnod = size(pp,1) ; 29 | 30 | %---------------------------------------------- basic checks 31 | if (min(min(tt(:,1:3))) < +1 || ... 32 | max(max(tt(:,1:3))) > nnod ) 33 | error('minlen2:invalidInputs', ... 34 | 'Invalid TRIA input array.') ; 35 | end 36 | 37 | %------------------------------------------ compute edge-len 38 | l1 = sum((pp(tt(:,2),:) ... 39 | -pp(tt(:,1),:)).^2,2); 40 | l2 = sum((pp(tt(:,3),:) ... 41 | -pp(tt(:,2),:)).^2,2); 42 | l3 = sum((pp(tt(:,1),:) ... 43 | -pp(tt(:,3),:)).^2,2); 44 | 45 | %------------------------------------------ compute min.-len 46 | [ll,ei] = min([l1,l2,l3],[],+2); 47 | 48 | end 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /mesh2d/poly-data/lake-1-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metorm/Unstructured-Level-Set-Method/f55d6ffc7223fc4c6a75aadd083c8525e7642309/mesh2d/poly-data/lake-1-small.png -------------------------------------------------------------------------------- /mesh2d/poly-data/lake-2-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metorm/Unstructured-Level-Set-Method/f55d6ffc7223fc4c6a75aadd083c8525e7642309/mesh2d/poly-data/lake-2-small.png -------------------------------------------------------------------------------- /mesh2d/poly-data/wavy-channel.msh: -------------------------------------------------------------------------------- 1 | # channel.msh file, created by JIGSAW 2 | # https://github.com/dengwirda/jigsaw-matlab 3 | mshid=1 4 | ndims=2 5 | point=103 6 | 0;0;0 7 | 5;0;0 8 | 5;1.1;0 9 | 4.95;1.096858316112863;0 10 | 4.9;1.087630668004386;0 11 | 4.85;1.072896862742141;0 12 | 4.8;1.0535826794979;0 13 | 4.75;1.030901699437495;0 14 | 4.7;1.006279051952931;0 15 | 4.65;0.9812618685414277;0 16 | 4.6;0.9574220708434924;0 17 | 4.55;0.9362576010251309;0 18 | 4.5;0.9190983005625052;0 19 | 4.45;0.9070223514111748;0 20 | 4.4;0.9007885298685523;0 21 | 4.35;0.9007885298685523;0 22 | 4.3;0.9070223514111749;0 23 | 4.25;0.9190983005625053;0 24 | 4.2;0.936257601025131;0 25 | 4.15;0.9574220708434925;0 26 | 4.1;0.9812618685414279;0 27 | 4.05;1.006279051952932;0 28 | 4;1.030901699437495;0 29 | 3.95;1.0535826794979;0 30 | 3.9;1.072896862742141;0 31 | 3.85;1.087630668004387;0 32 | 3.8;1.096858316112863;0 33 | 3.75;1.1;0 34 | 3.7;1.096858316112863;0 35 | 3.65;1.087630668004386;0 36 | 3.6;1.072896862742141;0 37 | 3.55;1.0535826794979;0 38 | 3.5;1.030901699437495;0 39 | 3.45;1.006279051952931;0 40 | 3.4;0.9812618685414274;0 41 | 3.35;0.9574220708434924;0 42 | 3.3;0.9362576010251309;0 43 | 3.25;0.9190983005625052;0 44 | 3.2;0.9070223514111748;0 45 | 3.15;0.9007885298685522;0 46 | 3.1;0.9007885298685523;0 47 | 3.05;0.9070223514111749;0 48 | 3;0.9190983005625053;0 49 | 2.95;0.9362576010251312;0 50 | 2.9;0.9574220708434928;0 51 | 2.85;0.9812618685414275;0 52 | 2.8;1.006279051952931;0 53 | 2.75;1.030901699437495;0 54 | 2.7;1.0535826794979;0 55 | 2.65;1.072896862742141;0 56 | 2.6;1.087630668004386;0 57 | 2.55;1.096858316112863;0 58 | 2.5;1.1;0 59 | 2.45;1.096858316112863;0 60 | 2.4;1.087630668004386;0 61 | 2.35;1.072896862742141;0 62 | 2.3;1.0535826794979;0 63 | 2.25;1.030901699437495;0 64 | 2.2;1.006279051952931;0 65 | 2.15;0.9812618685414274;0 66 | 2.1;0.9574220708434927;0 67 | 2.05;0.9362576010251311;0 68 | 2;0.9190983005625052;0 69 | 1.95;0.9070223514111749;0 70 | 1.9;0.9007885298685522;0 71 | 1.85;0.9007885298685522;0 72 | 1.8;0.9070223514111748;0 73 | 1.75;0.9190983005625053;0 74 | 1.7;0.9362576010251309;0 75 | 1.65;0.9574220708434926;0 76 | 1.6;0.9812618685414275;0 77 | 1.55;1.006279051952931;0 78 | 1.5;1.030901699437495;0 79 | 1.45;1.0535826794979;0 80 | 1.4;1.072896862742141;0 81 | 1.35;1.087630668004386;0 82 | 1.3;1.096858316112863;0 83 | 1.25;1.1;0 84 | 1.2;1.096858316112863;0 85 | 1.15;1.087630668004386;0 86 | 1.1;1.072896862742141;0 87 | 1.05;1.0535826794979;0 88 | 1;1.030901699437495;0 89 | 0.9500000000000001;1.006279051952931;0 90 | 0.9;0.9812618685414275;0 91 | 0.8500000000000001;0.9574220708434927;0 92 | 0.8;0.936257601025131;0 93 | 0.75;0.9190983005625053;0 94 | 0.7000000000000001;0.9070223514111748;0 95 | 0.65;0.9007885298685522;0 96 | 0.6000000000000001;0.9007885298685522;0 97 | 0.55;0.9070223514111748;0 98 | 0.5;0.9190983005625053;0 99 | 0.45;0.936257601025131;0 100 | 0.4;0.9574220708434927;0 101 | 0.35;0.9812618685414275;0 102 | 0.3;1.006279051952931;0 103 | 0.25;1.030901699437495;0 104 | 0.2;1.0535826794979;0 105 | 0.15;1.072896862742141;0 106 | 0.1;1.087630668004386;0 107 | 0.05;1.096858316112863;0 108 | 0;1.1;0 109 | edge2=103 110 | 0;1;0 111 | 1;2;0 112 | 2;3;0 113 | 3;4;0 114 | 4;5;0 115 | 5;6;0 116 | 6;7;0 117 | 7;8;0 118 | 8;9;0 119 | 9;10;0 120 | 10;11;0 121 | 11;12;0 122 | 12;13;0 123 | 13;14;0 124 | 14;15;0 125 | 15;16;0 126 | 16;17;0 127 | 17;18;0 128 | 18;19;0 129 | 19;20;0 130 | 20;21;0 131 | 21;22;0 132 | 22;23;0 133 | 23;24;0 134 | 24;25;0 135 | 25;26;0 136 | 26;27;0 137 | 27;28;0 138 | 28;29;0 139 | 29;30;0 140 | 30;31;0 141 | 31;32;0 142 | 32;33;0 143 | 33;34;0 144 | 34;35;0 145 | 35;36;0 146 | 36;37;0 147 | 37;38;0 148 | 38;39;0 149 | 39;40;0 150 | 40;41;0 151 | 41;42;0 152 | 42;43;0 153 | 43;44;0 154 | 44;45;0 155 | 45;46;0 156 | 46;47;0 157 | 47;48;0 158 | 48;49;0 159 | 49;50;0 160 | 50;51;0 161 | 51;52;0 162 | 52;53;0 163 | 53;54;0 164 | 54;55;0 165 | 55;56;0 166 | 56;57;0 167 | 57;58;0 168 | 58;59;0 169 | 59;60;0 170 | 60;61;0 171 | 61;62;0 172 | 62;63;0 173 | 63;64;0 174 | 64;65;0 175 | 65;66;0 176 | 66;67;0 177 | 67;68;0 178 | 68;69;0 179 | 69;70;0 180 | 70;71;0 181 | 71;72;0 182 | 72;73;0 183 | 73;74;0 184 | 74;75;0 185 | 75;76;0 186 | 76;77;0 187 | 77;78;0 188 | 78;79;0 189 | 79;80;0 190 | 80;81;0 191 | 81;82;0 192 | 82;83;0 193 | 83;84;0 194 | 84;85;0 195 | 85;86;0 196 | 86;87;0 197 | 87;88;0 198 | 88;89;0 199 | 89;90;0 200 | 90;91;0 201 | 91;92;0 202 | 92;93;0 203 | 93;94;0 204 | 94;95;0 205 | 95;96;0 206 | 96;97;0 207 | 97;98;0 208 | 98;99;0 209 | 99;100;0 210 | 100;101;0 211 | 101;102;0 212 | 102;0;0 213 | -------------------------------------------------------------------------------- /mesh2d/relhfn2.m: -------------------------------------------------------------------------------- 1 | function [hrel] = relhfn2(pp,tt,hv) 2 | %RELHFN2 calc. relative edge-length for a 2-simplex triangu- 3 | %lation embedded in the two-dimensional plane. 4 | % [HREL] = RELHFN2(VERT,TRIA) returns the rel. edge-len., 5 | % indicating conformance to the imposed mesh-spacing cons- 6 | % traints, where HREL is a E-by-1 vector, VERT is a V-by-2 7 | % array of XY coordinates, and TRIA is a T-by-3 array of 8 | % vertex indexing, where each row defines a triangle, such 9 | % that VERT(TRIA(II,1),:), VERT(TRIA(II,2),:) and VERT( 10 | % TRIA(II,3),:) are the coordinates of the II-TH triangle. 11 | % 12 | % See also TRISCR2, TRIAREA, TRIANG2, TRIBAL2 13 | 14 | % Darren Engwirda : 2017 -- 15 | % Email : de2363@columbia.edu 16 | % Last updated : 11/07/2017 17 | 18 | %---------------------------------------------- basic checks 19 | if (~isnumeric(pp) || ~isnumeric(tt) || ... 20 | ~isnumeric(hv) ) 21 | error('relhfn2:incorrectInputClass' , ... 22 | 'Incorrect input class.') ; 23 | end 24 | 25 | %---------------------------------------------- basic checks 26 | if (ndims(pp) ~= +2 || ndims(tt) ~= +2 ) 27 | error('relhfn2:incorrectDimensions' , ... 28 | 'Incorrect input dimensions.'); 29 | end 30 | if (size(pp,2)~= +2 || size(tt,2) < +3 ) 31 | error('relhfn2:incorrectDimensions' , ... 32 | 'Incorrect input dimensions.'); 33 | end 34 | if (size(hv,2)~= +1 || size(hv,1) ~= size(pp,1) ) 35 | error('relhfn2:incorrectDimensions' , ... 36 | 'Incorrect input dimensions.'); 37 | end 38 | 39 | nnod = size(pp,1) ; 40 | 41 | %---------------------------------------------- basic checks 42 | if (min(min(tt(:,1:3))) < +1 || ... 43 | max(max(tt(:,1:3))) > nnod ) 44 | error('relhfn2:invalidInputs', ... 45 | 'Invalid TRIA input array.') ; 46 | end 47 | 48 | %----------------------------------- compute rel. mesh-sizes 49 | [ee,tt] = tricon2(tt); 50 | 51 | evec = pp(ee(:,2),:)-pp(ee(:,1),:) ; 52 | 53 | elen = sqrt(sum(evec.^2,+2)); 54 | 55 | hmid = hv(ee(:,2),:)+hv(ee(:,1),:) ; 56 | hmid = hmid * +0.50 ; 57 | hrel = elen ./ hmid ; 58 | 59 | end 60 | 61 | 62 | -------------------------------------------------------------------------------- /mesh2d/setset2.m: -------------------------------------------------------------------------------- 1 | function [same,sloc] = setset2(iset,jset) 2 | %SETSET2 a (much) faster variant of ISMEMBER for edge lists. 3 | % [IN] = SETSET2(ISET,JSET) returns an I-by-1 array IN, 4 | % with IN(K) = TRUE if ISET(K,:) is present in JSET. This 5 | % routine is essentially an optimised ISMEMBER variant de- 6 | % signed for processing lists of edge indexing. ISET is an 7 | % I-by-2 array of "query" edges, JSET is a J-by-2 array of 8 | % edges to test against. 9 | % 10 | % See also ISMEMBER 11 | 12 | % Darren Engwirda : 2017 -- 13 | % Email : de2363@columbia.edu 14 | % Last updated : 29/01/2017 15 | 16 | %---------------------------------------------- basic checks 17 | if ( ~isnumeric(iset) || ~isnumeric(jset) ) 18 | error('setset2:incorrectInputClass' , ... 19 | 'Incorrect input class.') ; 20 | end 21 | 22 | %---------------------------------------------- basic checks 23 | if (ndims(iset) ~= +2 || ndims(jset) ~= +2) 24 | error('setset2:incorrectDimensions' , ... 25 | 'Incorrect input dimensions.'); 26 | end 27 | if (size(iset,2)~= +2 || size(jset,2)~= +2) 28 | error('setset2:incorrectDimensions' , ... 29 | 'Incorrect input dimensions.'); 30 | end 31 | 32 | %---------------------------------------------- set v1 <= v2 33 | iset = sort(iset,2) ; 34 | jset = sort(jset,2) ; 35 | 36 | %-- this is the slow, but easy-to-undertsand version of what 37 | %-- is happening here... 38 | 39 | % if (nargout == +1) 40 | % same = ismember(iset,jset,'rows') ; 41 | % else 42 | % [same,sloc] = ... 43 | % ismember(iset,jset,'rows') ; 44 | 45 | %-- as above, the 'ROWS' based call to ISMEMBER can be sped 46 | %-- up by casting the edge lists (i.e. pairs of UINT32 valu- 47 | %-- es) to DOUBLE, and performing the sorted queries on vec- 48 | %-- tor inputs! 49 | if (nargout == +1) 50 | same = ismember( ... 51 | iset*[2^31;1], jset*[2^31;1]) ; 52 | else 53 | [same,sloc] = ismember( ... 54 | iset*[2^31;1], jset*[2^31;1]) ; 55 | end 56 | 57 | end 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /mesh2d/triang2.m: -------------------------------------------------------------------------------- 1 | function [dcos] = triang2(pp,tt) 2 | %TRIANG2 calc. enclosed angles for a 2-simplex triangulation 3 | %embedded in the two-dimensional plane. 4 | % [ADEG] = TRIANG2(VERT,TRIA) returns the enclosed angles 5 | % associated with each triangle, where ADEG is a T-by-3 6 | % array of the angles subtended at each vertex, VERT is a 7 | % V-by-2 array of XY coordinates, and TRIA is a T-by-3 ar- 8 | % ray of vertex indexing, where each row defines a triang- 9 | % le, such that VERT(TRIA(II,1),:), VERT(TRIA(II,2),:) and 10 | % VERT(TRIA(II,3),:) are the coordinates of the II-TH tri- 11 | % angle. Angles are returned in degrees. 12 | % 13 | % See also TRISCR2, TRIAREA, TRIBAL2 14 | 15 | % Darren Engwirda : 2017 -- 16 | % Email : de2363@columbia.edu 17 | % Last updated : 17/01/2017 18 | 19 | %---------------------------------------------- basic checks 20 | if (~isnumeric(pp) || ~isnumeric(tt) ) 21 | error('triang2:incorrectInputClass' , ... 22 | 'Incorrect input class.') ; 23 | end 24 | 25 | %---------------------------------------------- basic checks 26 | if (ndims(pp) ~= +2 || ndims(tt) ~= +2 ) 27 | error('triang2:incorrectDimensions' , ... 28 | 'Incorrect input dimensions.'); 29 | end 30 | if (size(pp,2)~= +2 || size(tt,2) < +3 ) 31 | error('triang2:incorrectDimensions' , ... 32 | 'Incorrect input dimensions.'); 33 | end 34 | 35 | nnod = size(pp,1) ; 36 | 37 | %---------------------------------------------- basic checks 38 | if (min(min(tt(:,1:3))) < +1 || ... 39 | max(max(tt(:,1:3))) > nnod ) 40 | error('triang2:invalidInputs', ... 41 | 'Invalid TRIA input array.') ; 42 | end 43 | 44 | %----------------------------------- compute enclosed angles 45 | dcos = zeros(size(tt,1),3) ; 46 | 47 | ev12 = pp(tt(:,2),:)-pp(tt(:,1),:); 48 | ev23 = pp(tt(:,3),:)-pp(tt(:,2),:); 49 | ev31 = pp(tt(:,1),:)-pp(tt(:,3),:); 50 | 51 | lv11 = sqrt(sum(ev12.^2,2)); 52 | lv22 = sqrt(sum(ev23.^2,2)); 53 | lv33 = sqrt(sum(ev31.^2,2)); 54 | 55 | ev12 = ev12 ./ ... 56 | lv11(:,ones(1,size(pp,2))); 57 | ev23 = ev23 ./ ... 58 | lv22(:,ones(1,size(pp,2))); 59 | ev31 = ev31 ./ ... 60 | lv33(:,ones(1,size(pp,2))); 61 | 62 | dcos(:,1) = sum(-ev12.*ev23,2); 63 | dcos(:,2) = sum(-ev23.*ev31,2); 64 | dcos(:,3) = sum(-ev31.*ev12,2); 65 | 66 | dcos = acos(dcos) * 180. / pi ; 67 | 68 | end 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /mesh2d/triarea.m: -------------------------------------------------------------------------------- 1 | function [area] = triarea(pp,tt) 2 | %TRIAREA calc. triangle areas for a 2-simplex triangulation 3 | %embedded in the two-dimensional plane. 4 | % [AREA] = TRIAREA(VERT,TRIA) returns the signed triangle 5 | % areas, where AREA is a T-by-1 vector, VERT is a V-by-2 6 | % array of XY coordinates, and TRIA is a T-by-3 array of 7 | % vertex indexing, where each row defines a triangle, such 8 | % that VERT(TRIA(II,1),:), VERT(TRIA(II,2),:) and VERT( 9 | % TRIA(II,3),:) are the coordinates of the II-TH triangle. 10 | % 11 | % See also TRISCR2, TRIANG2, TRIBAL2 12 | 13 | % Darren Engwirda : 2017 -- 14 | % Email : engwirda@mit.edu 15 | % Last updated : 17/01/2017 16 | 17 | %---------------------------------------------- basic checks 18 | if (~isnumeric(pp) || ~isnumeric(tt) ) 19 | error('triarea:incorrectInputClass' , ... 20 | 'Incorrect input class.') ; 21 | end 22 | 23 | %---------------------------------------------- basic checks 24 | if (ndims(pp) ~= +2 || ndims(tt) ~= +2 ) 25 | error('triarea:incorrectDimensions' , ... 26 | 'Incorrect input dimensions.'); 27 | end 28 | if (size(pp,2)~= +2 || size(tt,2) < +3 ) 29 | error('triarea:incorrectDimensions' , ... 30 | 'Incorrect input dimensions.'); 31 | end 32 | 33 | nnod = size(pp,1) ; 34 | 35 | %---------------------------------------------- basic checks 36 | if (min(min(tt(:,1:3))) < +1 || ... 37 | max(max(tt(:,1:3))) > nnod ) 38 | error('triarea:invalidInputs', ... 39 | 'Invalid TRIA input array.') ; 40 | end 41 | 42 | %--------------------------------------- compute signed area 43 | ev12 = pp(tt(:,2),:)-pp(tt(:,1),:) ; 44 | ev13 = pp(tt(:,3),:)-pp(tt(:,1),:) ; 45 | 46 | switch (size(pp,2)) 47 | case +2 48 | 49 | area = ev12(:,1).*ev13(:,2) ... 50 | - ev12(:,2).*ev13(:,1) ; 51 | area = 0.5 * area; 52 | 53 | case +3 54 | 55 | avec = cross(ev12,ev13); 56 | area = sqrt(sum(avec.^2,2)) ; 57 | area = 0.5 * area; 58 | 59 | otherwise 60 | error('Unsupported dimension.') ; 61 | end 62 | 63 | end 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /mesh2d/tribal2.m: -------------------------------------------------------------------------------- 1 | function [bb] = tribal2(pp,tt) 2 | %TRIBAL2 compute the circumballs associated with a 2-simplex 3 | %triangulation embedded in R^2. 4 | % [BB] = TRIBAL2(PP,TT) returns the circumscribing balls 5 | % associated with the triangles in [PP,TT], such that BB = 6 | % [XC,YC,RC.^2]. 7 | 8 | % Darren Engwirda : 2017 -- 9 | % Email : engwirda@mit.edu 10 | % Last updated : 29/04/2017 11 | 12 | %---------------------------------------------- basic checks 13 | if (~isnumeric(pp) || ... 14 | ~isnumeric(tt) ) 15 | error('tribal2:incorrectInputClass' , ... 16 | 'Incorrect input class.') ; 17 | end 18 | %---------------------------------------------- basic checks 19 | if (ndims(pp) ~= +2 || ndims(tt) ~= +2) 20 | error('tribal2:incorrectDimensions' , ... 21 | 'Incorrect input dimensions.'); 22 | end 23 | if (size(pp,2)~= +2 || size(tt,2) < +3) 24 | error('tribal2:incorrectDimensions' , ... 25 | 'Incorrect input dimensions.'); 26 | end 27 | 28 | %------------------------------------------------ lhs matrix 29 | ab = pp(tt(:,2),:)-pp(tt(:,1),:) ; 30 | ac = pp(tt(:,3),:)-pp(tt(:,1),:) ; 31 | 32 | %------------------------------------------------ rhs vector 33 | rv11 = sum(ab.*ab,2) ; 34 | rv22 = sum(ac.*ac,2) ; 35 | 36 | %------------------------------------------------ solve sys. 37 | dd = ab(:,1) .* ac(:,2) - ... 38 | ab(:,2) .* ac(:,1) ; 39 | 40 | bb = zeros(size(tt,1),3) ; 41 | bb(:,1) = (ac(:,2) .* rv11 - ... 42 | ab(:,2) .* rv22 ) ... 43 | ./ dd * +.5 ; 44 | 45 | bb(:,2) = (ab(:,1) .* rv22 - ... 46 | ac(:,1) .* rv11 ) ... 47 | ./ dd * +.5 ; 48 | 49 | bb(:,3) = sum(bb(:,1:2).^2,2) ; 50 | bb(:,1:2) = ... 51 | pp(tt(:,1),:) + bb(:,1:2) ; 52 | 53 | end 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /mesh2d/tricon2.m: -------------------------------------------------------------------------------- 1 | function [ee,tt] = tricon2(varargin) 2 | %TRICON2 edge-centred connectivity for a conforming 2-simpl- 3 | %ex triangulation in the two-dimensional plane. 4 | % [EE,TT] = TRICON2(TT,CC) returns the edge-based adjacen- 5 | % cy for a mesh of 2-simlexes (triangles). EE = [V1,V2,T1, 6 | % T2,CE] is the set of unique 1-simplexes (edges) in the 7 | % mesh TT. Each row of {V1,V2} defines an edge, each row 8 | % of {T1,T2} defines the two triangles adjacent to an edge 9 | % and CE is a "constraint" flag, indicating which row in 10 | % CC (if any) the edge matches. TT = [V1,V2,V3,E1,E2,E3], 11 | % is the set of unique 2-simplexes in the mesh, where 12 | % {E1,E2,E3} define the tria-to-edge mapping. Each row of 13 | % {E1,E2,E3} are the indicies of the three edges that make 14 | % up each triangle. 15 | 16 | % Darren Engwirda : 2014 -- 17 | % Email : de2363@columbia.edu 18 | % Last updated : 01/10/2017 19 | 20 | %---------------------------------------------- extract args 21 | tt = []; cc = []; 22 | 23 | if (nargin>=1), tt = varargin{1}; end 24 | if (nargin>=2), cc = varargin{2}; end 25 | 26 | %---------------------------------------------- basic checks 27 | if (~isnumeric(tt)) 28 | error('tricon2:incorrectInputClass' , ... 29 | 'Incorrect input class.') ; 30 | end 31 | if (~isnumeric(cc)) 32 | error('tricon2:incorrectInputClass' , ... 33 | 'Incorrect input class.') ; 34 | end 35 | 36 | %---------------------------------------------- basic checks 37 | if (ndims(tt) ~= +2 || size(tt,2) ~= +3) 38 | error('tricon2:incorrectDimensions' , ... 39 | 'Incorrect input dimensions.'); 40 | end 41 | if (min(tt(:)) < +1 ) 42 | error('tricon2:invalidInputs', ... 43 | 'Invalid TRIA input array.') ; 44 | end 45 | 46 | if (~isempty(cc)) 47 | if (ndims(cc) ~= +2 || size(cc,2) ~= +2) 48 | error('tricon2:incorrectDimensions' , ... 49 | 'Incorrect input dimensions.'); 50 | end 51 | end 52 | 53 | isoctave = ... 54 | exist('OCTAVE_VERSION','builtin') > +0; 55 | 56 | nt = size(tt,1); 57 | nc = size(cc,1); 58 | 59 | %------------------------------ assemble non-unique edge set 60 | ee = zeros(nt*3,2); 61 | ee((1:nt)+nt*0,:) = tt(:,[1,2]); 62 | ee((1:nt)+nt*1,:) = tt(:,[2,3]); 63 | ee((1:nt)+nt*2,:) = tt(:,[3,1]); 64 | 65 | %------------------------------ unique edges and re-indexing 66 | %[ee, iv, jv] = ... 67 | % unique(sort(ee, 2), 'rows'); 68 | 69 | %-- as a (much) faster alternative to the 'ROWS' based call 70 | %-- to UNIQUE above, the edge list (i.e. pairs of UINT32 va- 71 | %-- lues) can be cast to DOUBLE, and the sorted comparisons 72 | %-- performed on vector inputs! 73 | ee = sort(ee,2); 74 | [ed,iv,jv] = unique(ee*[2^31;1]); 75 | ee = ee (iv,:); 76 | 77 | %------------------- tria-to-edge indexing: 3 edges per tria 78 | tt = [tt, zeros(nt*1,3)] ; 79 | tt(:,4) = jv((1:nt)+nt*0); 80 | tt(:,5) = jv((1:nt)+nt*1); 81 | tt(:,6) = jv((1:nt)+nt*2); 82 | 83 | %------------------- edge-to-tria indexing: 2 trias per edge 84 | 85 | if (isoctave) 86 | 87 | %-- OCTAVE is *shockingly* bad at executing loops, so -- 88 | %-- even though it involves far more operations! -- call 89 | %-- the vectorised version below. 90 | 91 | ne = size(ee,1); 92 | ee = [ee, zeros(ne*1,3)] ; 93 | 94 | ei = [tt(:,4);tt(:,5);tt(:,6)] ; 95 | ti = [(+1:nt),(+1:nt),(+1:nt)]'; 96 | 97 | [ei,ix] = sort(ei,'ascend') ; 98 | ti = ti (ix,:); 99 | 100 | ix = find(diff(ei)>=+1); 101 | 102 | ni = length(ti); 103 | 104 | ep = [+1; ix+1]; 105 | ep = [ep; ni+1]; 106 | 107 | in = ep(2:ne+1)-ep(1:ne+0) > 1 ; 108 | 109 | ee( :,3) = ti(ep(1:ne)+0); 110 | ee(in,4) = ti(ep( in)+1); 111 | 112 | else 113 | 114 | %-- MATLAB is actually pretty good at JIT-ing code these 115 | %-- days, so use the asymptotically faster version based 116 | %-- on the pre-computed ordering. 117 | 118 | ne = size(ee,1); 119 | ee = [ee, zeros(ne*1,3)] ; 120 | ep = +3 * ones (ne*1,1) ; 121 | for ti = +1 : nt 122 | ei = tt(ti,4) ; 123 | ee(ei,ep(ei)) = ti; 124 | ej = tt(ti,5) ; 125 | ee(ej,ep(ej)) = ti; 126 | ek = tt(ti,6) ; 127 | ee(ek,ep(ek)) = ti; 128 | 129 | ep(ei) = ep(ei)+1 ; 130 | ep(ej) = ep(ej)+1 ; 131 | ep(ek) = ep(ek)+1 ; 132 | end 133 | 134 | end 135 | 136 | if (isempty(cc)), return; end 137 | 138 | %------------------------------------ find constrained edges 139 | %[ip,ip] = ismember( ... 140 | % ee(:,1:2),sort(cc,2),'rows'); 141 | 142 | %-- as above, the 'ROWS' based call to ISMEMBER can be sped 143 | %-- up by casting the edge lists (i.e. pairs of UINT32 valu- 144 | %-- es) to DOUBLE, and performing the sorted queries on vec- 145 | %-- tor inputs! 146 | cc = sort(cc,2); 147 | [ip,ip] = ismember(ed, cc*[2^31;+1]); 148 | 149 | %------------------------------------ mark constrained edges 150 | ee(:,5) = ip; 151 | 152 | end 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /mesh2d/trideg2.m: -------------------------------------------------------------------------------- 1 | function [vdeg] = trideg2(pp,tt) 2 | %TRIDEG2 calc. topological degree for vertices in a 2-simpl- 3 | %ex triangulation. 4 | % [VDEG] = TRIDEG2(VERT,TRIA) returns the no. of triangles 5 | % incident to each vertex. VDEG is a V-by-1 array of vert- 6 | % ex degrees, VERT is a V-by-2 array of XY coordinates, 7 | % and TRIA is a T-by-3 array of vertex indexing, where 8 | % each row defines a triangle, 9 | % such that VERT(TRIA(II,1),:), VERT(TRIA(II,2),:) and 10 | % VERT(TRIA(II,3),:) are the coordinates of the II-TH tri- 11 | % angle. 12 | % 13 | % See also TRISCR2, TRIAREA, TRIANG2, TRIBAL2 14 | 15 | %----------------------------------------------------------- 16 | % Darren Engwirda : 2017 -- 17 | % Email : de2363@columbia.edu 18 | % Last updated : 09/06/2017 19 | %----------------------------------------------------------- 20 | 21 | %---------------------------------------------- basic checks 22 | if (~isnumeric(pp) || ~isnumeric(tt) ) 23 | error('trideg2:incorrectInputClass' , ... 24 | 'Incorrect input class.') ; 25 | end 26 | 27 | %---------------------------------------------- basic checks 28 | if (ndims(pp) ~= +2 || ndims(tt) ~= +2 ) 29 | error('trideg2:incorrectDimensions' , ... 30 | 'Incorrect input dimensions.'); 31 | end 32 | if (size(pp,2)~= +2 || size(tt,2) < +3 ) 33 | error('trideg2:incorrectDimensions' , ... 34 | 'Incorrect input dimensions.'); 35 | end 36 | 37 | nvrt = size(pp,1) ; 38 | ntri = size(tt,1) ; 39 | 40 | %---------------------------------------------- basic checks 41 | if (min(min(tt(:,1:3))) < +1 || ... 42 | max(max(tt(:,1:3))) > nvrt ) 43 | error('trideg2:invalidInputs', ... 44 | 'Invalid TRIA input array.') ; 45 | end 46 | 47 | %------------------------------------- compute vertex degree 48 | tvec = (+1 : ntri)'; 49 | 50 | sdeg = sparse(tt(:,1:3),[tvec,tvec,tvec],+1,nvrt,ntri) ; 51 | 52 | vdeg = ... 53 | sdeg * ones(ntri,1); 54 | 55 | end 56 | 57 | 58 | -------------------------------------------------------------------------------- /mesh2d/tridiv2.m: -------------------------------------------------------------------------------- 1 | function [vert,conn,tria,tnum] = tridiv2(varargin) 2 | %TRIDIV2 "quadtree" refinement for 2-simplex triangulations. 3 | % [VERT,EDGE,TRIA,TNUM] = TRIDIV2(VERT,EDGE,TRIA,TNUM) re- 4 | % turns a globally refined triangulation, in which all ed- 5 | % ges are bisected about their midpoints. Such refinement 6 | % splits each triangle into four new sub-triangles accord- 7 | % ing to a shape-preserving scheme. 8 | % 9 | % VERT is a V-by-2 array of XY coordinates in the triangu- 10 | % lation, EDGE is an array of constrained edges, TRIA is a 11 | % T-by-3 array of triangles, and TNUM is a T-by-1 array of 12 | % part indices. Each row of TRIA and EDGE define an eleme- 13 | % nt. VERT(TRIA(II,1),:), VERT(TRIA(II,2),:) and VERT(TRIA 14 | % (II,3),:) are the coordinates of the II-TH triangle. The 15 | % edges in EDGE are defined in a similar manner. NUM is an 16 | % array of part indexing, such that TNUM(II) is the index 17 | % of the part in which the II-TH triangle resides. 18 | % 19 | % [VERT,EDGE,TRIA,TNUM] = TRIDIV2(... ,TDIV) returns a se- 20 | % lectively refined mesh, where TDIV is a T-by-1 logical 21 | % array, with TDIV(KK) = TRUE if TRIA(KK,:) is to be refi- 22 | % ned. Such triangles are refined using the four-way split 23 | % described above. Additionally, a "halo" of adjacent tri- 24 | % angles are also refined to preseve compatibility of the 25 | % mesh. Such triangles are refined using a two-way bisect- 26 | % ion type scheme. 27 | 28 | % See also REFINE2, SMOOTH2 29 | 30 | % Darren Engwirda : 2017 -- 31 | % Email : de2363@columbia.edu 32 | % Last updated : 29/01/2017 33 | 34 | %---------------------------------------------- extract args 35 | vert = []; conn = []; tria = []; tnum = [] ; 36 | tdiv = []; 37 | 38 | if (nargin>=+1), vert = varargin{1}; end 39 | if (nargin>=+2), conn = varargin{2}; end 40 | if (nargin>=+3), tria = varargin{3}; end 41 | if (nargin>=+4), tnum = varargin{4}; end 42 | if (nargin>=+5), tdiv = varargin{5}; end 43 | 44 | %---------------------------------------------- default arg. 45 | if (isempty(tnum)) 46 | tnum = ones(size(tria,1),1) ; 47 | end 48 | if (isempty(tdiv)) 49 | tdiv = true(size(tria,1),1) ; 50 | end 51 | 52 | %---------------------------------------------- basic checks 53 | if ( ~isnumeric(vert) || ... 54 | ~isnumeric(conn) || ... 55 | ~isnumeric(tria) || ... 56 | ~isnumeric(tnum) || ... 57 | ~islogical(tdiv) ) 58 | error('tridiv2:incorrectInputClass' , ... 59 | 'Incorrect input class.') ; 60 | end 61 | 62 | %---------------------------------------------- basic checks 63 | tnum = tnum(:) ; tdiv = tdiv(:) ; 64 | if (ndims(vert) ~= +2 || ... 65 | ndims(conn) ~= +2 || ... 66 | ndims(tria) ~= +2 || ... 67 | ndims(tnum) ~= +2 || ... 68 | ndims(tdiv) ~= +2 ) 69 | error('tridiv2:incorrectDimensions' , ... 70 | 'Incorrect input dimensions.'); 71 | end 72 | if (size(vert,2)~= +2 || ... 73 | size(conn,2)~= +2 || ... 74 | size(tria,2)~= +3 || ... 75 | size(tnum,2)~= +1 || ... 76 | size(tria,1)~= size(tnum,1) ) 77 | error('tridiv2:incorrectDimensions' , ... 78 | 'Incorrect input dimensions.'); 79 | end 80 | 81 | nvrt = size(vert,1) ; 82 | 83 | %---------------------------------------------- basic checks 84 | if (min(min(conn(:,1:2))) < +1 || ... 85 | max(max(conn(:,1:2))) > nvrt ) 86 | error('tridiv2:invalidInputs', ... 87 | 'Invalid EDGE input array.') ; 88 | end 89 | 90 | if (min(min(tria(:,1:3))) < +1 || ... 91 | max(max(tria(:,1:3))) > nvrt ) 92 | error('tridiv2:invalidInputs', ... 93 | 'Invalid TRIA input array.') ; 94 | end 95 | 96 | %------------------------------ assemble extended adj. info. 97 | [edge,tria] = tricon2(tria,conn) ; 98 | 99 | ediv = false(size(edge,1),1) ; 100 | ediv(tria(tdiv,4:6)) = true ; 101 | 102 | snum = length(find(ediv)); 103 | 104 | while (true) 105 | 106 | %-------------------------- tria's with >= 2 edge splits 107 | div3 = sum(double( ... 108 | ediv(tria(:,4:6))),2)>=2; 109 | 110 | %-------------------------- expand onto adj. edge splits 111 | ediv(tria(div3,4:6)) = true ; 112 | 113 | snew = length(find(ediv)) ; 114 | 115 | if (snew == snum), break; end 116 | 117 | snum = snew ; 118 | 119 | end 120 | 121 | %------------------------------ tria's with == 1 edge splits 122 | div1 = sum( ... 123 | double( ediv(tria(:,4:6))),2)==1; 124 | 125 | %------------------------------ indexing for mid-point vert. 126 | ivec = zeros(size(edge,1),1); 127 | ivec(ediv) = ... 128 | (1:snum)' + size(vert,1); 129 | 130 | %------------------------------------------ update vert. set 131 | emid = vert(edge(ediv,1),:) ... 132 | + vert(edge(ediv,2),:) ; 133 | vert =[vert ; emid * 0.5] ; 134 | 135 | %------------------------------------------ update edge. set 136 | [cvec,eloc] = ... 137 | setset2(conn,edge(ediv, 1:2)) ; 138 | 139 | epos = ivec(ediv); 140 | epos = epos(eloc(eloc>0)) ; 141 | 142 | conn = [conn(~cvec,1:2) ; ... 143 | conn(cvec,1) , epos ; ... 144 | conn(cvec,2) , epos ] ; 145 | 146 | %------------------------------------ push 1-to-4 refinement 147 | tr41 = ones(length(find(div3)),3) ; 148 | tn41 = ones(length(find(div3)),1) ; 149 | tn41(:,1) = tnum(div3,1); 150 | tr41(:,1) = tria(div3,1); 151 | tr41(:,2) = ivec(tria(div3,4)); 152 | tr41(:,3) = ivec(tria(div3,6)); 153 | 154 | tr42 = ones(length(find(div3)),3) ; 155 | tn42 = ones(length(find(div3)),1) ; 156 | tn42(:,1) = tnum(div3,1); 157 | tr42(:,1) = ivec(tria(div3,4)); 158 | tr42(:,2) = tria(div3,2); 159 | tr42(:,3) = ivec(tria(div3,5)); 160 | 161 | tr43 = ones(length(find(div3)),3) ; 162 | tn43 = ones(length(find(div3)),1) ; 163 | tn43(:,1) = tnum(div3,1); 164 | tr43(:,1) = ivec(tria(div3,6)); 165 | tr43(:,2) = ivec(tria(div3,5)); 166 | tr43(:,3) = tria(div3,3); 167 | 168 | tr44 = ones(length(find(div3)),3) ; 169 | tn44 = ones(length(find(div3)),1) ; 170 | tn44(:,1) = tnum(div3,1); 171 | tr44(:,1) = ivec(tria(div3,6)); 172 | tr44(:,2) = ivec(tria(div3,4)); 173 | tr44(:,3) = ivec(tria(div3,5)); 174 | 175 | %----------------------- push 1-to-2 refinement about edge 1 176 | tvec = false(size(tria,1), 1) ; 177 | tvec(ediv(tria(:,4))&div1) = true ; 178 | 179 | tr21 = ones(length(find(tvec)),3) ; 180 | tn21 = ones(length(find(tvec)),1) ; 181 | tn21(:,1) = tnum(tvec,1); 182 | tr21(:,1) = ivec(tria(tvec,4)); 183 | tr21(:,2) = tria(tvec,3); 184 | tr21(:,3) = tria(tvec,1); 185 | 186 | tr22 = ones(length(find(tvec)),3) ; 187 | tn22 = ones(length(find(tvec)),1) ; 188 | tn22(:,1) = tnum(tvec,1); 189 | tr22(:,1) = ivec(tria(tvec,4)); 190 | tr22(:,2) = tria(tvec,2); 191 | tr22(:,3) = tria(tvec,3); 192 | 193 | %----------------------- push 1-to-2 refinement about edge 2 194 | tvec = false(size(tria,1), 1) ; 195 | tvec(ediv(tria(:,5))&div1) = true ; 196 | 197 | tr23 = ones(length(find(tvec)),3) ; 198 | tn23 = ones(length(find(tvec)),1) ; 199 | tn23(:,1) = tnum(tvec,1); 200 | tr23(:,1) = ivec(tria(tvec,5)); 201 | tr23(:,2) = tria(tvec,1); 202 | tr23(:,3) = tria(tvec,2); 203 | 204 | tr24 = ones(length(find(tvec)),3) ; 205 | tn24 = ones(length(find(tvec)),1) ; 206 | tn24(:,1) = tnum(tvec,1); 207 | tr24(:,1) = ivec(tria(tvec,5)); 208 | tr24(:,2) = tria(tvec,3); 209 | tr24(:,3) = tria(tvec,1); 210 | 211 | %----------------------- push 1-to-2 refinement about edge 3 212 | tvec = false(size(tria,1), 1) ; 213 | tvec(ediv(tria(:,6))&div1) = true ; 214 | 215 | tr25 = ones(length(find(tvec)),3) ; 216 | tn25 = ones(length(find(tvec)),1) ; 217 | tn25(:,1) = tnum(tvec,1); 218 | tr25(:,1) = ivec(tria(tvec,6)); 219 | tr25(:,2) = tria(tvec,2); 220 | tr25(:,3) = tria(tvec,3); 221 | 222 | tr26 = ones(length(find(tvec)),3) ; 223 | tn26 = ones(length(find(tvec)),1) ; 224 | tn26(:,1) = tnum(tvec,1); 225 | tr26(:,1) = ivec(tria(tvec,6)); 226 | tr26(:,2) = tria(tvec,1); 227 | tr26(:,3) = tria(tvec,2); 228 | 229 | %------------------------------------------ update tria. set 230 | tria = [tria(~div1&~div3,1:3) ; ... 231 | tr41 ; tr42 ; tr43 ; tr44 ; ... 232 | tr21 ; tr22 ; 233 | tr23 ; tr24 ; 234 | tr25 ; tr26 ] ; 235 | 236 | tnum = [tnum(~div1&~div3,1:1) ; ... 237 | tn41 ; tn42 ; tn43 ; tn44 ; ... 238 | tn21 ; tn22 ; 239 | tn23 ; tn24 ; 240 | tn25 ; tn26 ] ; 241 | 242 | end 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /mesh2d/trihfn2.m: -------------------------------------------------------------------------------- 1 | function [hval] = trihfn2(test,vert,tria,tree,hfun) 2 | %TRIHFN2 evaluate a discrete mesh-size function defined on a 3 | %2-simplex triangulation embedded in R^2. 4 | % [HVAL] = TRIHFN2(TEST,VERT,TRIA,TREE,HFUN) returns an 5 | % interpolation of the discrete mesh-size function HFUN to 6 | % the queries points TEST. HVAL is a Q-by-1 array of func- 7 | % tion values associated with the Q-by-2 array of XY coor- 8 | % dinates TEST. {VERT,TRIA,HFUN} is a discrete mesh-size 9 | % function, where VERT is a V-by-2 array of XY coordinates 10 | % TRIA is a T-by-3 array of triangles and HFUN is a V-by-1 11 | % array of mesh-size values. Each row of TRIA defines a 12 | % triangle, such that VERT(TRIA(II,1),:), 13 | % VERT(TRIA(II,2),:) and VERT(TRIA(II,3),:) are the coord- 14 | % inates of the II-TH triangle. TREE is a spatial indexing 15 | % structure for {VERT,TRIA}, as returned by IDXTRI2. 16 | % 17 | % See also LFSHFN2, LIMHFN2, IDXTRI2 18 | 19 | % Darren Engwirda : 2017 -- 20 | % Email : de2363@columbia.edu 21 | % Last updated : 07/04/2017 22 | 23 | %---------------------------------------------- basic checks 24 | if ( ~isnumeric(test) || ... 25 | ~isnumeric(vert) || ... 26 | ~isnumeric(tria) || ... 27 | ~isnumeric(hfun) || ... 28 | ~isstruct (tree) ) 29 | error('trihfn2:incorrectInputClass' , ... 30 | 'Incorrect input class.') ; 31 | end 32 | 33 | %---------------------------------------------- basic checks 34 | if (ndims(test) ~= +2 || ... 35 | ndims(vert) ~= +2 || ... 36 | ndims(tria) ~= +2 || ... 37 | ndims(hfun) ~= +2 ) 38 | error('trihfn2:incorrectDimensions' , ... 39 | 'Incorrect input dimensions.'); 40 | end 41 | if (size(test,2)~= +2 || ... 42 | size(vert,2)~= +2 || ... 43 | size(tria,2) < +3 || ... 44 | size(hfun,2)~= +1 || ... 45 | size(vert,1)~= size(hfun,1) ) 46 | error('trihfn2:incorrectDimensions' , ... 47 | 'Incorrect input dimensions.'); 48 | end 49 | 50 | nvrt = size(vert,1) ; 51 | 52 | %---------------------------------------------- basic checks 53 | if (min(min(tria(:,1:3))) < +1 || ... 54 | max(max(tria(:,1:3))) > nvrt ) 55 | error('trihfn2:invalidInputs', ... 56 | 'Invalid TRIA input array.') ; 57 | end 58 | 59 | %-------------------------------------- test-to-tria queries 60 | [tp,tj] = ... 61 | findtria (vert,tria,test,tree); 62 | 63 | if (isempty(tp)) 64 | in = false(size(test,1),1); 65 | ti = []; 66 | else 67 | in = tp(:,1) > +0 ; 68 | ti = tj(tp(in,+1)); 69 | end 70 | 71 | %-------------------------------------- calc. linear interp. 72 | hval = max(hfun) * ones(size(test,1),1) ; 73 | 74 | if (any(in)) 75 | 76 | d1 = test(in,:) - vert(tria(ti,1),:); 77 | d2 = test(in,:) - vert(tria(ti,2),:); 78 | d3 = test(in,:) - vert(tria(ti,3),:); 79 | 80 | a3 = abs(d1(:,1) .* d2(:,2) ... 81 | - d1(:,2) .* d2(:,1) ) ; 82 | a2 = abs(d1(:,1) .* d3(:,2) ... 83 | - d1(:,2) .* d3(:,1) ) ; 84 | a1 = abs(d3(:,1) .* d2(:,2) ... 85 | - d3(:,2) .* d2(:,1) ) ; 86 | 87 | hval(in) = a1.*hfun(tria(ti,1)) ... 88 | + a2.*hfun(tria(ti,2)) ... 89 | + a3.*hfun(tria(ti,3)) ; 90 | 91 | hval(in) = hval(in)./(a1+a2+a3) ; 92 | 93 | end 94 | 95 | end 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /mesh2d/triread.m: -------------------------------------------------------------------------------- 1 | function [vert,edge,tria,tnum] = triread(name) 2 | %TRIREAD read two-dimensional triangulation data from file. 3 | % [VERT,EDGE,TRIA,TNUM] = TRIREAD(NAME) returns the 2-sim- 4 | % plex triangulation {VERT,TRIA} contained in the mesh-fi- 5 | % le NAME. 6 | % 7 | % VERT is a V-by-2 array of XY coordinates in the triangu- 8 | % lation, EDGE is an array of constrained edges, TRIA is a 9 | % T-by-3 array of triangles, and TNUM is a T-by-1 array of 10 | % part indices. Each row of TRIA and EDGE define an eleme- 11 | % nt. VERT(TRIA(II,1),:), VERT(TRIA(II,2),:) and VERT(TRIA 12 | % (II,3),:) are the coordinates of the II-TH triangle. The 13 | % edges in EDGE are defined in a similar manner. NUM is an 14 | % array of part indexing, such that TNUM(II) is the index 15 | % of the part in which the II-TH triangle resides. 16 | % 17 | % Data is returned non-empty if it is present in the file. 18 | % 19 | % See also TRISAVE 20 | 21 | % This routine borrows functionality from the JIGSAW pack- 22 | % age: github.com/dengwirda/jigsaw-matlab. 23 | 24 | % Darren Engwirda : 2017 -- 25 | % Email : de2363@columbia.edu 26 | % Last updated : 01/10/2017 27 | 28 | filename = mfilename('fullpath'); 29 | filepath = fileparts( filename ); 30 | 31 | addpath([filepath,'/mesh-file']); 32 | 33 | vert = []; edge = []; tria = []; tnum = []; 34 | 35 | %---------------------------------------------- basic checks 36 | if (~ischar(name)) 37 | error('triread:incorrectInputClass' , ... 38 | 'Incorrect input class.') ; 39 | end 40 | 41 | %----------------------------------- borrow JIGSAW I/O func! 42 | [mesh] = loadmsh(name) ; 43 | 44 | %----------------------------------- extract data if present 45 | if (isfield(mesh,'point') && ... 46 | isfield(mesh.point,'coord')) 47 | vert = mesh.point.coord(:,1:2) ; 48 | end 49 | if (isfield(mesh,'edge2') && ... 50 | isfield(mesh.edge2,'index')) 51 | edge = mesh.edge2.index(:,1:2) ; 52 | end 53 | if (isfield(mesh,'tria3') && ... 54 | isfield(mesh.tria3,'index')) 55 | tria = mesh.tria3.index(:,1:3) ; 56 | tnum = mesh.tria3.index(:, 4) ; 57 | end 58 | 59 | end 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /mesh2d/triscr2.m: -------------------------------------------------------------------------------- 1 | function [tscr] = triscr2(pp,tt) 2 | %TRISCR2 calc. area-len. ratios for triangles in a 2-simplex 3 | %triangulation in the two-dimensional plane. 4 | % [SCR2] = TRISCR2(VERT,TRIA) returns the area-len. ratios 5 | % where SCR2 is a T-by-1 vector, VERT is a V-by-2 array of 6 | % XY coordinates, and TRIA is a T-by-3 array of 7 | % vertex indexing, where each row defines a triangle, such 8 | % that VERT(TRIA(II,1),:), VERT(TRIA(II,2),:) and VERT( 9 | % TRIA(II,3),:) are the coordinates of the II-TH triangle. 10 | % 11 | % See also TRIAREA, TRIANG2, TRIBAL2 12 | 13 | % Darren Engwirda : 2017 -- 14 | % Email : de2363@columbia.edu 15 | % Last updated : 17/01/2017 16 | 17 | %--------------------------- compute signed area-len. ratios 18 | scal = 4.0 * sqrt(3.0) / 3.0; 19 | 20 | area = triarea(pp,tt) ; % also error checks! 21 | 22 | lrms = sum((pp(tt(:,2),:) ... 23 | - pp(tt(:,1),:)).^2,2) ... 24 | + sum((pp(tt(:,3),:) ... 25 | - pp(tt(:,2),:)).^2,2) ... 26 | + sum((pp(tt(:,3),:) ... 27 | - pp(tt(:,1),:)).^2,2) ; 28 | 29 | lrms =(lrms / 3.0) .^ 1.00; 30 | 31 | tscr = scal * area ./ lrms; 32 | 33 | end 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/GenerateBasicData.m: -------------------------------------------------------------------------------- 1 | clc; 2 | clear; 3 | addpath('./../mesh2d/') 4 | config; 5 | %% Rectangle 6 | 7 | [p,~,t,~]=refine2([0,0; 1,0; 1,1; 0,1;],[], [], [], scale); 8 | 9 | [C,NC,CMid,NCMid,NeighbourCells]=buildConnection(t); 10 | 11 | phi=fdNotchedCircle(p); 12 | 13 | save(['notchedCircle_' num2str(scale) '.mat'],'C','NC','CMid','NCMid','NeighbourCells','t','p','phi'); 14 | 15 | %% 2 circles in a block 16 | phi=fd2Circles(p); 17 | 18 | save(['3Circles_' num2str(scale) '.mat'],'C','NC','CMid','NCMid','NeighbourCells','t','p','phi'); 19 | 20 | %% U 21 | 22 | vertices=[0,0; 1,0; 1,1; 0.6,1; 0.6,0.6; 0.4,0.6; 0.4,1; 0,1]; 23 | edges=[1, 2; 2, 3; 3, 4; 4, 5; 5, 6; 6, 7; 7, 8; 8, 1]; 24 | [p,~,t,~]=refine2(vertices, edges, [], [], scale); 25 | 26 | [C,NC,CMid,NCMid,NeighbourCells]=buildConnection(t); 27 | 28 | phi=fdUpperBlock(p); 29 | 30 | save(['U_Region_' num2str(scale) '.mat'],'C','NC','CMid','NCMid','NeighbourCells','t','p','phi'); 31 | 32 | %% Rate stick 33 | 34 | [p,~,t,~]=refine2([0,0; 1,0; 1,0.05; 0,0.05;],[], [], [], scaleStick); 35 | 36 | [C,NC,CMid,NCMid,NeighbourCells]=buildConnection(t); 37 | phi=drectangle(p, -1, 0, -1, 1); 38 | 39 | save(['RateStick_' num2str(scaleStick) '.mat'],'C','NC','CMid','NCMid','NeighbourCells','t','p','phi'); 40 | 41 | %% Functions 42 | 43 | function d = fdRec( p ) 44 | d=drectangle(p, -0, 1, -0, 1); 45 | end 46 | 47 | function d = fdU( p ) 48 | dBlock=drectangle(p, -0, 1, -0, 5); 49 | dUR=fdUpperBlock(p); 50 | d=ddiff(dBlock, dUR); 51 | end 52 | 53 | function d = fdStick( p ) 54 | d=drectangle(p, 0, 1, 0, 0.1); 55 | end 56 | 57 | function d = fdNotchedCircle( p ) 58 | gRec1=drectangle(p, 0.43, 0.57, 0.65, 0.99); 59 | gCircle=dcircle(p, 0.5, 0.65, 0.25); 60 | d=ddiff(gCircle, gRec1); 61 | end 62 | 63 | function d = fdUpperBlock( p ) 64 | d1=drectangle(p, 0.4, 0.6, 0.6, 1.1); 65 | d2=drectangle(p, 0, 1, 1, 1.1); 66 | d=dunion(d1,d2); 67 | end 68 | 69 | function d = fd2Circles( p ) 70 | d1=dcircle(p, 0.3, 0.3, 0.2); 71 | d2=dcircle(p, 0.7, 0.7, 0.2); 72 | d=dunion(d1,d2); 73 | end -------------------------------------------------------------------------------- /src/RateStickEvolve.m: -------------------------------------------------------------------------------- 1 | clc; 2 | clear; 3 | config; 4 | load(['RateStick_' num2str(scaleStick) '.mat']); 5 | 6 | %% load mesh 7 | NPoints=size(phi,1); 8 | 9 | %% initialization 10 | circleR=scaleStick/2; 11 | phi=dcircle(p, 0.0, 0.025, circleR); 12 | 13 | velocity=ones(size(p,1),1); 14 | 15 | %% auxiliary data 16 | disp('Building edges ...') 17 | ovData=buildOutgoingEdges(p,C,NC,CMid,NCMid); 18 | [A,edgeWeights]=buildMatrixA(ovData); 19 | disp('Building edges done.') 20 | 21 | %% evolve 22 | NEvolove=round(1/evolveStepStick); 23 | Result=cell(NEvolove,1); 24 | EmResult=zeros(NEvolove,1); 25 | EaResult=zeros(NEvolove,1); 26 | 27 | isoLines=[-0.2:0.02:-0.04, -0.02:0.005:0.02, 0.04:0.02:0.4]; 28 | 29 | for e=1:NEvolove 30 | disp(['Step ' num2str(e) ' ' datestr(now,13)]); 31 | Result{e,1}=phi; 32 | 33 | GEvolve=calcGradient(p, phi, ovData, A, velocity, edgeWeights); 34 | GEvolveModule=sqrt(GEvolve(:,1).^2 + GEvolve(:,2).^2); 35 | deltaAmountEvolve=GEvolveModule .* velocity * evolveStepStick; 36 | intermediaPhi=phi-0.5*deltaAmountEvolve; 37 | 38 | GEvolve=calcGradient(p, intermediaPhi, ovData, A, velocity, edgeWeights); 39 | GEvolveModule=sqrt(GEvolve(:,1).^2 + GEvolve(:,2).^2); 40 | deltaAmountEvolve=GEvolveModule .* velocity * evolveStepStick; 41 | phi=phi-deltaAmountEvolve; 42 | 43 | %% error & record 44 | circleR=circleR + evolveStepStick; 45 | %% analytical data 46 | phiAnalytical=dcircle(p, 0.0, 0.025, circleR); 47 | 48 | ErrorTags=abs(phiAnalytical)<5*scaleStick; 49 | Error=abs(phi(ErrorTags)-phiAnalytical(ErrorTags)); 50 | EmResult(e)=max(Error); 51 | EaResult(e)=mean(Error); 52 | disp(['Error: max->' num2str(EmResult(e)) ' average->' num2str(EaResult(e))]); 53 | 54 | NR=10; 55 | for r=1:NR 56 | disp(['Reinitial ' num2str(r) ' ' datestr(now,13)]); 57 | 58 | GReinitial=calcGradient(p, phi, ovData, A, phi, edgeWeights); 59 | S=phi./sqrt(phi.^2+(scaleStick*1)^2); 60 | deltaAmountReinitial=S .* (sqrt(GReinitial(:,1).^2+GReinitial(:,2).^2)-1) * reinitialStepStick; 61 | intermediaPhi=phi-0.5*deltaAmountReinitial; 62 | 63 | GReinitial=calcGradient(p, intermediaPhi, ovData, A, phi, edgeWeights); 64 | S=phi./sqrt(intermediaPhi.^2+(scaleStick*1)^2); 65 | deltaAmountReinitial=S .* (sqrt(GReinitial(:,1).^2+GReinitial(:,2).^2)-1) * reinitialStepStick; 66 | phi=phi-deltaAmountReinitial; 67 | end 68 | end 69 | 70 | %% save 71 | save(['resultData/RateStick_' num2str(scaleStick) '.mat'], 'Result', 'EmResult', 'EaResult', 'p', 't'); -------------------------------------------------------------------------------- /src/Reinitialize2D.m: -------------------------------------------------------------------------------- 1 | clc; 2 | clear; 3 | config; 4 | 5 | %% load mesh 6 | load(['3Circles_' num2str(scale) '.mat']); 7 | NPoints=size(phi,1); 8 | 9 | %% initial phi 10 | x1=0.25; 11 | x2=0.75; 12 | y1=0.25; 13 | y2=0.75; 14 | 15 | phi=-min(min(min(-y1+p(:,2), y2-p(:,2)),-x1+p(:,1)),x2-p(:,1)); 16 | phi=phi.*((p(:,1)-0.3).^2 + (p(:,2)-0.2).^2 + 0.1); 17 | 18 | %% auxiliary data 19 | disp('Building edges ...') 20 | ovData=buildOutgoingEdges(p,C,NC,CMid,NCMid); 21 | [A,edgeWeights]=buildMatrixA(ovData); 22 | disp('Building edges done.') 23 | 24 | %% evolve 25 | NEvolove=80; 26 | NWriteInterval=2; 27 | Result=cell(round(NEvolove/NWriteInterval)+1,1); 28 | 29 | isoLines=-0.5:1e-2:0.5; 30 | 31 | for e=1:NEvolove 32 | disp(['Step ' num2str(e) ' ' datestr(now,13)]); 33 | 34 | if mod(e,NWriteInterval)==1 35 | Result{1+(e-1)/NWriteInterval,1}=phi; 36 | end 37 | 38 | GReinitial=calcGradient(p, phi, ovData, A, phi, edgeWeights); 39 | S=phi./sqrt(phi.^2+(scale*1)^2); 40 | deltaAmountReinitial=S .* (sqrt(GReinitial(:,1).^2+GReinitial(:,2).^2)-1) * reinitialStep; 41 | intermediaPhi=phi-0.5*deltaAmountReinitial; 42 | 43 | GReinitial=calcGradient(p, intermediaPhi, ovData, A, phi, edgeWeights); 44 | S=phi./sqrt(intermediaPhi.^2+(scale*1)^2); 45 | deltaAmountReinitial=S .* (sqrt(GReinitial(:,1).^2+GReinitial(:,2).^2)-1) * reinitialStep; 46 | phi=phi-deltaAmountReinitial; 47 | 48 | end 49 | 50 | %% draw 51 | clf; 52 | tricontour(p,t,phi,isoLines); 53 | xlim([0,1]); 54 | ylim([0,1]); 55 | drawnow; -------------------------------------------------------------------------------- /src/ThreeCirclesEvolve.m: -------------------------------------------------------------------------------- 1 | clc; 2 | clear; 3 | config; 4 | 5 | %% load mesh 6 | load(['3Circles_' num2str(scale) '.mat']); 7 | NPoints=size(phi,1); 8 | 9 | %% initialization 10 | circleR=0.2; 11 | d1=dcircle(p, 0.3, 0.3, circleR); 12 | d2=dcircle(p, 0.7, 0.3, circleR); 13 | d3=dcircle(p, 0.5, 0.7, circleR); 14 | phi=dunion(dunion(d1,d2),d3); 15 | 16 | velocity=ones(size(p,1),1); 17 | 18 | %% auxiliary data 19 | disp('Building edges ...') 20 | ovData=buildOutgoingEdges(p,C,NC,CMid,NCMid); 21 | [A,edgeWeights]=buildMatrixA(ovData); 22 | disp('Building edges done.') 23 | 24 | %% evolve 25 | NEvolove=100; 26 | Result=cell(NEvolove,1); 27 | EmResult=zeros(NEvolove,1); 28 | EaResult=zeros(NEvolove,1); 29 | 30 | isoLines=[-0.2:0.02:-0.04, -0.02:0.01:0.02, 0.04:0.02:0.4]; 31 | 32 | for e=1:NEvolove 33 | disp(['Step ' num2str(e) ' ' datestr(now,13)]); 34 | Result{e,1}=phi; 35 | 36 | GEvolve=calcGradient(p, phi, ovData, A, velocity, edgeWeights); 37 | GEvolveModule=sqrt(GEvolve(:,1).^2 + GEvolve(:,2).^2); 38 | deltaAmountEvolve=GEvolveModule .* velocity * evolveStep; 39 | intermediaPhi=phi-0.5*deltaAmountEvolve; 40 | 41 | GEvolve=calcGradient(p, intermediaPhi, ovData, A, velocity, edgeWeights); 42 | GEvolveModule=sqrt(GEvolve(:,1).^2 + GEvolve(:,2).^2); 43 | deltaAmountEvolve=GEvolveModule .* velocity * evolveStep; 44 | phi=phi-deltaAmountEvolve; 45 | 46 | %% error & record 47 | circleR=circleR+velocity * evolveStep; 48 | %% analytical data 49 | d1=dcircle(p, 0.3, 0.3, circleR); 50 | d2=dcircle(p, 0.7, 0.3, circleR); 51 | d3=dcircle(p, 0.5, 0.7, circleR); 52 | phiAnalytical=dunion(dunion(d1,d2),d3); 53 | 54 | ErrorTags=abs(phiAnalytical)<5*scale; 55 | Error=abs(phi(ErrorTags)-phiAnalytical(ErrorTags)); 56 | EmResult(e)=max(Error); 57 | EaResult(e)=mean(Error); 58 | disp(['Error: max->' num2str(EmResult(e)) ' average->' num2str(EaResult(e))]); 59 | end 60 | 61 | %% save 62 | save(['resultData/3Circles_' num2str(scale) '.mat'], 'Result', 'EmResult', 'EaResult', 'p', 't'); -------------------------------------------------------------------------------- /src/UShapedAnalytical.m: -------------------------------------------------------------------------------- 1 | % this file contains necessary functions to calculate the U-shaped example 2 | function [phi]=UShapedAnalytical(e, p, arcStep) 3 | % main function 4 | % input: e: propagate distance of the slowest area 5 | % p: points 6 | 7 | %% coordinates of key vertices, left -> right 8 | Rsf=0.5; 9 | Rsfs2 = Rsf^2; 10 | taper = pi/2 + asin(Rsf); 11 | v1X = 0; 12 | v1Y = 1 - e; 13 | 14 | v3X = 0.15; 15 | v3Y = 1 - e/Rsf; 16 | [l23a, l23b, l23c] = pointK2ABC(v3X, v3Y, taper); 17 | 18 | % line l12: y = v1Y 19 | [v2X, v2Y] = lineCross(l23a, l23b, l23c, 0, 1, -v1Y); 20 | 21 | edges = [... 22 | v1X, v1Y;... 23 | v2X, v2Y;... 24 | v3X, v3Y;... 25 | ]; 26 | 27 | % if v2X < 0, v1 disappears 28 | if v2X < 0 29 | edges(1, :) = []; 30 | edges(1, :) = [0, -l23c/l23b;]; 31 | end 32 | 33 | if (0.4 - e/Rsf) < 0.15 34 | error('Analytical solution fails when (0.4 - e/Rsf) < 0.15'); 35 | end 36 | v3aX = 0.4 - e/Rsf; 37 | v3aY = v3Y; 38 | 39 | v4X = v3aX; 40 | v4Y = 0.6; 41 | 42 | edges = [edges; v3aX, v3aY; v4X, v4Y]; 43 | 44 | % there is a arc v4 ~ v5 45 | 46 | v6X = 0.6; 47 | v6Y = 0.6 - e/Rsfs2; 48 | [l56a, l56b, l56c] = pointK2ABC(v6X, v6Y, taper); 49 | 50 | v5aY = 0.6 - e/Rsf; 51 | v5aX = (-l56c - l56b*v5aY) / l56a; 52 | 53 | if v5aX > 0.4 54 | % horizon line v5 - v5a exists 55 | edges = [edges; arc2Poly(0.4, 0.6, e/Rsf, pi, 1.5*pi, arcStep);... 56 | v5aX, v5aY; v6X, v6Y]; 57 | else 58 | % the line starts from v6 intersects with arc v4 ~ v5 59 | [v5X, v5Y] = lineCrossCircle(l56a, l56b, l56c, 0.4, 0.6, e/Rsf, v6X, v6Y); 60 | theta2 = atan2(v5X - 0.4, v5Y - 0.6); 61 | while theta2 < pi 62 | theta2 = theta2 + 2*pi; 63 | end 64 | edges = [edges; arc2Poly(0.4, 0.6, e/Rsf, pi, theta2, arcStep); v6X, v6Y]; 65 | end 66 | 67 | % there is a arc v6 ~ v7 68 | 69 | v7X = 0.6 + e/Rsfs2; 70 | v7Y = 0.6; 71 | 72 | v9X = 1; 73 | v9Y = 1 - e/Rsfs2; 74 | 75 | v8X = v7X; 76 | v8Y = v9Y; 77 | 78 | if v8X > 1 79 | theta2 = 2*pi - acos(0.4/(e/Rsfs2)); 80 | edges = [edges; arc2Poly(0.6, 0.6, e/Rsfs2, 1.5*pi, theta2, arcStep)]; 81 | else 82 | edges = [edges; arc2Poly(0.6, 0.6, e/Rsfs2, 1.5*pi, 2*pi, arcStep)]; 83 | edges = [edges; v7X, v7Y; v8X, v8Y; v9X, v9Y]; 84 | end 85 | 86 | %% distance 87 | [~, phi, ~] = distance2curve(edges, p); 88 | 89 | %% sign 90 | 91 | if v2X > 0 92 | tag = p(:, 1) < v2X & p(:,2) > 1-e; 93 | phi(tag) = -phi(tag); 94 | tag = p(:, 1) >= v2X & p(:, 1) < v3X & isOnUpperOrRightOfLine(l23a, l23b, l23c, p(:,1), p(:,2)); 95 | phi(tag) = -phi(tag); 96 | else 97 | tag = p(:, 1) < v3X & isOnUpperOrRightOfLine(l23a, l23b, l23c, p(:,1), p(:,2)); 98 | phi(tag) = -phi(tag); 99 | end 100 | 101 | tag = p(:, 1) >= v3X & p(:, 1) < v3aX & p(:, 2) > 1-e/Rsf; 102 | phi(tag) = -phi(tag); 103 | 104 | if v5aX > 0.4 105 | % horizon line v5 - v5a exists 106 | tag = p(:, 1) >= v3aX & p(:, 1) < 0.4 & ... 107 | (p(:, 2) >= 0.6 | ... 108 | (p(:, 2) < 0.6 & sqrt( (p(:, 1) - 0.4).^2 + (p(:, 2) - 0.6).^2) < e/Rsf)); 109 | tag = tag | (p(:, 1) >= 0.4 & p(:, 1) < v5aX & p(:, 2) > 0.6 - e/Rsf); 110 | tag = tag | (p(:, 1) >= v5aX & p(:, 1) < 0.6 & ... 111 | isOnUpperOrRightOfLine(l56a, l56b, l56c, p(:,1), p(:,2))); 112 | 113 | phi(tag) = -phi(tag); 114 | else 115 | % the line starts from v6 intersects with arc v4 ~ v5 116 | tag = p(:, 1) >= v3aX & p(:, 1) < v5X & ... 117 | (p(:, 2) >= 0.6 | ... 118 | (p(:, 2) < 0.6 & sqrt( (p(:, 1) - 0.4).^2 + (p(:, 2) - 0.6).^2) < e/Rsf)); 119 | tag = tag | (p(:, 1) >= v5X & p(:, 1) < 0.6 & ... 120 | isOnUpperOrRightOfLine(l56a, l56b, l56c, p(:,1), p(:,2))); 121 | 122 | phi(tag) = -phi(tag); 123 | end 124 | 125 | if v8X > 1 126 | arcEndY = 0.6 - sqrt((e/Rsfs2)^2 - 0.16); 127 | tag = p(:, 1) >= 0.6 & (p(:, 2) >= arcEndY | ... 128 | (p(:, 2) < arcEndY & sqrt( (p(:, 1) - 0.6).^2 + (p(:, 2) - 0.6).^2) < e/Rsfs2)); 129 | phi(tag) = -phi(tag); 130 | else 131 | tag = p(:, 1) >= 0.6 & p(:, 1) < v7X & ... 132 | (p(:, 2) >= 0.6 | ... 133 | (p(:, 2) < 0.6 & sqrt( (p(:, 1) - 0.6).^2 + (p(:, 2) - 0.6).^2) < e/Rsfs2)); 134 | tag = tag | (p(:, 1) >= v7X & p(:, 2) > 1-e/Rsfs2); 135 | 136 | phi(tag) = -phi(tag); 137 | end 138 | 139 | end 140 | 141 | %% utility functions 142 | function [A, B, C] = pointK2ABC(x0, y0, theta) 143 | % converte line equation format 144 | % (x0, y0), theta ==>> Ax + By + C = 0 145 | % 146 | % y-y0=tan(theta)(x-x0) 147 | % y/tan(theta) - y0/tan(theta) = x - x0 148 | 149 | iPi_2=theta/(pi/2); 150 | iPi=theta/(pi); 151 | isPi_2= abs(iPi_2-round(iPi_2)) < eps && abs(iPi - round(iPi)) >= eps; 152 | isPi = abs(iPi - round(iPi)) (-C/A); 170 | else 171 | r = y > (-C - A*x)/B; 172 | end 173 | end 174 | 175 | function [rX, rY] = lineCross(A1, B1, C1, A2, B2, C2) 176 | r=[A1, B1; A2, B2]\[-C1;-C2]; 177 | rX=r(1); 178 | rY=r(2); 179 | end 180 | 181 | function [rX, rY] = lineCrossCircle(A, B, C, cX, cY, R, xN, yN) 182 | % find the intersection of circle and line, then choose the nearer one to 183 | % [xN, yN] and return it 184 | % 185 | % Ax + By + C = 0 186 | % x^2 - 2 cX x + y^2 - 2 cY y + cX^2 + cY^2 = R^2 187 | % (x - cX)^2 + (y - cY)^2 = R^2 188 | if abs(A) t1 x + t2 209 | t1 = -A/B; 210 | t2 = -C/B - cY; 211 | % x^2 - 2 cX x + cX^2 + t1^2 x^2 + 2 t1 t2 x + t2^2 = R^2 212 | % (1+t1^2) x^2 + (2 t1 t2 - 2cX) x + cX^2 + t2^2 - R^2 = 0 213 | b = 2 * t1 * t2 - 2 * cX; 214 | a = 1+t1^2; 215 | c = cX^2 + t2^2 - R^2; 216 | 217 | dlt = sqrt(b^2 - 4 * a * c); 218 | if ~isreal(dlt) 219 | if abs(imag(dlt)) < eps 220 | dlt = real(dlt); 221 | else 222 | error('No real root!'); 223 | end 224 | end 225 | rX1 = (-b + dlt) / (2*a); 226 | rY1 = - (C + A*rX1) / B; 227 | rX2 = (-b - dlt) / (2*a); 228 | rY2 = - (C + A*rX2) / B; 229 | end 230 | 231 | d1 = (rX1 - xN)^2 + (rY1 - yN)^2; 232 | d2 = (rX2 - xN)^2 + (rY2 - yN)^2; 233 | 234 | if d1 > d2 235 | rX = rX2; 236 | rY = rY2; 237 | else 238 | rX = rX1; 239 | rY = rY1; 240 | end 241 | 242 | end 243 | 244 | function [r] = arc2Poly(cX, cY, R, theta1, theta2, stepScale) 245 | % theta1, theta2 must be counter-clockwise 246 | 247 | step = stepScale / R; % (2*pi) / (2*R*pi/stepScale) 248 | thetas = (theta1:step:theta2)'; 249 | if abs(thetas(end) - theta2) > eps 250 | thetas = [thetas; theta2]; 251 | end 252 | 253 | r = repmat([cX, cY], numel(thetas), 1) + [cos(thetas), sin(thetas)] * R; 254 | 255 | end -------------------------------------------------------------------------------- /src/UShapedEvolve.m: -------------------------------------------------------------------------------- 1 | clc; 2 | clear; 3 | config; 4 | load(['U_Region_' num2str(scale) '.mat']); 5 | 6 | NPoints=size(phi,1); 7 | Rsf=0.5; 8 | 9 | % set velocity 10 | velocity=ones(size(p,1),1); 11 | velocity(p(:,1)<0.6)=Rsf; 12 | velocity(p(:,1)<0.15)=Rsf*Rsf; 13 | 14 | disp('Building edges ...') 15 | ovData=buildOutgoingEdges(p,C,NC,CMid,NCMid); 16 | [A,edgeWeights]=buildMatrixA(ovData); 17 | disp('Building edges done.') 18 | 19 | disp('Building triangle basic data ...') 20 | [triangleOutNorms ,cornerNorm, areas]=generateTriangleNorms(p, t); 21 | disp('Building triangle basic data done.') 22 | 23 | NEvolove=round(0.124/(evolveStep*Rsf*Rsf)); 24 | Result=cell(NEvolove,1); 25 | AnalyticalResult=cell(NEvolove,1); 26 | EmResult=zeros(NEvolove,1); 27 | EaResult=zeros(NEvolove,1); 28 | 29 | isoLines=[-0.2:0.02:-0.04, -0.02:0.01:0.02, 0.04:0.02:0.4]; 30 | 31 | for r=1:30 32 | disp(['Reinitial ' num2str(r) ' ' datestr(now,13)]); 33 | 34 | GReinitial=calcGradient(p, phi, ovData, A, phi, edgeWeights); 35 | S=phi./sqrt(phi.^2+(scale*0.8)^2); 36 | deltaAmountReinitial=S .* (sqrt(GReinitial(:,1).^2+GReinitial(:,2).^2)-1) * reinitialStep; 37 | phi=phi-deltaAmountReinitial; 38 | end 39 | 40 | e = 0; 41 | for idx=1:NEvolove 42 | disp(['Step ' num2str(idx) ' ' datestr(now,13)]); 43 | 44 | %% Evolve 45 | GEvolve=calcGradient(p, phi, ovData, A, velocity, edgeWeights); 46 | GEvolveModule=sqrt(GEvolve(:,1).^2 + GEvolve(:,2).^2); 47 | deltaAmountEvolve=GEvolveModule .* velocity * evolveStep; 48 | intermediaPhi=phi-0.5*deltaAmountEvolve; 49 | 50 | GEvolve=calcGradient(p, intermediaPhi, ovData, A, velocity, edgeWeights); 51 | GEvolveModule=sqrt(GEvolve(:,1).^2 + GEvolve(:,2).^2); 52 | deltaAmountEvolve=GEvolveModule .* velocity * evolveStep; 53 | phi=phi-deltaAmountEvolve; 54 | 55 | %% Reinitial 56 | NR=3; 57 | for r=1:NR 58 | disp(['Reinitial ' num2str(r) ' ' datestr(now,13)]); 59 | 60 | GReinitial=calcGradient(p, phi, ovData, A, phi, edgeWeights); 61 | S=phi./sqrt(phi.^2+(scale*1)^2); 62 | deltaAmountReinitial=S .* (sqrt(GReinitial(:,1).^2+GReinitial(:,2).^2)-1) * reinitialStep; 63 | intermediaPhi=phi-0.5*deltaAmountReinitial; 64 | 65 | GReinitial=calcGradient(p, intermediaPhi, ovData, A, phi, edgeWeights); 66 | S=phi./sqrt(intermediaPhi.^2+(scale*1)^2); 67 | deltaAmountReinitial=S .* (sqrt(GReinitial(:,1).^2+GReinitial(:,2).^2)-1) * reinitialStep; 68 | phi=phi-deltaAmountReinitial; 69 | end 70 | 71 | %% Error % record 72 | e = e + evolveStep*Rsf*Rsf; 73 | phiAnalytical = UShapedAnalytical(e, p, scale/100); 74 | 75 | Result{idx, 1} = phi; 76 | AnalyticalResult{idx, 1} = phiAnalytical; 77 | 78 | ErrorTags=abs(phiAnalytical)<5*scale; 79 | Error=abs(phi(ErrorTags)-phiAnalytical(ErrorTags)); 80 | EmResult(idx)=max(Error); 81 | EaResult(idx)=mean(Error); 82 | disp(['Error: max->' num2str(EmResult(idx)) ' average->' num2str(EaResult(idx))]); 83 | 84 | hold on; 85 | [~, h]=tricontour(p,t,phi,[0,0]); 86 | for idxH=1:numel(h) 87 | h(idxH).EdgeColor='b'; 88 | end 89 | [~, h]=tricontour(p,t,phiAnalytical,[0,0]); 90 | for idxH=1:numel(h) 91 | h(idxH).EdgeColor='r'; 92 | end 93 | drawnow; 94 | end 95 | 96 | save(['resultData/UShaped_' num2str(scale) '.mat'], 'Result', 'AnalyticalResult', 'EmResult', 'EaResult', 'p', 't'); 97 | %save(['resultData/UShaped_ExplictDisabled_' num2str(scale) '.mat'], 'Result', 'AnalyticalResult', 'EmResult', 'EaResult', 'p', 't'); -------------------------------------------------------------------------------- /src/buildConnection.m: -------------------------------------------------------------------------------- 1 | function [C, NC, CMid, NCMid, NeighbourCells] = buildConnection(t) 2 | 3 | MAX_CONNECTION_PER_VERTEX=10; 4 | MAX_NEIGHBOR_CELLS_PER_VERTEX=8; 5 | 6 | NPoints=max(max(t)); 7 | C=zeros(NPoints, MAX_CONNECTION_PER_VERTEX); 8 | CMid=zeros(NPoints, MAX_NEIGHBOR_CELLS_PER_VERTEX, 2); 9 | NeighbourCells=zeros(NPoints, MAX_NEIGHBOR_CELLS_PER_VERTEX); 10 | 11 | for i=1:NPoints 12 | 13 | logicalInvolvedTag=sum(t==i, 2)>0; 14 | [r,~,~]=find(logicalInvolvedTag); 15 | NeighbourCells(i,1:numel(r))=r'; 16 | 17 | sourceMat=t(logicalInvolvedTag,:); 18 | writeCounter=1; 19 | 20 | for p=1:size(sourceMat,1) 21 | triTags=sourceMat(p,:); 22 | b=ismember(triTags,i); 23 | for bi=1:size(b,2) 24 | if((~b(bi)) && (~sum(ismember(C(i,:),triTags(bi))))) 25 | if(writeCounter>MAX_CONNECTION_PER_VERTEX) 26 | error(['writeCounter > ' num2str(MAX_CONNECTION_PER_VERTEX)]); 27 | else 28 | C(i,writeCounter)=triTags(bi); 29 | writeCounter=writeCounter+1; 30 | end 31 | end 32 | end 33 | CMid(i,p,:)=triTags(~b); 34 | end 35 | end 36 | 37 | NC=sum(C~=0,2); 38 | NCMid=sum(sum(CMid~=0,3)/2,2); 39 | end -------------------------------------------------------------------------------- /src/buildMatrixA.m: -------------------------------------------------------------------------------- 1 | function [rA, edgeWeights] = buildMatrixA(ovData) 2 | NPoints=size(ovData,1); 3 | rA(NPoints,4).OK=true; 4 | rA(NPoints,4).A=[]; 5 | edgeWeights=cell(NPoints,4); 6 | 7 | for idxP=1:NPoints 8 | for idxC=1:4 9 | OV=ovData{idxP,idxC}{1,1}; 10 | OVMid=ovData{idxP,idxC}{1,3}; 11 | A=[OV(:,1:2);OVMid(:,1:2)]; 12 | 13 | edgeWeights{idxP,idxC}=[ovData{idxP,idxC}{1,5};ovData{idxP,idxC}{1,6}]; 14 | 15 | rA(idxP,idxC).A=A .* edgeWeights{idxP,idxC}; 16 | rA(idxP,idxC).OK=size(A,1)>=2; 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/buildOutgoingEdges.m: -------------------------------------------------------------------------------- 1 | function OV = buildOutgoingEdges(p, C, NC, CMid, NCMid) 2 | 3 | NVertices=size(NC,1); 4 | OV=cell(NVertices,5); 5 | 6 | xPos=[1, 0]; 7 | xNeg=[-1, 0]; 8 | yPos=[0, 1]; 9 | yNeg=[0, -1]; 10 | 11 | global divergenceThreshold; 12 | 13 | for idxP=1:NVertices 14 | crdT=[p(idxP, 1) p(idxP, 2)]; 15 | 16 | neighborTag=C(idxP,1:NC(idxP))'; 17 | crdV=p(neighborTag,:); 18 | crdOV=crdV-crdT; 19 | crdOVNorm=normr(crdOV); 20 | 21 | neighborTagMid=CMid(idxP,1:NCMid(idxP),:); 22 | neighborTagMid=reshape(neighborTagMid, [NCMid(idxP) 2]); 23 | crdVMid=(p(neighborTagMid(:,1),:)+p(neighborTagMid(:,2),:))./2; 24 | crdOVMid=crdVMid-crdT; 25 | crdOVNormMid=normr(crdOVMid); 26 | 27 | % xPos -> 1 28 | divergence=dot(crdOVNorm',repmat(xPos,NC(idxP),1)')'; 29 | logicTag=divergence>divergenceThreshold; 30 | divergenceMid=dot(crdOVNormMid',repmat(xPos,NCMid(idxP),1)')'; 31 | logicTagMid=divergenceMid>divergenceThreshold; 32 | OV{idxP,1}=compactOVInfo(logicTag, crdOV, neighborTag, logicTagMid,... 33 | crdOVMid, neighborTagMid, divergence, divergenceMid); 34 | 35 | % xNeg -> 2 36 | divergence=dot(crdOVNorm',repmat(xNeg,NC(idxP),1)')'; 37 | logicTag=divergence>divergenceThreshold; 38 | divergenceMid=dot(crdOVNormMid',repmat(xNeg,NCMid(idxP),1)')'; 39 | logicTagMid=divergenceMid>divergenceThreshold; 40 | OV{idxP,2}=compactOVInfo(logicTag, crdOV, neighborTag, logicTagMid,... 41 | crdOVMid, neighborTagMid, divergence, divergenceMid); 42 | 43 | % yPos -> 3 44 | divergence=dot(crdOVNorm',repmat(yPos,NC(idxP),1)')'; 45 | logicTag=divergence>divergenceThreshold; 46 | divergenceMid=dot(crdOVNormMid',repmat(yPos,NCMid(idxP),1)')'; 47 | logicTagMid=divergenceMid>divergenceThreshold; 48 | OV{idxP,3}=compactOVInfo(logicTag, crdOV, neighborTag, logicTagMid,... 49 | crdOVMid, neighborTagMid, divergence, divergenceMid); 50 | 51 | % yNeg -> 4 52 | divergence=dot(crdOVNorm',repmat(yNeg,NC(idxP),1)')'; 53 | logicTag=divergence>divergenceThreshold; 54 | divergenceMid=dot(crdOVNormMid',repmat(yNeg,NCMid(idxP),1)')'; 55 | logicTagMid=divergenceMid>divergenceThreshold; 56 | OV{idxP,4}=compactOVInfo(logicTag, crdOV, neighborTag, logicTagMid,... 57 | crdOVMid, neighborTagMid, divergence, divergenceMid); 58 | 59 | % all -> 5 60 | OV{idxP,5}={[crdOV, normr(crdOV), sqrt(crdOV(:,1).^2+crdOV(:,2).^2)], neighborTag,... 61 | [crdOVMid, normr(crdOVMid), sqrt(crdOVMid(:,1).^2+crdOVMid(:,2).^2)], neighborTagMid}; 62 | 63 | end 64 | 65 | end 66 | 67 | function OVInfoCell = compactOVInfo(logicTag, crdOV, neighborTag, logicTagMid,... 68 | crdOVMid, neighborTagMid, divergence, divergenceMid) 69 | selectedcrdOV=crdOV(logicTag,:); 70 | selectedneighborTag=neighborTag(logicTag); 71 | selectedcrdOVMid=crdOVMid(logicTagMid,:); 72 | selectedneighborTagMid=neighborTagMid(logicTagMid,:); 73 | OVInfoCell={[selectedcrdOV, sqrt(selectedcrdOV(:,1).^2+selectedcrdOV(:,2).^2)], selectedneighborTag, ... 74 | [selectedcrdOVMid, sqrt(selectedcrdOVMid(:,1).^2+selectedcrdOVMid(:,2).^2)], selectedneighborTagMid... 75 | divergence(logicTag), divergenceMid(logicTagMid)}; 76 | end 77 | -------------------------------------------------------------------------------- /src/calcGradient.m: -------------------------------------------------------------------------------- 1 | function G = calcGradient(p, phi, ovData, A, S, edgeWeights) 2 | global eps_; 3 | 4 | NPoints=size(phi,1); 5 | % 2D 6 | G=zeros(NPoints, 2); 7 | 8 | if size(S,2) > 1 9 | isUseSxyAsMainAxis=true; 10 | Sx=S(:,1); 11 | Sy=S(:,2); 12 | else 13 | isUseSxyAsMainAxis=false; 14 | Sx=S; 15 | Sy=S; 16 | end 17 | 18 | ovData5=ovData(:,5); 19 | 20 | 21 | for idxP=1:NPoints 22 | 23 | crtPhi=phi(idxP); 24 | crtCoordinate=p(idxP,:); 25 | ovEndPhi=phi(ovData5{idxP}{2}); 26 | isOnZeroContour=(crtPhi > eps_ && any(ovEndPhi < -eps_)) ... 27 | || (crtPhi < -eps_ && any(ovEndPhi > eps_)); 28 | % set isOnZeroContour = false here to disable explicit searching branch 29 | %isOnZeroContour = false; 30 | 31 | myOVEdges=ovData5{idxP}{1}; 32 | myOVEdgesMid=ovData5{idxP}{3}; 33 | myOVEdgesMidIndices=ovData5{idxP}{4}; 34 | 35 | 36 | if isOnZeroContour 37 | [GExplicit,OK]=calcGradientViaExplicitGeometry(p, idxP, myOVEdgesMidIndices, crtCoordinate, crtPhi, phi, isUseSxyAsMainAxis); 38 | if OK 39 | G(idxP,:)=GExplicit; 40 | else 41 | error('calcGradientViaExplicitGeometry Error') 42 | end 43 | 44 | else 45 | 46 | changingAmount=ovEndPhi-crtPhi; 47 | changingRate=(changingAmount)./(myOVEdges(:,5)); 48 | 49 | 50 | % find the vertex aiming at zero or aiming to upwind of S 51 | if isUseSxyAsMainAxis 52 | aimingDirection=-normr([Sx(idxP), Sy(idxP)]); 53 | else 54 | if crtPhi > 0 55 | [~,im]=min(changingRate); 56 | else 57 | [~,im]=max(changingRate); 58 | end 59 | aimingDirection=myOVEdges(im,3:4); 60 | end 61 | 62 | [GAiming1,OK1] = estimateGradientOnOneDirection(aimingDirection, myOVEdges, changingAmount,... 63 | myOVEdgesMid, myOVEdgesMidIndices, crtPhi, phi); 64 | [GAiming2,OK2] = estimateGradientOnOneDirection(-aimingDirection, myOVEdges, changingAmount,... 65 | myOVEdgesMid, myOVEdgesMidIndices, crtPhi, phi); 66 | 67 | if OK1 && OK2 68 | [~, tag]=Godunov(dot(GAiming2, aimingDirection, 2),dot(GAiming1, aimingDirection, 2), S(idxP)); 69 | if tag == 1 70 | G(idxP,:)=GAiming1; 71 | elseif tag == -1 72 | G(idxP,:)=GAiming2; 73 | else 74 | G(idxP,:)=[0, 0]; 75 | end 76 | elseif OK1 && (~OK2) 77 | G(idxP,:)=GAiming1; 78 | elseif (~OK1) && OK2 79 | G(idxP,:)=GAiming2; 80 | else 81 | error('aimingDirection unavailable'); 82 | end 83 | end 84 | end 85 | 86 | end 87 | 88 | function [G,OK]=calcGradientViaExplicitGeometry(p, idxP, myOVEdgesMidIndices, crtCoordinate, crtPhi, phi, isUseSxyAsMainAxis) 89 | global eps_; 90 | % zeroContourEdges: [x1, y1, x2, y2]; 91 | zeroContourEdges1=zeros(size(myOVEdgesMidIndices)); 92 | zeroContourEdges2=zeros(size(myOVEdgesMidIndices)); 93 | nZeroContourEdges=0; 94 | 95 | for i = 1:size(myOVEdgesMidIndices,1) 96 | triangleOtherVertices=myOVEdgesMidIndices(i,:); 97 | phiArray=[crtPhi;phi(triangleOtherVertices)]; 98 | 99 | % see if this triangle is on zero contour 100 | if sum(phiArray>eps_)>0 && sum(phiArray<-eps_)>0 101 | 102 | % see if we need to filter the triangle 103 | if isUseSxyAsMainAxis 104 | divergence=dot([Sx(idxP) Sy(idxP)], myOVEdgesMid(i, 3:4)); 105 | 106 | % check for safe 107 | if abs(divergence) > 1 108 | error('divergence cannot > 1') 109 | end 110 | 111 | if divergence < divergenceThreshold + 0.1 112 | continue; 113 | end 114 | end 115 | 116 | nZeroContourEdges=nZeroContourEdges+1; 117 | 118 | % find the edge and add to zeroContourEdges 119 | [lineSeg1, lineSeg2] = findContourInTriangle(p([idxP, triangleOtherVertices],:), phiArray); 120 | zeroContourEdges1(nZeroContourEdges,:)=lineSeg1; 121 | zeroContourEdges2(nZeroContourEdges,:)=lineSeg2; 122 | end 123 | end 124 | 125 | zeroContourEdges1(nZeroContourEdges+1:end,:)=[]; 126 | zeroContourEdges2(nZeroContourEdges+1:end,:)=[]; 127 | 128 | % find the nearest point 129 | nearestPoint=[]; 130 | nearestDistance=Inf; 131 | for i=1:nZeroContourEdges 132 | [xy,dis,~]=distance2curve([zeroContourEdges1(i,:);zeroContourEdges2(i,:)],crtCoordinate); 133 | if dis < nearestDistance 134 | nearestPoint=xy; 135 | nearestDistance=dis; 136 | end 137 | end 138 | 139 | % calc real gradient 140 | gradientDirection=normr(nearestPoint-crtCoordinate); 141 | G=gradientDirection*(-crtPhi)/nearestDistance; 142 | 143 | OK=true; 144 | end 145 | 146 | function [G] = calcGradient1Axis1Vertex(phi,crtPhi,OVTags,OVMidTags,dA,weight) 147 | changingAmount=(phi(OVTags)-crtPhi); 148 | 149 | phiMid=(phi(OVMidTags(:,1))+phi(OVMidTags(:,2)))./2; 150 | changingAmountMid=(phiMid-crtPhi); 151 | 152 | b=[changingAmount;changingAmountMid] .* weight; 153 | G=dA\b; 154 | end 155 | 156 | function [G,OK] = estimateGradientOnOneDirection(aimingDirection, myOVEdges, changingAmount,... 157 | myOVEdgesMid, myOVEdgesMidIndices, crtPhi, phi) 158 | 159 | global divergenceThreshold; 160 | crtDivergenceThreshold=divergenceThreshold; 161 | 162 | % find the region where gradient vector may exist 163 | % construct A 164 | realEdgeDivergence=dot(myOVEdges(:,3:4),repmat(aimingDirection,size(myOVEdges,1),1),2); 165 | midEdgeDivergence=dot(myOVEdgesMid(:,3:4),repmat(aimingDirection,size(myOVEdgesMid,1),1),2); 166 | isRealEdgeDivergenceAccepted=realEdgeDivergence>crtDivergenceThreshold; 167 | isMidEdgeDivergenceAccepted=midEdgeDivergence>crtDivergenceThreshold; 168 | 169 | OK = (sum(isRealEdgeDivergenceAccepted) + sum(isMidEdgeDivergenceAccepted)) >= 2; 170 | 171 | if ~OK 172 | G=[0,0]; 173 | return; 174 | end 175 | 176 | weights=[realEdgeDivergence(isRealEdgeDivergenceAccepted);... 177 | midEdgeDivergence(isMidEdgeDivergenceAccepted)]; 178 | A=[myOVEdges(isRealEdgeDivergenceAccepted,1:2);myOVEdgesMid(isMidEdgeDivergenceAccepted,1:2)]... 179 | .*weights; 180 | b=[changingAmount(isRealEdgeDivergenceAccepted);... 181 | (phi(myOVEdgesMidIndices(isMidEdgeDivergenceAccepted,1)) +... 182 | phi(myOVEdgesMidIndices(isMidEdgeDivergenceAccepted,2)))./2 - crtPhi]... 183 | .*weights; 184 | gradientEstimatedVia1stStep=A\b; 185 | G=gradientEstimatedVia1stStep'; 186 | end 187 | 188 | function [R, returnTag] = Godunov(negativeDiff,positiveDiff,S) 189 | if S>0 190 | if (negativeDiff <= 0 && positiveDiff <= 0) 191 | returnTag=1; 192 | R=positiveDiff; 193 | return; 194 | end 195 | if (negativeDiff >= 0 && positiveDiff >= 0) 196 | returnTag=-1; 197 | R=negativeDiff; 198 | return; 199 | end 200 | if (negativeDiff > 0 && positiveDiff < 0) 201 | if (abs(negativeDiff) > abs(positiveDiff)) 202 | returnTag=-1; 203 | R=negativeDiff; 204 | return; 205 | else 206 | returnTag=1; 207 | R=positiveDiff; 208 | return; 209 | end 210 | else 211 | returnTag=0; 212 | R=0; 213 | return; 214 | end 215 | else 216 | if (negativeDiff <= 0 && positiveDiff <= 0) 217 | returnTag=-1; 218 | R=negativeDiff; 219 | return; 220 | end 221 | if (negativeDiff >= 0 && positiveDiff >= 0) 222 | returnTag=1; 223 | R=positiveDiff; 224 | return; 225 | end 226 | if (negativeDiff < 0 && positiveDiff > 0) 227 | if (abs(negativeDiff) > abs(positiveDiff)) 228 | returnTag=-1; 229 | R=negativeDiff; 230 | return; 231 | else 232 | returnTag=1; 233 | R=positiveDiff; 234 | return; 235 | end 236 | else 237 | returnTag=0; 238 | R=0; 239 | return; 240 | end 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /src/config.m: -------------------------------------------------------------------------------- 1 | global scale; 2 | scale=0.01; 3 | 4 | global divergenceThreshold; 5 | divergenceThreshold=0.65; 6 | 7 | global eps_; 8 | eps_=1e-3; 9 | 10 | center=[0.5, 0.5]; 11 | evolveStep=0.004; 12 | reinitialStep=0.007; 13 | 14 | global scaleStick; 15 | scaleStick=0.005; 16 | evolveStepStick=0.003; 17 | reinitialStepStick=0.001; -------------------------------------------------------------------------------- /src/dcircle.m: -------------------------------------------------------------------------------- 1 | function d=dcircle(p,xc,yc,r) 2 | 3 | % Copyright (C) 2004-2012 Per-Olof Persson. See COPYRIGHT.TXT for details. 4 | 5 | d=sqrt((p(:,1)-xc).^2+(p(:,2)-yc).^2)-r; 6 | -------------------------------------------------------------------------------- /src/ddiff.m: -------------------------------------------------------------------------------- 1 | function d=ddiff(d1,d2), d=max(d1,-d2); 2 | 3 | % Copyright (C) 2004-2012 Per-Olof Persson. See COPYRIGHT.TXT for details. 4 | -------------------------------------------------------------------------------- /src/distLineSeg2Point.m: -------------------------------------------------------------------------------- 1 | function [dist] = distLineSeg2Point(x, a, b) 2 | % input: [x,y] x: point a&b: line segment 3 | % output: dist 4 | 5 | d_ab = norm(a-b); 6 | d_ax = norm(a-x); 7 | d_bx = norm(b-x); 8 | 9 | if dot(a-b,x-b)*dot(b-a,x-a)>=0 10 | A = [a,1;b,1;x,1]; 11 | dist = abs(det(A))/d_ab; 12 | else 13 | dist = min(d_ax, d_bx); 14 | end 15 | end -------------------------------------------------------------------------------- /src/drawResult.m: -------------------------------------------------------------------------------- 1 | %% config 2 | config; 3 | meshEdgeColor=[.2,.2,.2]; 4 | startColor=[0, 0, 1]; 5 | endColor=[1, 0, 0]; 6 | fontSize=15; 7 | 8 | %% U region 9 | drawURegion=false; 10 | if drawURegion 11 | %% read data 12 | load(['resultData/UShaped_ExplictDisabled_' num2str(scale) '.mat']); 13 | disEaResult=EaResult; 14 | disEmResult=EmResult; 15 | disResult=Result; 16 | load(['resultData/UShaped_' num2str(scale) '.mat']); 17 | 18 | %% draw 19 | hold on; 20 | patch('faces',t,'vertices',p,'facecolor','w','edgecolor',meshEdgeColor); 21 | initialLineX=[0.0, 0.4, 0.4, 0.6, 0.6, 1.0]; 22 | initialLineY=[1.0, 1.0, 0.6, 0.6, 1.0, 1.0]; 23 | plot(initialLineX, initialLineY, 'r-', 'LineWidth', 3); 24 | ax = gca; 25 | ax.FontSize = fontSize; 26 | 27 | pause 28 | clf; 29 | hold on; 30 | xlim([0,1]); 31 | ylim([0,1]); 32 | vertices=[0,0; 1,0; 1,1; 0.6,1; 0.6,0.6; 0.4,0.6; 0.4,1; 0,1]; 33 | plot(vertices(:,1), vertices(:,2), 'Color', meshEdgeColor); 34 | plot([vertices(end,1), vertices(end,1)], [vertices(end,2) vertices(1,2)], 'Color', meshEdgeColor); 35 | for idxStep=1:12:length(EaResult) 36 | [~, ha]=tricontour(p, t, AnalyticalResult{idxStep}, [0, 0]); 37 | for idxH=1:numel(ha) 38 | ha(idxH).EdgeColor='r'; 39 | end 40 | 41 | [~, hd]=tricontour(p, t, disResult{idxStep}, [0, 0]); 42 | for idxH=1:numel(hd) 43 | hd(idxH).EdgeColor='b'; 44 | end 45 | 46 | [~, hr]=tricontour(p, t, Result{idxStep}, [0, 0]); 47 | for idxH=1:numel(hr) 48 | hr(idxH).EdgeColor='k'; 49 | end 50 | end 51 | legend([ha(1) hd(1) hr(1)],... 52 | {'Analytical result',... 53 | 'Simulation result without EC','Simulation result with EC'},... 54 | 'Location','SouthWest', 'FontSize', fontSize); 55 | ax = gca; 56 | ax.FontSize = fontSize; 57 | 58 | pause; 59 | clf; 60 | hold on; 61 | plot(disEmResult, 'b-.', 'LineWidth', 1); 62 | plot(disEaResult, 'r-.', 'LineWidth', 1); 63 | plot(EmResult, 'b-', 'LineWidth', 2); 64 | plot(EaResult, 'r-', 'LineWidth', 2); 65 | h=legend('E_m without EC','E_a without EC',... 66 | 'E_m with EC','E_a with EC',... 67 | 'Location','NorthWest'); 68 | h.FontSize = fontSize; 69 | xlabel('Step','FontSize', fontSize); 70 | ylabel('Error','FontSize', fontSize); 71 | ax = gca; 72 | ax.FontSize = fontSize; 73 | 74 | end 75 | 76 | %% 3 circles 77 | drawCircles=false; 78 | drawCirclesGap=3; 79 | if drawCircles 80 | load(['resultData/3Circles_' num2str(scale) '.mat']); 81 | 82 | %% iso lines 83 | idxArray=1:drawCirclesGap:size(Result,1); 84 | nLines=numel(idxArray); 85 | 86 | hold on; 87 | for idx=1:nLines 88 | phi=Result{idxArray(idx)}; 89 | [C, h]=tricontour(p, t, phi, [0, 0]); 90 | crtColor=(startColor * (nLines-idx) + endColor * idx)/nLines; 91 | for idxH=1:numel(h) 92 | h(idxH).EdgeColor=crtColor; 93 | end 94 | end 95 | ax = gca; 96 | ax.FontSize = fontSize; 97 | 98 | 99 | pause; 100 | %% error 101 | clf; 102 | hold on; 103 | plot(EmResult); 104 | plot(EaResult); 105 | ax = gca; 106 | ax.FontSize = fontSize; 107 | h=legend('E_m','E_a'); 108 | h.FontSize = fontSize; 109 | xlabel('Step','FontSize', fontSize); 110 | ylabel('Error','FontSize', fontSize); 111 | ax = gca; 112 | ax.FontSize = fontSize; 113 | end 114 | 115 | %% Rate stick 116 | drawRateStick=false; 117 | drawRateStickGap=8; 118 | if drawRateStick 119 | load(['resultData/RateStick_' num2str(scaleStick) '.mat']); 120 | 121 | %% mesh 122 | hold on; 123 | pbaspect([1 0.05 1]); 124 | patch('faces',t,'vertices',p,'facecolor','w','edgecolor',meshEdgeColor); 125 | plot(0, 0.025, 'ro', 'MarkerSize', 5, 'MarkerFaceColor', 'r'); 126 | text(-0.05, 0.025, '$\vec{x_0}$', 'Interpreter', 'latex', 'FontSize', fontSize+1); 127 | ax = gca; 128 | ax.YAxisLocation = 'right'; 129 | ax.FontSize = fontSize+1; 130 | 131 | %% contour 132 | pause; 133 | idxArray=1:drawRateStickGap:size(Result,1); 134 | nLines=numel(idxArray); 135 | 136 | clf; 137 | hold on; 138 | plot(0, 0.025, 'ro', 'MarkerSize', 5, 'MarkerFaceColor', 'r'); 139 | text(-0.05, 0.025, '$\vec{x_0}$', 'Interpreter', 'latex', 'FontSize', fontSize+1); 140 | pbaspect([1 0.05 1]); 141 | for idx=1:nLines 142 | phi=Result{idxArray(idx)}; 143 | [C, h]=tricontour(p, t, phi, [0, 0]); 144 | crtColor=(startColor * (nLines-idx) + endColor * idx)/nLines; 145 | for idxH=1:numel(h) 146 | h(idxH).EdgeColor=crtColor; 147 | end 148 | end 149 | ax = gca; 150 | ax.YAxisLocation = 'right'; 151 | ax.FontSize = fontSize+1; 152 | 153 | 154 | pause; 155 | %% error 156 | clf; 157 | hold on; 158 | pbaspect auto; 159 | plot(EmResult); 160 | plot(EaResult); 161 | h=legend('E_m','E_a','Location','SouthEast'); 162 | h.FontSize = fontSize; 163 | xlabel('Step','FontSize', fontSize); 164 | ylabel('Error','FontSize', fontSize); 165 | ax = gca; 166 | ax.FontSize = fontSize; 167 | 168 | end 169 | 170 | %% Redistance 171 | drawRedistance=true; 172 | if drawRedistance 173 | x = -1:scale:1; 174 | y = x; 175 | [gX, gY] = meshgrid(x, y); 176 | %gX = reshape(gX, numel(gX), 1); 177 | %gY = reshape(gY, numel(gY), 1); 178 | % only draw the z = 0 2D section 179 | gZ = 0; 180 | h = 0.5; 181 | phi = -min(min(min(min(h+gX, h-gX)), min(h+gY, h-gY)), min(h+gZ, h-gZ)); 182 | 183 | hold on; 184 | contour(gX, gY, phi, [0, 0], 'r-', 'LineWidth', 2); 185 | 186 | phi = phi .* ((gX-0.3).^2 + (gY-0.2).^2 + (gZ-0.1).^2 + 0.005); 187 | contour(gX, gY, phi, min(min(phi)):0.03:max(max(phi))); 188 | 189 | ax = gca; 190 | ax.FontSize = fontSize; 191 | end 192 | -------------------------------------------------------------------------------- /src/drectangle.m: -------------------------------------------------------------------------------- 1 | function d=drectangle(p,x1,x2,y1,y2) 2 | 3 | % Copyright (C) 2004-2012 Per-Olof Persson. See COPYRIGHT.TXT for details. 4 | 5 | d=-min(min(min(-y1+p(:,2),y2-p(:,2)),-x1+p(:,1)),x2-p(:,1)); 6 | -------------------------------------------------------------------------------- /src/dunion.m: -------------------------------------------------------------------------------- 1 | function d=dunion(d1,d2), d=min(d1,d2); 2 | 3 | % Copyright (C) 2004-2012 Per-Olof Persson. See COPYRIGHT.TXT for details. 4 | -------------------------------------------------------------------------------- /src/findContourInTriangle.m: -------------------------------------------------------------------------------- 1 | function [lineSeg1, lineSeg2] = findContourInTriangle(coordinates, phi) 2 | % coordinates*: [x, y; x, y; x, y] coordinates of 3 vertices 3 | % phi*: 3x1 value of phi on 3 vertices 4 | % output: lineSeg*: [x, y] 2 ends of the line segment 5 | 6 | if any(size(phi) ~= [3, 1]) 7 | error('size(phi) shall be 3x1'); 8 | end 9 | 10 | 11 | isEdge12Cutted= (phi(1)>=0 && phi(2) < 0) || (phi(1)<=0 && phi(2) > 0); 12 | isEdge23Cutted= (phi(2)>=0 && phi(3) < 0) || (phi(2)<=0 && phi(3) > 0); 13 | isEdge31Cutted= (phi(3)>=0 && phi(1) < 0) || (phi(3)<=0 && phi(1) > 0); 14 | 15 | 16 | if (isEdge31Cutted && isEdge23Cutted) 17 | % check on Edge 13 and 23 18 | lineSeg1 = findZeroOnEdgeWrapper(1, 3, coordinates, phi); 19 | lineSeg2 = findZeroOnEdgeWrapper(2, 3, coordinates, phi); 20 | elseif (isEdge12Cutted && isEdge23Cutted) 21 | % check on Edge 12 and 23 22 | lineSeg1 = findZeroOnEdgeWrapper(1, 2, coordinates, phi); 23 | lineSeg2 = findZeroOnEdgeWrapper(2, 3, coordinates, phi); 24 | elseif (isEdge12Cutted && isEdge31Cutted) 25 | % check on Edge 12 and 13 26 | lineSeg1 = findZeroOnEdgeWrapper(1, 2, coordinates, phi); 27 | lineSeg2 = findZeroOnEdgeWrapper(1, 3, coordinates, phi); 28 | else 29 | error('All phi have the same sign!') 30 | end 31 | 32 | end 33 | 34 | function [R] = findZeroOnEdgeWrapper(t1, t2, coordinates, phi) 35 | R = findZeroOnEdge(coordinates(t1,:),coordinates(t2,:),phi(t1),phi(t2)); 36 | end -------------------------------------------------------------------------------- /src/findZeroOnEdge.m: -------------------------------------------------------------------------------- 1 | function [Z] = findZeroOnEdge(a,b,phiA,phiB) 2 | % input: a&b: [x,y] line segment phiA/B: phi value on a and b 3 | % output: Z: [x,y] point on ab where phi=0, or NaN if there's no zero point 4 | 5 | if sign(phiA) == sign(phiB) 6 | Z=NaN; 7 | return; 8 | end 9 | 10 | Z=a+(abs(phiA)/(abs(phiA)+abs(phiB)))*(b-a); 11 | end 12 | 13 | -------------------------------------------------------------------------------- /src/generateTriangleNorms.m: -------------------------------------------------------------------------------- 1 | function [triangleOutNorms ,cornerNorm, areas] = generateTriangleNorms(p, t) 2 | % input: p, t: mesh 3 | % output: triangleOutNorm of size (nTriangles, 3, 2) 4 | % cornerNorm of size (nTriangles, 3, 2) 5 | 6 | nTriangles=size(t, 1); 7 | triangleOutNorms=zeros(nTriangles, 3, 2); 8 | cornerNorm=zeros(nTriangles, 3, 2); 9 | areas=zeros(nTriangles, 1); 10 | 11 | for idxT=1:nTriangles 12 | triangleIdxP=t(idxT,:); 13 | triangleCoordinates=p(triangleIdxP,:); 14 | 15 | % edge out norm: e12, e23, e31 16 | TON=triangleOutNorm(triangleCoordinates); 17 | triangleOutNorms(idxT, :, :)=TON; 18 | 19 | % corner norm 20 | % Nc1= e12 + e31 21 | cornerNorm(idxT, 1, :)=normr(TON(1, :) + TON(3, :)); 22 | % Nc2= e12 + e23 23 | cornerNorm(idxT, 2, :)=normr(TON(1, :) + TON(2, :)); 24 | % Nc3= e23 + e31 25 | cornerNorm(idxT, 3, :)=normr(TON(2, :) + TON(3, :)); 26 | 27 | areas(idxT, 1)=polyarea(triangleCoordinates(:,1), triangleCoordinates(:,2)); 28 | end 29 | end 30 | 31 | -------------------------------------------------------------------------------- /src/rayLineSegmentIntersection.m: -------------------------------------------------------------------------------- 1 | function [Intersetion, L, isCrossed] = rayLineSegmentIntersection(... 2 | rayOrigin, rayDirection, lineSegEnd1, lineSegEnd2) 3 | % input: [x,y] rayOrigin, rayDirection, lineSegEnd1, lineSegEnd2 4 | % output: Intersetion: [x,y] corss point 5 | % L: length from rayOrigin to Intersetion 6 | % isCrossed: true/false indicating if corss point is found 7 | 8 | rayDirection=normr(rayDirection); 9 | lineSegVector=lineSegEnd2-lineSegEnd1; 10 | segDirection=normr(lineSegVector); 11 | segLength=norm(lineSegVector); 12 | 13 | % if they are parallel 14 | if abs(dot(rayDirection,segDirection)-1)0 && st>0 && st