- OBJ read/write (multi-object, ngon fan-triangulation, negative indices), STL binary+ascii with auto-detect and vertex welding - info: bbox/size/area/volume + watertightness via edge manifold stats - view: z-buffered orthographic ASCII renders (front/side/top/iso/back) - transform: center/mirror/scale/rotate/translate/fit, per-object filter, winding auto-flip on negative determinant - create: box/sphere/cylinder/cone/plane/torus; merge; convert - go tests: primitive volumes vs analytic values, roundtrips, topology Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmdG9GqfSWCzts7AkDwRDh
60 lines
1.3 KiB
Go
60 lines
1.3 KiB
Go
package mesh
|
|
|
|
import "math"
|
|
|
|
// BBox returns the axis-aligned bounding box of the mesh.
|
|
func (m *Mesh) BBox() (min, max Vec3) {
|
|
if len(m.Verts) == 0 {
|
|
return Vec3{}, Vec3{}
|
|
}
|
|
min, max = m.Verts[0], m.Verts[0]
|
|
for _, v := range m.Verts[1:] {
|
|
min = min.Min(v)
|
|
max = max.Max(v)
|
|
}
|
|
return
|
|
}
|
|
|
|
// SurfaceArea sums the area of all triangles.
|
|
func (m *Mesh) SurfaceArea() float64 {
|
|
sum := 0.0
|
|
for _, t := range m.Tris {
|
|
a, b, c := m.Verts[t[0]], m.Verts[t[1]], m.Verts[t[2]]
|
|
sum += b.Sub(a).Cross(c.Sub(a)).Len() / 2
|
|
}
|
|
return sum
|
|
}
|
|
|
|
// SignedVolume computes the enclosed volume via the divergence theorem.
|
|
// Only meaningful for closed meshes; positive when windings face outward.
|
|
func (m *Mesh) SignedVolume() float64 {
|
|
sum := 0.0
|
|
for _, t := range m.Tris {
|
|
a, b, c := m.Verts[t[0]], m.Verts[t[1]], m.Verts[t[2]]
|
|
sum += a.Dot(b.Cross(c))
|
|
}
|
|
return sum / 6
|
|
}
|
|
|
|
// Volume is the absolute enclosed volume.
|
|
func (m *Mesh) Volume() float64 { return math.Abs(m.SignedVolume()) }
|
|
|
|
// SceneBBox returns the bounding box over the given meshes.
|
|
func SceneBBox(meshes []*Mesh) (min, max Vec3) {
|
|
first := true
|
|
for _, m := range meshes {
|
|
if len(m.Verts) == 0 {
|
|
continue
|
|
}
|
|
mn, mx := m.BBox()
|
|
if first {
|
|
min, max = mn, mx
|
|
first = false
|
|
} else {
|
|
min = min.Min(mn)
|
|
max = max.Max(mx)
|
|
}
|
|
}
|
|
return
|
|
}
|