mesh-tool: mesht - create/inspect/edit OBJ+STL with ASCII multi-view rendering
- 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
This commit is contained in:
59
mesh-tool/mesh/measure.go
Normal file
59
mesh-tool/mesh/measure.go
Normal file
@@ -0,0 +1,59 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user