diff --git a/main.go b/main.go index c658bdf..1e9d04a 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ func LoadPrimitives() error { func GetModel(conf ModelReaderConf, desiredSize MeshTypes.Vector) (*MeshTypes.Mesh, error) { var mesh *MeshTypes.Mesh - if conf.PrimitiveType == "Undefined" && conf.File != nil && conf.Filename != nil && *conf.Filename != "" { + if conf.File != nil && conf.Filename != nil && *conf.Filename != "" { filetype := filepath.Ext(*conf.Filename) switch filetype { case ".gltf", ".glb": @@ -48,15 +48,13 @@ func GetModel(conf ModelReaderConf, desiredSize MeshTypes.Vector) (*MeshTypes.Me default: return nil, fmt.Errorf("unknown model type %s", filetype) } - } else if conf.PrimitiveType != "Undefined" { + } else { if Primitives.Primitives[conf.PrimitiveType] == nil { return nil, fmt.Errorf("unknown primitive type %s", conf.PrimitiveType) } tempMesh := Primitives.Primitives[conf.PrimitiveType].Copy() mesh = &tempMesh mesh.ScaleToDimensions(&desiredSize) - } else { - return nil, fmt.Errorf("invalid ModelReader config") } return mesh, nil diff --git a/pkg/MeshTypes/vector.go b/pkg/MeshTypes/vector.go index 8f373a4..41f54cd 100644 --- a/pkg/MeshTypes/vector.go +++ b/pkg/MeshTypes/vector.go @@ -1,5 +1,26 @@ package MeshTypes +// Copyright (c) 2025 Michael Fogleman +// Portions adapted from FauxGL (https://github.com/fogleman/fauxgl) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + import "math" type Vector struct { @@ -8,6 +29,10 @@ type Vector struct { Z float64 } +func (a Vector) DivScalar(b float64) Vector { + return Vector{a.X / b, a.Y / b, a.Z / b} +} + func (a Vector) Add(b Vector) Vector { return Vector{a.X + b.X, a.Y + b.Y, a.Z + b.Z} } diff --git a/pkg/primitives/cube.go b/pkg/primitives/cube.go new file mode 100644 index 0000000..2369b06 --- /dev/null +++ b/pkg/primitives/cube.go @@ -0,0 +1,81 @@ +package Primitives + +import Types "github.com/Patch2PDF/GDTF-Mesh-Reader/pkg/MeshTypes" + +func NewCube() Types.Mesh { + v := []Types.Vector{ + {X: -1, Y: 1, Z: -1}, {X: -1, Y: 1, Z: 1}, {X: 1, Y: 1, Z: 1}, {X: 1, Y: 1, Z: -1}, + {X: -1, Y: -1, Z: -1}, {X: -1, Y: -1, Z: 1}, {X: 1, Y: -1, Z: 1}, {X: 1, Y: -1, Z: -1}, + } + mesh := Types.Mesh{ + Triangles: []*Types.Triangle{ + // top + { + V0: &Types.Vertex{Position: v[1], Normal: nil}, + V1: &Types.Vertex{Position: v[2], Normal: nil}, + V2: &Types.Vertex{Position: v[3], Normal: nil}, + }, + { + V0: &Types.Vertex{Position: v[0], Normal: nil}, + V1: &Types.Vertex{Position: v[1], Normal: nil}, + V2: &Types.Vertex{Position: v[3], Normal: nil}, + }, + // left + { + V0: &Types.Vertex{Position: v[4], Normal: nil}, + V1: &Types.Vertex{Position: v[1], Normal: nil}, + V2: &Types.Vertex{Position: v[0], Normal: nil}, + }, + { + V0: &Types.Vertex{Position: v[4], Normal: nil}, + V1: &Types.Vertex{Position: v[5], Normal: nil}, + V2: &Types.Vertex{Position: v[1], Normal: nil}, + }, + // front + { + V0: &Types.Vertex{Position: v[5], Normal: nil}, + V1: &Types.Vertex{Position: v[2], Normal: nil}, + V2: &Types.Vertex{Position: v[1], Normal: nil}, + }, + { + V0: &Types.Vertex{Position: v[5], Normal: nil}, + V1: &Types.Vertex{Position: v[6], Normal: nil}, + V2: &Types.Vertex{Position: v[2], Normal: nil}, + }, + // right + { + V0: &Types.Vertex{Position: v[7], Normal: nil}, + V1: &Types.Vertex{Position: v[3], Normal: nil}, + V2: &Types.Vertex{Position: v[2], Normal: nil}, + }, + { + V0: &Types.Vertex{Position: v[7], Normal: nil}, + V1: &Types.Vertex{Position: v[2], Normal: nil}, + V2: &Types.Vertex{Position: v[6], Normal: nil}, + }, + // back + { + V0: &Types.Vertex{Position: v[0], Normal: nil}, + V1: &Types.Vertex{Position: v[3], Normal: nil}, + V2: &Types.Vertex{Position: v[4], Normal: nil}, + }, + { + V0: &Types.Vertex{Position: v[7], Normal: nil}, + V1: &Types.Vertex{Position: v[4], Normal: nil}, + V2: &Types.Vertex{Position: v[3], Normal: nil}, + }, + // bottom + { + V0: &Types.Vertex{Position: v[6], Normal: nil}, + V1: &Types.Vertex{Position: v[5], Normal: nil}, + V2: &Types.Vertex{Position: v[4], Normal: nil}, + }, + { + V0: &Types.Vertex{Position: v[7], Normal: nil}, + V1: &Types.Vertex{Position: v[6], Normal: nil}, + V2: &Types.Vertex{Position: v[4], Normal: nil}, + }, + }, + } + return mesh +} diff --git a/pkg/primitives/cylinder.go b/pkg/primitives/cylinder.go new file mode 100644 index 0000000..5347ae1 --- /dev/null +++ b/pkg/primitives/cylinder.go @@ -0,0 +1,62 @@ +package Primitives + +// Copyright (c) 2025 Michael Fogleman +// Portions adapted from FauxGL (https://github.com/fogleman/fauxgl) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import ( + "math" + + Types "github.com/Patch2PDF/GDTF-Mesh-Reader/pkg/MeshTypes" +) + +func radians(degrees float64) float64 { + return degrees * math.Pi / 180 +} + +func NewCylinder(step int, capped bool) Types.Mesh { + var triangles []*Types.Triangle + for a0 := 0; a0 < 360; a0 += step { + a1 := (a0 + step) % 360 + r0 := radians(float64(a0)) + r1 := radians(float64(a1)) + x0 := math.Cos(r0) + y0 := math.Sin(r0) + x1 := math.Cos(r1) + y1 := math.Sin(r1) + p00 := Types.Vector{X: x0, Y: y0, Z: -0.5} + p10 := Types.Vector{X: x1, Y: y1, Z: -0.5} + p11 := Types.Vector{X: x1, Y: y1, Z: 0.5} + p01 := Types.Vector{X: x0, Y: y0, Z: 0.5} + t1 := &Types.Triangle{V0: p00.ToVertex(nil), V1: p10.ToVertex(nil), V2: p11.ToVertex(nil)} + t2 := &Types.Triangle{V0: p00.ToVertex(nil), V1: p11.ToVertex(nil), V2: p01.ToVertex(nil)} + triangles = append(triangles, t1) + triangles = append(triangles, t2) + if capped { + p0 := Types.Vector{X: 0, Y: 0, Z: -0.5} + p1 := Types.Vector{X: 0, Y: 0, Z: 0.5} + t3 := &Types.Triangle{V0: p0.ToVertex(nil), V1: p10.ToVertex(nil), V2: p00.ToVertex(nil)} + t4 := &Types.Triangle{V0: p1.ToVertex(nil), V1: p01.ToVertex(nil), V2: p11.ToVertex(nil)} + triangles = append(triangles, t3) + triangles = append(triangles, t4) + } + } + return Types.Mesh{Triangles: triangles} +} diff --git a/pkg/primitives/primitives.go b/pkg/primitives/primitives.go index 429b1c7..930f699 100644 --- a/pkg/primitives/primitives.go +++ b/pkg/primitives/primitives.go @@ -30,16 +30,30 @@ var Primitives = map[string]*Types.Mesh{} func LoadPrimitives() error { for primitiveType, path := range primitivePaths { - if path == "" { - continue - } - data, err := modelFS.ReadFile(path) - if err != nil { - return err - } - Primitives[primitiveType], err = FileHandlers.Load3DS(&data, nil) - if err != nil { - return err + switch primitiveType { + case "Cube": + mesh := NewCube() + Primitives[primitiveType] = &mesh + case "Cylinder": + mesh := NewCylinder(10, true) + Primitives[primitiveType] = &mesh + case "Sphere": + mesh := NewSphere(2) + Primitives[primitiveType] = &mesh + case "Pigtail": + Primitives[primitiveType] = &Types.Mesh{} // empty mesh + default: + if path == "" { + continue + } + data, err := modelFS.ReadFile(path) + if err != nil { + return err + } + Primitives[primitiveType], err = FileHandlers.Load3DS(&data, nil) + if err != nil { + return err + } } } return nil diff --git a/pkg/primitives/sphere.go b/pkg/primitives/sphere.go new file mode 100644 index 0000000..bf283e7 --- /dev/null +++ b/pkg/primitives/sphere.go @@ -0,0 +1,109 @@ +package Primitives + +// Copyright (c) 2025 Michael Fogleman +// Portions adapted from FauxGL (https://github.com/fogleman/fauxgl) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Types "github.com/Patch2PDF/GDTF-Mesh-Reader/pkg/MeshTypes" + +func NewSphere(detail int) Types.Mesh { + var triangles []*Types.Triangle + ico := NewIcosahedron() + for _, t := range ico.Triangles { + v1 := t.V0.Position + v2 := t.V1.Position + v3 := t.V2.Position + triangles = append(triangles, newSphereHelper(detail, v1, v2, v3)...) + } + return Types.Mesh{Triangles: triangles} +} + +func newSphereHelper(detail int, v1, v2, v3 Types.Vector) []*Types.Triangle { + if detail == 0 { + t := &Types.Triangle{ + V0: v1.ToVertex(nil), + V1: v2.ToVertex(nil), + V2: v3.ToVertex(nil), + } + return []*Types.Triangle{t} + } + var triangles []*Types.Triangle + v12 := v1.Add(v2).DivScalar(2).Normalize() + v13 := v1.Add(v3).DivScalar(2).Normalize() + v23 := v2.Add(v3).DivScalar(2).Normalize() + triangles = append(triangles, newSphereHelper(detail-1, v1, v12, v13)...) + triangles = append(triangles, newSphereHelper(detail-1, v2, v23, v12)...) + triangles = append(triangles, newSphereHelper(detail-1, v3, v13, v23)...) + triangles = append(triangles, newSphereHelper(detail-1, v12, v23, v13)...) + return triangles +} + +func NewIcosahedron() *Types.Mesh { + const a = 0.8506507174597755 + const b = 0.5257312591858783 + vertices := []Types.Vector{ + {X: -a, Y: -b, Z: 0}, + {X: -a, Y: b, Z: 0}, + {X: -b, Y: 0, Z: -a}, + {X: -b, Y: 0, Z: a}, + {X: 0, Y: -a, Z: -b}, + {X: 0, Y: -a, Z: b}, + {X: 0, Y: a, Z: -b}, + {X: 0, Y: a, Z: b}, + {X: b, Y: 0, Z: -a}, + {X: b, Y: 0, Z: a}, + {X: a, Y: -b, Z: 0}, + {X: a, Y: b, Z: 0}, + } + indices := [][3]int{ + {0, 3, 1}, + {1, 3, 7}, + {2, 0, 1}, + {2, 1, 6}, + {4, 0, 2}, + {4, 5, 0}, + {5, 3, 0}, + {6, 1, 7}, + {6, 7, 11}, + {7, 3, 9}, + {8, 2, 6}, + {8, 4, 2}, + {8, 6, 11}, + {8, 10, 4}, + {8, 11, 10}, + {9, 3, 5}, + {10, 5, 4}, + {10, 9, 5}, + {11, 7, 9}, + {11, 9, 10}, + } + triangles := make([]*Types.Triangle, len(indices)) + for i, idx := range indices { + p1 := vertices[idx[0]] + p2 := vertices[idx[1]] + p3 := vertices[idx[2]] + triangles[i] = &Types.Triangle{ + V0: p1.ToVertex(nil), + V1: p2.ToVertex(nil), + V2: p3.ToVertex(nil), + } + } + return &Types.Mesh{Triangles: triangles} +} diff --git a/tests/MeshTypes/vector_test.go b/tests/MeshTypes/vector_test.go index cc74691..b8b2306 100644 --- a/tests/MeshTypes/vector_test.go +++ b/tests/MeshTypes/vector_test.go @@ -7,6 +7,19 @@ import ( "github.com/Patch2PDF/GDTF-Mesh-Reader/pkg/MeshTypes" ) +func TestVectorDivScalar(t *testing.T) { + a := MeshTypes.Vector{ + X: 2, Y: 4, Z: 6, + } + b := 2.0 + want := MeshTypes.Vector{ + X: 1, Y: 2, Z: 3, + } + if !reflect.DeepEqual(a.DivScalar(b), want) { + t.Error("Vector DivScalar() returned value does not match expected value") + } +} + func TestVectorAdd(t *testing.T) { a := MeshTypes.Vector{ X: 1, Y: 2, Z: 3,