Conversation
006ae18 to
b14367f
Compare
| case 1: lo = node_box.low.y; hi = node_box.high.y; break; | ||
| default: lo = node_box.low.z; hi = node_box.high.z; break; | ||
| } | ||
| if (hi - lo < 1e-15) continue; |
There was a problem hiding this comment.
This should be 1e-15 * lengthscale where lengthscale is some characteristic scale, e.g. the diameter of the whole objects.
Similarly, parent_area = 1e-30 * lengthscale * lengthscale.
| vector3 n = vector3_cross(e1, e2); | ||
| double len = vector3_norm(n); | ||
| m->face_areas[f] = 0.5 * len; | ||
| m->face_normals[f] = (len > 1e-30) ? vector3_scale(1.0 / len, n) : n; |
There was a problem hiding this comment.
These "magic small numbers" should be relative to a lengthscale, e.g. the diameter of the object.
Implement a new MESH primitive alongside SPHERE, CYLINDER, BLOCK, and PRISM. A mesh is defined by a vertex array and triangle index array, with a BVH (bounding volume hierarchy) for O(log N) queries. Core operations implemented: - point_in_mesh: ray casting with parity counting and deduplication - normal_to_mesh: BVH-accelerated closest-face search - get_mesh_bounding_box: from BVH root node - get_mesh_volume: divergence theorem (signed tetrahedron volumes) - intersect_line_segment_with_mesh: for subpixel smoothing integration - init_mesh: validates geometry, computes normals, fixes winding, builds BVH BVH uses SAH (surface area heuristic) binned construction with flat array layout. Ray-triangle intersection uses Moller-Trumbore algorithm. Closest-point queries use Eberly's algorithm with BVH pruning. Also adds make_mesh/make_mesh_with_center constructors, mesh copy/equal/destroy in geom-ctl-io.c, and 11 unit tests covering point-in, volume, bbox, normals, line-segment intersection, and comparison against the Block primitive.
Two fixes: 1. point_in_mesh: use 3-ray majority vote instead of a single ray. A single ray can pass through a shared mesh edge, causing the deduplication to either miss or double-count a crossing and flip the inside/outside parity. Three rays in irrational directions make it extremely unlikely that more than one hits a degenerate case at the same query point. 2. reinit_mesh: skip BVH rebuild if already initialized. Unlike blocks/cylinders, mesh uses absolute coordinates and doesn't depend on the lattice basis. geom_fix_object_ptr is called hundreds of times during meep's init_sim via geometry copies; without the guard, each call rebuilt the BVH (~150ms for 9K triangles), causing ~40s of wasted time.
- point_in_mesh: cast 1 ray first, only fall back to 3-ray majority vote if deduplication removed near-duplicate t-values (indicating an edge/vertex hit). 3x faster for the common case (no edge hits). - init_mesh: validate mesh closure by building an edge-to-face-count hash table. Every edge must be shared by exactly 2 faces. Sets is_closed=false and prints a warning for open/non-manifold meshes. point_in_mesh returns false for all points on open meshes. - Add 2 new tests: open mesh detection, closed mesh detection (13 tests total, all passing).
Test boundary edges (1 face per edge), non-manifold edges (4 faces sharing an edge), and isolated vertices (vertex unreferenced by any face). 16 tests total, all passing.
1. make_mesh_with_center: shift vertices before calling init_mesh so the BVH is only built once (was building twice when center was specified). 2. intersect_line_segment_with_mesh: determine inside/outside at the segment start from the full intersection list parity (count crossings before parameter 'a') instead of calling point_in_mesh as a fallback. Eliminates millions of extra ray casts during subpixel smoothing. ~20% faster set_epsilon on the Utah teapot (28.6s → 22.9s).
- Remove unused bvh_node_box helper function - find_closest_face: push nearer BVH child last so it's popped first, improving pruning and reducing nodes visited for normal queries - Extract mesh_triangle_vertices helper to avoid redundant vertex fetches. Add mesh_triangle_bbox_centroid to compute both in one pass during BVH binning (was fetching 3 vertices twice per face)
These files are normally auto-generated from geom.scm via gen-ctl-io (requires Guile). Including them allows building libctlgeom without Guile, which is the common case for meep's Python-only builds.
b14367f to
a1001d3
Compare
…it buffer, vertex mutation rebuild
|
Hi Prof. @stevengj, I wanted to get your preference on how to handle STL/mesh file loading on the Python side. Option A: Lightweight custom parser (zero dependencies)
Option B: Use an existing library (e.g. trimesh, meshio, or numpy-stl)
Which approach do you prefer? Also happy to hear if there are specific mesh formats beyond STL that meep users commonly work with. Thanks! |
|
Let's just have the C api in libctl, and in meep a corresponding MeshObject constructor that takes a list of triangles. Then we can just document an example about how to use an existing library to load the list of triangles from a file and pass it to the MeshObject constructor. This way we don't need to add an explicit dependency to Meep itself — the user can call whatever library they want. |
| the fast path for geom_fix_object_ptr on copied meshes (called hundreds | ||
| of times during meep's init_sim) while correctly rebuilding when vertices | ||
| are mutated post-construction. */ | ||
| if (m->bvh != NULL && mesh_vertex_hash(m) == m->vertex_hash) return; |
There was a problem hiding this comment.
I think if (m->bvh != NULL) return; is sufficient here since we provide no documented way for the caller to mutate the vertices, and none of our other functions do so. So you can get rid of all the vertex_hash stuff.
(Otherwise, a hash collision could create a bug here.)
A non-NULL bvh is sufficient to skip rebuild: there is no documented way for callers to mutate mesh vertices post-construction, and none of our internal functions do so. Hash-based detection was also unsound on collisions. Removes the mesh_vertex_hash function, the vertex_hash field, and the now-irrelevant test_vertex_mutation case.
Summary
Closes #67. Adds a new
MESHgeometric object type for importing arbitrary triangulated 3D surfaces (e.g., from STL files) into libctl's geometry system.Design
The mesh follows the same tagged-union dispatch as
SPHERE,CYLINDER,BLOCK, andPRISM. A flat-array BVH with SAH binning accelerates all queries to O(log N).All seven core operations are implemented:
point_in_meshnormal_to_meshintersect_line_segment_with_meshget_mesh_volumeget_mesh_bounding_boxdisplay_mesh_infoinit_meshMeep's subpixel smoothing works automatically. No meep C++ changes required.
API
Robustness
point_in_meshreturns false for open meshes.reinit_meshguard: skips redundant BVH rebuilds ingeom_fix_object_ptr(mesh uses absolute coordinates, not lattice-relative).Test plan
Blockprimitive: 10K random points, 0 mismatches