Skip to content

Commit

Permalink
subdivide non-manifold 2d meshes
Browse files Browse the repository at this point in the history
  • Loading branch information
unixpickle committed Jul 8, 2024
1 parent eaee4b6 commit 54520c6
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 0 deletions.
47 changes: 47 additions & 0 deletions model2d/mesh_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,53 @@ func (m *Mesh) Subdivide(iters int) *Mesh {
return current
}

// SubdividePath is like Subdivide, except that it is meant
// to be applied to meshes that include unclosed paths
// rather than closed, manifold meshes.
//
// There should be no cycles in the paths in the mesh, i.e.
// no vertices with more than two attached segments.
func (m *Mesh) SubdividePath(iters int) *Mesh {
if iters < 1 {
panic("must subdivide for at least one iteration")
}
current := m
for i := 0; i < iters; i++ {
next := NewMesh()
current.Iterate(func(s *Segment) {
len0 := len(current.Find(s[0]))
len1 := len(current.Find(s[1]))
if len0 > 2 || len1 > 2 {
panic("mesh does not consist of manifold sub-meshes or unclosed loops")
}
mp1 := s[0].Scale(0.75).Add(s[1].Scale(0.25))
mp2 := s[1].Scale(0.75).Add(s[0].Scale(0.25))
if len0 == 2 && len1 == 2 {
next.Add(&Segment{mp1, mp2})
}

if len1 == 2 {
others := current.Find(s[1])
s1 := others[0]
if s1 == s {
s1 = others[1]
}
mp3 := s1[0].Scale(0.75).Add(s1[1].Scale(0.25))
next.Add(&Segment{mp2, mp3})
if len0 == 1 {
next.Add(&Segment{s[0], mp2})
}
} else if len0 == 2 {
next.Add(&Segment{mp1, s[1]})
} else {
next.Add(&Segment{s[0], s[1]})
}
})
current = next
}
return current
}

// Manifold checks if the mesh is manifold, i.e. if every
// vertex has two segments.
func (m *Mesh) Manifold() bool {
Expand Down
26 changes: 26 additions & 0 deletions model2d/mesh_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,32 @@ func TestMeshSubdivide(t *testing.T) {
}
}

func TestMeshSubdividePath(t *testing.T) {
mesh := MarchingSquaresSearch(&Circle{Radius: 0.9}, 0.01, 8)

// Remove some segment and subdivide it separately,
// which should actually leave it unchanged.
seg := mesh.SegmentsSlice()[0]
mesh.Remove(seg)
mesh1 := mesh.SubdividePath(2)
mesh2 := NewMeshSegments([]*Segment{seg}).SubdividePath(2)
mesh1.AddMesh(mesh2)
mesh.Add(seg)

MustValidateMesh(t, mesh1, true)

sdf := MeshToSDF(mesh)
sdf1 := MeshToSDF(mesh1)
for i := 0; i < 1000; i++ {
c := NewCoordRandNorm()
s1 := sdf.SDF(c)
s2 := sdf1.SDF(c)
if math.Abs(s1-s2) > 0.01 {
t.Errorf("bad SDF at %v: expected %f but got %f", c, s1, s2)
}
}
}

func TestMeshRepair(t *testing.T) {
original := NewMesh()
for i := 0.0; i < 2*math.Pi; i += 0.1 {
Expand Down

0 comments on commit 54520c6

Please sign in to comment.