Eliminating Non-Manifold LLM Meshes in Blender bpy and Three.js r170+
Unassisted LLMs generate 3D meshes with open boundary seams, inverted winding normals, and self-intersecting lathe profiles. Here is the exact half-edge manifold gate Baltor runs before exporting .glb assets.
When asked to create a fluted architectural column, a procedural ceramic vessel, or an articulated character mesh in Blender bpy or Three.js r170+, unassisted language models fail in two predictable ways. Either they emit a smooth cylinder with a 2D noise texture and call it "3D surface relief," or they emit raw vertex arrays with open seam edges (valence != 2), inverted face winding, and degenerate zero-area triangles at the poles. By placing a deterministic half-edge manifold verifier and parametric lathe generator inside the Baltor harness (assets.baltor.ai and compare.baltor.ai/?mode=relief), 100% of promoted meshes satisfy watertight 2-manifold invariants before rendering or .glb export.
Three geometric invariants every procedural mesh must pass
A triangle mesh M = (V, E, F) is a closed, orientable 2-manifold only if it satisfies three topological and geometric checks after spatial vertex welding (epsilon = 1e-5):
- Edge valence of exactly 2: Every undirected edge must be shared by exactly two triangles. If an edge belongs to only one triangle (
valence == 1), the mesh has a hole or unwelded UV seam. If an edge belongs to three or more triangles (valence >= 3), the mesh has internal non-manifold fins that break boolean modifiers and shadow maps. - Consistent counter-clockwise (CCW) half-edge orientation: If triangle
Atraverses edge(u, v), the adjacent triangleBmust traverse the opposite directed half-edge(v, u). If both traverse(u, v), their surface normals point in opposite directions, producing black shading bands under PBR lighting. - Non-degenerate triangle area and outward signed volume: Every triangle must have cross-product area
0.5 * ||(B - A) x (C - A)|| > 1e-8, and the total signed volume summed overdot(A, cross(B, C)) / 6.0must be strictly positive.
// Half-Edge Manifold & Winding Verifier for Three.js BufferGeometry
export function verifyWatertightManifold(positions, indices) {
const directedEdges = new Map();
const undirectedCounts = new Map();
let degenerateFaces = 0;
let signedVolume6 = 0;
for (let i = 0; i < indices.length; i += 3) {
const a = indices[i], b = indices[i + 1], c = indices[i + 2];
const ax = positions[a*3], ay = positions[a*3+1], az = positions[a*3+2];
const bx = positions[b*3], by = positions[b*3+1], bz = positions[b*3+2];
const cx = positions[c*3], cy = positions[c*3+1], cz = positions[c*3+2];
const abx = bx - ax, aby = by - ay, abz = bz - az;
const acx = cx - ax, acy = cy - ay, acz = cz - az;
const nx = aby * acz - abz * acy;
const ny = abz * acx - abx * acz;
const nz = abx * acy - aby * acx;
if (Math.hypot(nx, ny, nz) < 1e-8) degenerateFaces++;
signedVolume6 += ax * (by * cz - bz * cy) + ay * (bz * cx - bx * cz) + az * (bx * cy - by * cx);
for (const [u, v] of [[a, b], [b, c], [c, a]]) {
const dirKey = `${u}:${v}`;
directedEdges.set(dirKey, (directedEdges.get(dirKey) || 0) + 1);
const undirKey = u < v ? `${u}:${v}` : `${v}:${u}`;
undirectedCounts.set(undirKey, (undirectedCounts.get(undirKey) || 0) + 1);
}
}
let boundaryEdges = 0, nonManifoldEdges = 0, windingConflicts = 0;
for (const [edge, count] of undirectedCounts.entries()) {
if (count === 1) boundaryEdges++;
else if (count > 2) nonManifoldEdges++;
const [u, v] = edge.split(":");
if (directedEdges.get(`${u}:${v}`) !== 1 || directedEdges.get(`${v}:${u}`) !== 1) {
windingConflicts++;
}
}
return {
isManifold: boundaryEdges === 0 && nonManifoldEdges === 0 && windingConflicts === 0 && degenerateFaces === 0 && signedVolume6 > 0,
boundaryEdges,
nonManifoldEdges,
windingConflicts,
degenerateFaces,
signedVolume: signedVolume6 / 6.0
};
}
Inspecting true relief vs. bump illusions with a 3x Loupe
On compare.baltor.ai/?mode=relief, you can hover the 3x magnification loupe across the vessel silhouette. The unassisted model's cylinder silhouette stays razor-flat because its fluting is only a fragment shader perturbation. The Baltor-verified mesh displaces radial vertices r(theta, v) along continuous harmonic flutes and welds the polar caps cleanly, casting true self-shadowing across the grazing rim light.
| Navigation | Post Title | Direct Links |
|---|---|---|
| Previous Post | Why Every Baltor Capability Starts as a < 2 KB Contract Card Before Loading Source | HTML · Markdown |
| Next Post | Building Playable Godot 4 and WebGL Games That Survive Revision | HTML · Markdown |
| Blog Index & Feeds | Return to Baltor Engineering Blog Index (8 Posts) | index.json · feed.xml |