package mesh import "math" // All primitives are centered at the origin with +Y up and get outward // (counter-clockwise) winding; ensureOutward fixes the global // orientation via the signed volume as a safety net. func ensureOutward(m *Mesh) *Mesh { if m.SignedVolume() < 0 { m.FlipWinding() } return m } // Box builds an axis-aligned box of the given size. func Box(size Vec3) *Mesh { x, y, z := size.X/2, size.Y/2, size.Z/2 m := &Mesh{ Name: "box", Verts: []Vec3{ {-x, -y, -z}, {x, -y, -z}, {x, y, -z}, {-x, y, -z}, // back (z-) {-x, -y, z}, {x, -y, z}, {x, y, z}, {-x, y, z}, // front (z+) }, } quads := [][4]int{ {0, 3, 2, 1}, // back {4, 5, 6, 7}, // front {0, 1, 5, 4}, // bottom {2, 3, 7, 6}, // top {1, 2, 6, 5}, // right {0, 4, 7, 3}, // left } for _, q := range quads { m.Tris = append(m.Tris, Triangle{q[0], q[1], q[2]}, Triangle{q[0], q[2], q[3]}) } return ensureOutward(m) } // Plane builds a flat rectangle in the XZ plane (an open mesh). func Plane(w, d float64) *Mesh { x, z := w/2, d/2 return &Mesh{ Name: "plane", Verts: []Vec3{{-x, 0, -z}, {x, 0, -z}, {x, 0, z}, {-x, 0, z}}, Tris: []Triangle{{0, 2, 1}, {0, 3, 2}}, // +Y facing } } // Sphere builds a UV sphere. segments = around the equator (>= 3), // rings = from pole to pole (>= 2). func Sphere(r float64, segments, rings int) *Mesh { if segments < 3 { segments = 3 } if rings < 2 { rings = 2 } m := &Mesh{Name: "sphere"} top := len(m.Verts) m.Verts = append(m.Verts, Vec3{0, r, 0}) // interior rings, top to bottom ringStart := make([]int, rings) for i := 1; i < rings; i++ { theta := math.Pi * float64(i) / float64(rings) y := r * math.Cos(theta) rad := r * math.Sin(theta) ringStart[i] = len(m.Verts) for j := 0; j < segments; j++ { phi := 2 * math.Pi * float64(j) / float64(segments) m.Verts = append(m.Verts, Vec3{rad * math.Cos(phi), y, rad * math.Sin(phi)}) } } bottom := len(m.Verts) m.Verts = append(m.Verts, Vec3{0, -r, 0}) at := func(ring, seg int) int { return ringStart[ring] + seg%segments } for j := 0; j < segments; j++ { m.Tris = append(m.Tris, Triangle{top, at(1, j), at(1, j+1)}) // top cap m.Tris = append(m.Tris, Triangle{bottom, at(rings-1, j+1), at(rings-1, j)}) } for i := 1; i < rings-1; i++ { for j := 0; j < segments; j++ { a, b := at(i, j), at(i, j+1) c, d := at(i+1, j+1), at(i+1, j) m.Tris = append(m.Tris, Triangle{a, b, c}, Triangle{a, c, d}) } } return ensureOutward(m) } // Cylinder builds a closed cylinder of height h around the Y axis. func Cylinder(r, h float64, segments int) *Mesh { if segments < 3 { segments = 3 } m := &Mesh{Name: "cylinder"} y := h / 2 topC := len(m.Verts) m.Verts = append(m.Verts, Vec3{0, y, 0}) botC := len(m.Verts) m.Verts = append(m.Verts, Vec3{0, -y, 0}) topStart := len(m.Verts) for j := 0; j < segments; j++ { phi := 2 * math.Pi * float64(j) / float64(segments) m.Verts = append(m.Verts, Vec3{r * math.Cos(phi), y, r * math.Sin(phi)}) } botStart := len(m.Verts) for j := 0; j < segments; j++ { phi := 2 * math.Pi * float64(j) / float64(segments) m.Verts = append(m.Verts, Vec3{r * math.Cos(phi), -y, r * math.Sin(phi)}) } t := func(j int) int { return topStart + j%segments } b := func(j int) int { return botStart + j%segments } for j := 0; j < segments; j++ { m.Tris = append(m.Tris, Triangle{topC, t(j + 1), t(j)}, // top cap Triangle{botC, b(j), b(j + 1)}, // bottom cap Triangle{t(j), t(j + 1), b(j + 1)}, // side Triangle{t(j), b(j + 1), b(j)}, // side ) } return ensureOutward(m) } // Cone builds a closed cone with its base at -h/2 and apex at +h/2. func Cone(r, h float64, segments int) *Mesh { if segments < 3 { segments = 3 } m := &Mesh{Name: "cone"} apex := len(m.Verts) m.Verts = append(m.Verts, Vec3{0, h / 2, 0}) baseC := len(m.Verts) m.Verts = append(m.Verts, Vec3{0, -h / 2, 0}) start := len(m.Verts) for j := 0; j < segments; j++ { phi := 2 * math.Pi * float64(j) / float64(segments) m.Verts = append(m.Verts, Vec3{r * math.Cos(phi), -h / 2, r * math.Sin(phi)}) } at := func(j int) int { return start + j%segments } for j := 0; j < segments; j++ { m.Tris = append(m.Tris, Triangle{apex, at(j + 1), at(j)}, Triangle{baseC, at(j), at(j + 1)}, ) } return ensureOutward(m) } // Torus builds a torus around the Y axis: ring radius R (center of tube // to center of torus) and tube radius r. func Torus(R, r float64, segments, rings int) *Mesh { if segments < 3 { segments = 3 } if rings < 3 { rings = 3 } m := &Mesh{Name: "torus"} for i := 0; i < segments; i++ { // around the main ring phi := 2 * math.Pi * float64(i) / float64(segments) cx, cz := math.Cos(phi), math.Sin(phi) for j := 0; j < rings; j++ { // around the tube theta := 2 * math.Pi * float64(j) / float64(rings) rad := R + r*math.Cos(theta) m.Verts = append(m.Verts, Vec3{rad * cx, r * math.Sin(theta), rad * cz}) } } at := func(i, j int) int { return (i%segments)*rings + j%rings } for i := 0; i < segments; i++ { for j := 0; j < rings; j++ { a, b := at(i, j), at(i+1, j) c, d := at(i+1, j+1), at(i, j+1) m.Tris = append(m.Tris, Triangle{a, b, c}, Triangle{a, c, d}) } } return ensureOutward(m) }