1 /*
2 GNU Octave level-set package.
3 Copyright (C) 2013-2014 Daniel Kraft <d@domob.eu>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /* Internal C++ implementation for mesh building. Inside and outside elements
20 are simply split in two triangles, while boundary elements are split
21 depending on how they are intersected by the boundary. For them, the
22 geom.internal data about "boundary segments" is used. */
23
24 #include <octave/oct.h>
25 #include <octave/Cell.h>
26 #include <octave/ov-struct.h>
27
28 #include <stdexcept>
29 #include <vector>
30
31 #include "Utils.hpp"
32
33 /**
34 * Type used to hold maps of indices. It needs to allow negative values, too,
35 * since they are used as "indices" of intersection points in the geometry.
36 * Furthermore, -1 is used to denote not-yet-set indices (since NA is not
37 * available as an integer value).
38 */
39 typedef std::vector<int> indexArr;
40
41 /** Type used for coordinates in space. */
42 typedef double realT;
43
44 /* ************************************************************************** */
45 /* Containers for the datas of the mesh. */
46
47 /**
48 * Represent a vertex in the list of vertices of a mesh (the mesh.p result).
49 */
50 class Vertex
51 {
52
53 private:
54
55 /** This vertex' x coordinate in space. */
56 realT x;
57 /** This vertex' y coordinate in space. */
58 realT y;
59
60 public:
61
62 /**
63 * Construct with given coordinates.
64 * @param mx x coordinate.
65 * @param my y coordinate.
66 */
Vertex(const realT mx,const realT my)67 inline Vertex (const realT mx, const realT my)
68 : x(mx), y(my)
69 {
70 // Nothing else to do.
71 }
72
73 /* Default copying. */
74 Vertex () = default;
75 Vertex (const Vertex& o) = default;
76 Vertex& operator= (const Vertex& o) = default;
77
78 /**
79 * Convert to row in the vertex matrix to be returned to Octave.
80 * @param i Index for the row to write in.
81 * @param m Matrix to write to.
82 */
83 inline void
toMatrix(const unsigned i,Matrix & m) const84 toMatrix (const unsigned i, Matrix& m) const
85 {
86 m(0, i) = x;
87 m(1, i) = y;
88 }
89
90 };
91
92 /**
93 * Represent a triangle in the list of triangles of a mesh (mesh.t field).
94 */
95 class Triangle
96 {
97
98 private:
99
100 /** Indices of the vertices. */
101 unsigned vertices[3];
102
103 public:
104
105 /**
106 * Construct with given vertices.
107 * @param a First vertex.
108 * @param b Second vertex.
109 * @param c Third vertex.
110 */
Triangle(const unsigned a,const unsigned b,const unsigned c)111 inline Triangle (const unsigned a, const unsigned b, const unsigned c)
112 : vertices{a, b, c}
113 {
114 // Nothing else to do.
115 }
116
117 /* Default copying. */
118 Triangle () = default;
119 Triangle (const Triangle& o) = default;
120 Triangle& operator= (const Triangle& o) = default;
121
122 /**
123 * Convert to row in the triangle matrix to be returned to Octave.
124 * @param i Index for the row to write in.
125 * @param m Matrix to write to.
126 */
127 inline void
toMatrix(const unsigned i,Matrix & m) const128 toMatrix (const unsigned i, Matrix& m) const
129 {
130 for (unsigned j = 0; j < 3; ++j)
131 m(j, i) = vertices[j];
132 }
133
134 };
135
136 /* ************************************************************************** */
137 /* Mesh class itself. */
138
139 /**
140 * This class represents the mesh that is being built up. It keeps track of
141 * the vertices and triangles as they are added. In addition,
142 * in order to do that, it also keeps a map between original vertex indices
143 * and indices into the already set-up list of vertices.
144 */
145 class Mesh
146 {
147
148 private:
149
150 /** The list of vertices. */
151 std::vector<Vertex> vertices;
152 /** The list of triangles. */
153 std::vector<Triangle> triangles;
154
155 /** Map ordinary grid point index to that in list of vertices. */
156 indexArr innerVertexInd;
157 /** Map intersection point index to that in list of vertices. */
158 indexArr bdryVertexInd;
159 /**
160 * Map index of vertices on the mesh to the vertex code on the geometry.
161 * I. e., may be positive or negative depending on grid point or ispt.
162 */
163 indexArr meshVertexInd;
164
165 public:
166
167 /**
168 * Construct the yet empty mesh.
169 * @param n Number of inner grid points.
170 * @param nBdry Number of intersection points.
171 */
172 Mesh (const unsigned n, const unsigned nBdry);
173
174 /* No copying. */
175 Mesh () = delete;
176 Mesh (const Mesh& o) = delete;
177 Mesh& operator= (const Mesh& o) = delete;
178
179 /**
180 * Get internal vertex index for a given grid point. Insert it into
181 * the list of vertices if necessary.
182 * @param ind Grid point index.
183 * @param coord Full coordinate array, so we know the coordinates.
184 * @return Index into internal vertex list.
185 */
186 unsigned getInnerVertex (const unsigned ind, const Matrix& coord);
187
188 /**
189 * Get internal vertex index for a given intersection point. Insert it into
190 * the list of vertices if necessary.
191 * @param ind Grid point index.
192 * @param coord Array of coordinates of boundary points.
193 * @return Index into internal vertex list.
194 */
195 unsigned getBoundaryVertex (const unsigned ind, const Matrix& coord);
196
197 /**
198 * Add a triangle with the given vertices.
199 * @param a First vertex.
200 * @param b Second vertex.
201 * @param c Third vertex.
202 */
203 inline void
addTriangle(const unsigned a,const unsigned b,const unsigned c)204 addTriangle (const unsigned a, const unsigned b, const unsigned c)
205 {
206 triangles.push_back (Triangle(a, b, c));
207 }
208
209 /**
210 * Wrap the mesh's content up into the struct expected by Octave.
211 * @param res Store the msh structure here.
212 * @param vertexMap Store the vertex map here.
213 */
214 void toStruct (octave_scalar_map& res, octave_scalar_map& vertexMap) const;
215
216 };
217
Mesh(const unsigned n,const unsigned nBdry)218 Mesh::Mesh (const unsigned n, const unsigned nBdry)
219 : vertices(), triangles(),
220 innerVertexInd(n, -1), bdryVertexInd(nBdry, -1), meshVertexInd()
221 {
222 // Nothing else to do.
223 }
224
225 unsigned
getInnerVertex(const unsigned ind,const Matrix & coord)226 Mesh::getInnerVertex (const unsigned ind, const Matrix& coord)
227 {
228 if (innerVertexInd[ind] < 0)
229 {
230 const unsigned next = vertices.size ();
231 assert (next == meshVertexInd.size ());
232
233 vertices.push_back (Vertex(coord(ind, 0), coord(ind, 1)));
234 meshVertexInd.push_back (ind + 1);
235
236 innerVertexInd[ind] = next + 1;
237 }
238
239 assert (innerVertexInd[ind] >= 0);
240 return innerVertexInd[ind];
241 }
242
243 unsigned
getBoundaryVertex(const unsigned ind,const Matrix & coord)244 Mesh::getBoundaryVertex (const unsigned ind, const Matrix& coord)
245 {
246 if (bdryVertexInd[ind] < 0)
247 {
248 const unsigned next = vertices.size ();
249 assert (next == meshVertexInd.size ());
250
251 vertices.push_back (Vertex(coord(ind, 0), coord(ind, 1)));
252 /* FIXME: Can get rid of cast? */
253 meshVertexInd.push_back (-static_cast<int> (ind) - 1);
254
255 bdryVertexInd[ind] = next + 1;
256 }
257
258 assert (bdryVertexInd[ind] >= 0);
259 return bdryVertexInd[ind];
260 }
261
262 void
toStruct(octave_scalar_map & res,octave_scalar_map & vertexMap) const263 Mesh::toStruct (octave_scalar_map& res, octave_scalar_map& vertexMap) const
264 {
265 Matrix vert(2, vertices.size ());
266 Matrix tri(3, triangles.size ());
267
268 for (unsigned i = 0; i < vertices.size (); ++i)
269 vertices[i].toMatrix (i, vert);
270 for (unsigned i = 0; i < triangles.size (); ++i)
271 triangles[i].toMatrix (i, tri);
272
273 vertexMap = octave_scalar_map ();
274 vertexMap.assign ("grid", convertToColumnVector (innerVertexInd));
275 vertexMap.assign ("ispt", convertToColumnVector (bdryVertexInd));
276 vertexMap.assign ("mesh", convertToColumnVector (meshVertexInd));
277
278 res = octave_scalar_map ();
279 res.assign ("p", vert);
280 res.assign ("t", tri);
281 /* e is filled out from geom.bedges in the m-file itself. */
282 }
283
284 /* ************************************************************************** */
285
286 /**
287 * Extract the relevant information for building up the triangles from
288 * the boundary element segments information provided by ls_find_geometry.
289 * It basically just extracts everything of interest and reorders the points.
290 * @param segs Boundary element segment info from ls_find_geometry.
291 * @param bdryPoints Return boundary points here.
292 * @param innerPts Add inner points here.
293 */
294 void
getInnerSegment(const octave_scalar_map & segs,unsigned bdryPts[2],indexArr & innerPts)295 getInnerSegment (const octave_scalar_map& segs,
296 unsigned bdryPts[2], indexArr& innerPts)
297 {
298 bdryPts[0] = segs.contents ("endPt").int_value () - 1;
299 bdryPts[1] = segs.contents ("startPt").int_value () - 1;
300
301 assert (innerPts.empty ());
302 const ColumnVector inners = segs.contents ("inners").column_vector_value ();
303 const unsigned nInners = inners.numel ();
304 for (unsigned i = 0; i < nInners; ++i)
305 innerPts.push_back (inners(nInners - i - 1) - 1);
306 }
307
308 /* Octave routine for mesh building. */
309 DEFUN_DLD (__levelset_internal_mesh, args, nargout,
310 " [MESH, VMAP] = internal_mesh (L, M, INSIDE, COORDS, ISPTCOORDS,\n"
311 " NODELIST, INELEMS, OUTELEMS,\n"
312 " BDRYELEMS, BDRYELSEGS, GLOBAL)\n\n"
313 "Internal, fast implementation of mesh-building. Returned are already\n"
314 "the mesh structure (except the 'e' field) and vertex map returned\n"
315 "from ls_build_mesh, but the arguments are already picked apart by the\n"
316 "wrapper routine.\n\n"
317 "L, M are the size of the grid, COORDS give the coordinates of grid points\n"
318 "in space and INSIDE flags which of those points are inside the domain.\n"
319 "ISPTCOORDS are the coordinates of all intersection points indexed by\n"
320 "intersection point number. GLOBAL signals that we want a global mesh\n"
321 "instead of a list of meshes for only the inner parts.\n\n"
322 "The remaining arguments contain the relevant information from the\n"
323 "geometry struct picked apart.\n")
324 {
325 try
326 {
327 if (args.length () != 11 || nargout > 2)
328 throw std::runtime_error ("invalid argument counts");
329
330 /* Get arguments in. */
331 const unsigned L = args(0).int_value ();
332 const unsigned M = args(1).int_value ();
333 const boolNDArray inside = args(2).bool_array_value ();
334 const Matrix coords = args(3).matrix_value ();
335 const Matrix isptCoords = args(4).matrix_value ();
336 const Matrix nodelist = args(5).matrix_value ();
337 const ColumnVector inElems = args(6).column_vector_value ();
338 const ColumnVector outElems = args(7).column_vector_value ();
339 const ColumnVector bdryElems = args(8).column_vector_value ();
340 const Cell bdryelSegs = args(9).cell_value ();
341 const bool global = args(10).bool_value ();
342
343 /* Check and extract dimensions. */
344 getDimension (inside, L * M, 1);
345 getDimension (coords, L * M, 2);
346 const unsigned numIntsec = getDimension (isptCoords, -1, 2);
347 getDimension (nodelist, (L - 1) * (M - 1), 4);
348 const unsigned numInElems = getDimension (inElems, -1, 1);
349 const unsigned numOutElems = getDimension (outElems, -1, 1);
350 const unsigned numBdryElems = getDimension (bdryElems, -1, 1);
351
352 /* Construct empty mesh. */
353 Mesh mesh(L * M, numIntsec);
354
355 /* Construct triangles for inner elements. */
356 for (unsigned i = 0; i < numInElems; ++i)
357 {
358 const unsigned cur = inElems(i) - 1;
359
360 unsigned vertInds[4];
361 for (unsigned j = 0; j < 4; ++j)
362 vertInds[j] = mesh.getInnerVertex (nodelist(cur, j) - 1, coords);
363
364 mesh.addTriangle (vertInds[0], vertInds[1], vertInds[2]);
365 mesh.addTriangle (vertInds[0], vertInds[2], vertInds[3]);
366 }
367
368 /* In global mode, do that also for outer elements. */
369 if (global)
370 for (unsigned i = 0; i < numOutElems; ++i)
371 {
372 const unsigned cur = outElems(i) - 1;
373
374 unsigned vertInds[4];
375 for (unsigned j = 0; j < 4; ++j)
376 vertInds[j] = mesh.getInnerVertex (nodelist(cur, j) - 1, coords);
377
378 mesh.addTriangle (vertInds[0], vertInds[1], vertInds[2]);
379 mesh.addTriangle (vertInds[0], vertInds[2], vertInds[3]);
380 }
381
382 /* Go over boundary elements and handle them. We use the information
383 provided already by ls_find_geometry internally about the segments
384 of each boundary element that belong to the domain. These need
385 to be split into triangles. */
386 for (unsigned i = 0; i < numBdryElems; ++i)
387 {
388 const unsigned cur = bdryElems(i) - 1;
389 const Cell cellSegs = bdryelSegs(i).cell_value ();
390 const unsigned nSegs = cellSegs.numel ();
391
392 std::vector<octave_scalar_map> segs;
393 indexArr endEdges;
394 for (unsigned j = 0; j < nSegs; ++j)
395 {
396 segs.push_back (cellSegs(j).scalar_map_value ());
397 int edge = segs.back ().contents ("endEdge").int_value () - 1;
398 endEdges.push_back (edge);
399 }
400
401 /* Special case is that of *two* starting points, which
402 indicates that we have a narrow pair. */
403 if (segs.size () == 2)
404 {
405 assert ((endEdges[0] + 2) % 4 == endEdges[1]);
406
407 unsigned bdryPts[4];
408 indexArr innerPts[2];
409 for (unsigned j = 0; j < 2; ++j)
410 {
411 getInnerSegment (segs[j], bdryPts + 2 * j, innerPts[j]);
412 assert (innerPts[j].size () == 1);
413
414 for (unsigned k = 0; k < 2; ++k)
415 {
416 const unsigned ind = 2 * j + k;
417 bdryPts[ind] = mesh.getBoundaryVertex (bdryPts[ind],
418 isptCoords);
419 }
420 for (auto& p : innerPts[j])
421 p = mesh.getInnerVertex (p, coords);
422
423 mesh.addTriangle (bdryPts[2 * j], innerPts[j][0],
424 bdryPts[2 * j + 1]);
425 }
426
427 if (global)
428 {
429 int outerPts[] = {endEdges[0], endEdges[1]};
430 for (auto& p : outerPts)
431 {
432 p = nodelist(cur, p) - 1;
433 assert (!inside (p));
434 p = mesh.getInnerVertex (p, coords);
435 }
436
437 mesh.addTriangle (bdryPts[1], outerPts[1], bdryPts[2]);
438 mesh.addTriangle (bdryPts[0], bdryPts[1], bdryPts[2]);
439 mesh.addTriangle (bdryPts[0], bdryPts[2], bdryPts[3]);
440 mesh.addTriangle (bdryPts[0], bdryPts[3], outerPts[0]);
441 }
442 }
443
444 /* "Ordinary" case. */
445 else
446 {
447 assert (segs.size () == 1);
448
449 /* Find whole list of points forming the segment. */
450 unsigned bdryPts[2];
451 indexArr innerPts;
452 getInnerSegment (segs[0], bdryPts, innerPts);
453 assert (!innerPts.empty ());
454
455 indexArr outerPts;
456 for (unsigned p = endEdges[0] % 4; ; p = (p + 3) % 4)
457 {
458 const unsigned pInd = nodelist (cur, p) - 1;
459 if (static_cast<int> (pInd) == innerPts.back ())
460 break;
461 assert (!inside (pInd));
462 outerPts.push_back (pInd);
463 }
464 assert (innerPts.size () + outerPts.size () == 4);
465
466 /* Add to list of vertices in the mesh and overwrite the
467 indices on the way. */
468 for (auto& p : bdryPts)
469 p = mesh.getBoundaryVertex (p, isptCoords);
470 for (auto& p : innerPts)
471 p = mesh.getInnerVertex (p, coords);
472 if (global)
473 for (auto& p : outerPts)
474 p = mesh.getInnerVertex (p, coords);
475
476 /* Build the triangles accordingly and also the list of
477 boundary edges in this case. */
478 switch (innerPts.size ())
479 {
480 case 1:
481 mesh.addTriangle (bdryPts[0], innerPts[0], bdryPts[1]);
482 if (global)
483 {
484 mesh.addTriangle (bdryPts[0], outerPts[1], outerPts[0]);
485 mesh.addTriangle (bdryPts[0], bdryPts[1], outerPts[1]);
486 mesh.addTriangle (bdryPts[1], outerPts[2], outerPts[1]);
487 }
488 break;
489 case 2:
490 mesh.addTriangle (bdryPts[0], innerPts[0], innerPts[1]);
491 mesh.addTriangle (innerPts[1], bdryPts[1], bdryPts[0]);
492 if (global)
493 {
494 mesh.addTriangle (bdryPts[1], outerPts[1], outerPts[0]);
495 mesh.addTriangle (outerPts[0], bdryPts[0], bdryPts[1]);
496 }
497 break;
498 case 3:
499 mesh.addTriangle (bdryPts[0], innerPts[0], innerPts[1]);
500 mesh.addTriangle (innerPts[1], bdryPts[1], bdryPts[0]);
501 mesh.addTriangle (innerPts[1], innerPts[2], bdryPts[1]);
502 if (global)
503 mesh.addTriangle (bdryPts[1], outerPts[0], bdryPts[0]);
504 break;
505 default:
506 assert (false);
507 }
508 }
509 }
510
511 /* Build return results. */
512 octave_scalar_map msh, vertexMap;
513 mesh.toStruct (msh, vertexMap);
514 octave_value_list res;
515 res(0) = msh;
516 res(1) = vertexMap;
517
518 return res;
519 }
520 catch (const std::runtime_error& exc)
521 {
522 error (exc.what ());
523 return octave_value_list ();
524 }
525 }
526