Initializing 3D Canvas...

Mesh Welding

2 min read1 page

When importing raw 3D data (like STL files from CAD or 3D scans), the geometry is often a "Polygon Soup". This means adjacent triangles don't actually share a Vertex ID. They just happen to have vertices placed at the exact same 3D coordinates.

The Seam Problem:

1. Because the faces don't share vertex indices, the Half-Edge graph sees them as completely disconnected. 2. This causes false "Holes" or "Naked Edges" everywhere. 3. The rendering engine will calculate normals independently, causing a faceted or cracked appearance along the seam.
python
1# Finding Un-welded Seams
2def analyze_mesh_seams(mesh):
3 """Finds coincident vertices that are not topologically connected."""
4
5 # In a "polygon soup" mesh (like raw STL files),
6 # two adjacent triangles might share the exact same 3D coordinate
7 # but use different vertex indices.
8
9 # 1. Build topology
10 edges = build_half_edges(mesh.faces)
11
12 # 2. Any edge without a twin is a Naked Edge.
13 # If two Naked Edges occupy the exact same physical space,
14 # that is a Seam that needs welding.
15
16 return [e for e in edges if e.twin is None]
Explode Seam
0.20
2 min read1 page

To weld vertices, we need to find which ones are very close to each other. Checking every vertex against every other vertex takes O(N²) time, which is too slow for meshes with millions of triangles.

Spatial Hashing:

1. Divide 3D space into a grid of tiny cubes (buckets). 2. Calculate which cube each vertex falls into by dividing its coordinates by the Tolerance. 3. We now only have to compare vertices that landed in the same or adjacent buckets!
python
1# Spatial Hashing for Fast Lookups
2def create_spatial_hash(vertices, cell_size=0.01):
3 """Sorts vertices into a 3D grid to avoid O(N^2) comparisons."""
4 hash_map = {}
5
6 for i, v in enumerate(vertices):
7 # Convert continuous 3D coordinate to discrete grid indices
8 ix = int(math.floor(v.x / cell_size))
9 iy = int(math.floor(v.y / cell_size))
10 iz = int(math.floor(v.z / cell_size))
11
12 key = (ix, iy, iz)
13
14 if key not in hash_map:
15 hash_map[key] = []
16
17 hash_map[key].append(i) # Store the vertex index
18
19 return hash_map
Grid Resolution
2.00
2 min read1 page

Once vertices are sorted into buckets, the algorithm iterates through each bucket. If two vertices are closer to each other than the user-defined Tolerance, they are flagged to be merged into a single vertex.

Distance Check:

1. Look inside bucket (x,y,z). 2. Measure the exact 3D Euclidean distance between every pair of vertices inside it. 3. If Distance <= Tolerance, record the index pair.
python
1# Proximity Matching
2def find_matches(hash_map, vertices, tolerance):
3 """Finds which vertices should be welded based on distance."""
4 matches = []
5
6 for bucket, indices in hash_map.items():
7 # Compare points only within the same bucket
8 for i in range(len(indices)):
9 for j in range(i + 1, len(indices)):
10 v1_idx = indices[i]
11 v2_idx = indices[j]
12
13 dist = vertices[v1_idx].distance_to(vertices[v2_idx])
14
15 if dist <= tolerance:
16 matches.append((v1_idx, v2_idx))
17
18 return matches
Weld Tolerance
0.50
2 min read1 page

When two vertices are matched, we do not physically "glue" them. Instead, we performIndex Re-mapping. We tell the triangle that was using Vertex B to now use Vertex A. Vertex B is then deleted from memory.

Topological Merge:

1. Pick a "Master" vertex (e.g. Vertex 2). 2. Pick the "Duplicate" vertex (e.g. Vertex 5). 3. Scan all triangles. If a triangle uses Vertex 5, change it to Vertex 2. 4. The two faces now mathematically share Vertex 2.
python
1# Remapping Face Indices
2def merge_vertices(mesh, matches):
3 """Updates faces to point to a single shared vertex."""
4
5 # Create a map showing which old index points to which new index
6 # e.g., index_map[5] = 2 means "vertex 5 has been merged into vertex 2"
7 index_map = build_index_map(matches)
8
9 # Go through every face, and update its indices
10 for face in mesh.faces:
11 face[0] = index_map.get(face[0], face[0])
12 face[1] = index_map.get(face[1], face[1])
13 face[2] = index_map.get(face[2], face[2])
14
15 # Delete the unused vertices from memory
16 mesh.vertices = remove_unused_vertices(mesh.vertices, index_map)
Merge Step (Separate/Remap/Weld)
0.00
2 min read1 page

A dangerous side effect of Vertex Welding is that if your Tolerance is set too high, you might merge two vertices that belong to the same triangle.

Degenerate Collapse:

1. If a triangle's vertices A and B are welded together, the triangle effectively becomes a straight line (A-A-C). 2. A triangle with zero area causes math errors (division by zero) when calculating normals. 3. The algorithm must do a final pass to delete any faces where v0 == v1 or Area is zero.
python
1# Removing Degenerate Faces
2def clean_degenerate_faces(mesh):
3 """Removes faces that collapsed into lines or points after welding."""
4 valid_faces = []
5
6 for face in mesh.faces:
7 # Check if any two indices in the triangle are exactly the same
8 if face[0] == face[1] or face[1] == face[2] or face[2] == face[0]:
9 # This triangle has collapsed! Skip it.
10 continue
11
12 # Optional: Check if the 3D area is zero
13 area = calculate_triangle_area(mesh.vertices[face[0]],
14 mesh.vertices[face[1]],
15 mesh.vertices[face[2]])
16 if area > 0.00001:
17 valid_faces.append(face)
18
19 mesh.faces = valid_faces
State (Base/Collapsed/Cleaned)
0.00
2 min read1 page

The primary visual benefit of welding a mesh is achieving smooth shading across seams. Before welding, the renderer sees two separate vertices at the seam, and assigns them hard, flat normals.

Smooth Shading:

1. Once the faces share a single vertex, we calculate the Vertex Normal. 2. A Vertex Normal is the average of all surrounding Face Normals. 3. The shader interpolates these normals across the triangle, creating the optical illusion of a perfectly smooth, curved surface.
python
1# Recalculating Normals
2def compute_vertex_normals(mesh):
3 """Averages face normals to create smooth shading."""
4
5 # Reset all vertex normals to zero
6 for v in mesh.vertices:
7 v.normal = Vector3(0,0,0)
8
9 # Add each face's normal to its vertices
10 for face in mesh.faces:
11 face_normal = calculate_face_normal(face)
12 mesh.vertices[face[0]].normal += face_normal
13 mesh.vertices[face[1]].normal += face_normal
14 mesh.vertices[face[2]].normal += face_normal
15
16 # Normalize the final sums
17 for v in mesh.vertices:
18 v.normal = v.normal.normalize()
Shading (Flat/Smooth)
0.00
2 min read1 page

In a real 3D application, you control the Weld Tolerance. If the tolerance is too small, seams remain unwelded. If it is too large, you might accidentally weld distinct features together, collapsing geometry and destroying details (a process that actually forms the basis of "Mesh Decimation").

Welding Tool:

1. Observe the seams (red lines) and faceted normals. 2. Increase the Weld Tolerance to find matching vertices across the gap. 3. The faces are merged, the seams disappear, and smooth vertex normals are recalculated.
python
1# Automated Welding Workflow
2def weld_mesh(mesh, tolerance=0.01):
3 """The complete welding pipeline."""
4
5 # 1. Spatial Hashing for performance
6 buckets = create_spatial_hash(mesh.vertices, cell_size=tolerance*2)
7
8 # 2. Find points within tolerance
9 matches = find_matches(buckets, mesh.vertices, tolerance)
10
11 # 3. Update face indices and delete duplicates
12 merge_vertices(mesh, matches)
13
14 # 4. Remove zero-area degenerate triangles
15 clean_degenerate_faces(mesh)
16
17 # 5. Recalculate topology graph and vertex normals
18 mesh.rebuild_topology()
19 compute_vertex_normals(mesh)
20
21 return mesh
Weld Tolerance
0.00
Auto-Rotate
1.00