Files
claude 62065f237b 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
2026-07-14 02:38:29 +02:00

123 lines
2.6 KiB
Go

package mesh
import "math"
// Vec3 is a point or direction in 3D space.
type Vec3 struct{ X, Y, Z float64 }
func (a Vec3) Add(b Vec3) Vec3 { return Vec3{a.X + b.X, a.Y + b.Y, a.Z + b.Z} }
func (a Vec3) Sub(b Vec3) Vec3 { return Vec3{a.X - b.X, a.Y - b.Y, a.Z - b.Z} }
func (a Vec3) Mul(s float64) Vec3 { return Vec3{a.X * s, a.Y * s, a.Z * s} }
func (a Vec3) Dot(b Vec3) float64 { return a.X*b.X + a.Y*b.Y + a.Z*b.Z }
func (a Vec3) Len() float64 { return math.Sqrt(a.Dot(a)) }
func (a Vec3) Cross(b Vec3) Vec3 {
return Vec3{
a.Y*b.Z - a.Z*b.Y,
a.Z*b.X - a.X*b.Z,
a.X*b.Y - a.Y*b.X,
}
}
// Norm returns the unit vector, or the zero vector for zero-length input.
func (a Vec3) Norm() Vec3 {
l := a.Len()
if l == 0 {
return Vec3{}
}
return a.Mul(1 / l)
}
// Min/Max return the component-wise minimum/maximum.
func (a Vec3) Min(b Vec3) Vec3 {
return Vec3{math.Min(a.X, b.X), math.Min(a.Y, b.Y), math.Min(a.Z, b.Z)}
}
func (a Vec3) Max(b Vec3) Vec3 {
return Vec3{math.Max(a.X, b.X), math.Max(a.Y, b.Y), math.Max(a.Z, b.Z)}
}
// Mat4 is a row-major 4x4 transform matrix.
type Mat4 [16]float64
func Identity() Mat4 {
return Mat4{
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
}
}
// Mul returns m * n (n is applied first when transforming points).
func (m Mat4) Mul(n Mat4) Mat4 {
var r Mat4
for row := 0; row < 4; row++ {
for col := 0; col < 4; col++ {
sum := 0.0
for k := 0; k < 4; k++ {
sum += m[row*4+k] * n[k*4+col]
}
r[row*4+col] = sum
}
}
return r
}
// Apply transforms a point (w = 1).
func (m Mat4) Apply(v Vec3) Vec3 {
return Vec3{
m[0]*v.X + m[1]*v.Y + m[2]*v.Z + m[3],
m[4]*v.X + m[5]*v.Y + m[6]*v.Z + m[7],
m[8]*v.X + m[9]*v.Y + m[10]*v.Z + m[11],
}
}
// Det3 is the determinant of the upper-left 3x3. Negative means the
// transform mirrors space, which flips triangle winding.
func (m Mat4) Det3() float64 {
return m[0]*(m[5]*m[10]-m[6]*m[9]) -
m[1]*(m[4]*m[10]-m[6]*m[8]) +
m[2]*(m[4]*m[9]-m[5]*m[8])
}
func Translate(t Vec3) Mat4 {
m := Identity()
m[3], m[7], m[11] = t.X, t.Y, t.Z
return m
}
func ScaleXYZ(s Vec3) Mat4 {
m := Identity()
m[0], m[5], m[10] = s.X, s.Y, s.Z
return m
}
func RotateX(deg float64) Mat4 {
s, c := math.Sincos(deg * math.Pi / 180)
return Mat4{
1, 0, 0, 0,
0, c, -s, 0,
0, s, c, 0,
0, 0, 0, 1,
}
}
func RotateY(deg float64) Mat4 {
s, c := math.Sincos(deg * math.Pi / 180)
return Mat4{
c, 0, s, 0,
0, 1, 0, 0,
-s, 0, c, 0,
0, 0, 0, 1,
}
}
func RotateZ(deg float64) Mat4 {
s, c := math.Sincos(deg * math.Pi / 180)
return Mat4{
c, -s, 0, 0,
s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
}
}