Skip to content

Commit 46ae361

Browse files
authored
feat: Primitive Shapes for Cube, Cylinder and Sphere (inspired by fauxgl)
Primitive shapes
2 parents 9930be2 + 17fa194 commit 46ae361

7 files changed

Lines changed: 316 additions & 14 deletions

File tree

main.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func LoadPrimitives() error {
2727
func GetModel(conf ModelReaderConf, desiredSize MeshTypes.Vector) (*MeshTypes.Mesh, error) {
2828
var mesh *MeshTypes.Mesh
2929

30-
if conf.PrimitiveType == "Undefined" && conf.File != nil && conf.Filename != nil && *conf.Filename != "" {
30+
if conf.File != nil && conf.Filename != nil && *conf.Filename != "" {
3131
filetype := filepath.Ext(*conf.Filename)
3232
switch filetype {
3333
case ".gltf", ".glb":
@@ -48,15 +48,13 @@ func GetModel(conf ModelReaderConf, desiredSize MeshTypes.Vector) (*MeshTypes.Me
4848
default:
4949
return nil, fmt.Errorf("unknown model type %s", filetype)
5050
}
51-
} else if conf.PrimitiveType != "Undefined" {
51+
} else {
5252
if Primitives.Primitives[conf.PrimitiveType] == nil {
5353
return nil, fmt.Errorf("unknown primitive type %s", conf.PrimitiveType)
5454
}
5555
tempMesh := Primitives.Primitives[conf.PrimitiveType].Copy()
5656
mesh = &tempMesh
5757
mesh.ScaleToDimensions(&desiredSize)
58-
} else {
59-
return nil, fmt.Errorf("invalid ModelReader config")
6058
}
6159

6260
return mesh, nil

pkg/MeshTypes/vector.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
package MeshTypes
22

3+
// Copyright (c) 2025 Michael Fogleman
4+
// Portions adapted from FauxGL (https://github.com/fogleman/fauxgl)
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the “Software”), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
324
import "math"
425

526
type Vector struct {
@@ -8,6 +29,10 @@ type Vector struct {
829
Z float64
930
}
1031

32+
func (a Vector) DivScalar(b float64) Vector {
33+
return Vector{a.X / b, a.Y / b, a.Z / b}
34+
}
35+
1136
func (a Vector) Add(b Vector) Vector {
1237
return Vector{a.X + b.X, a.Y + b.Y, a.Z + b.Z}
1338
}

pkg/primitives/cube.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package Primitives
2+
3+
import Types "github.com/Patch2PDF/GDTF-Mesh-Reader/pkg/MeshTypes"
4+
5+
func NewCube() Types.Mesh {
6+
v := []Types.Vector{
7+
{X: -1, Y: 1, Z: -1}, {X: -1, Y: 1, Z: 1}, {X: 1, Y: 1, Z: 1}, {X: 1, Y: 1, Z: -1},
8+
{X: -1, Y: -1, Z: -1}, {X: -1, Y: -1, Z: 1}, {X: 1, Y: -1, Z: 1}, {X: 1, Y: -1, Z: -1},
9+
}
10+
mesh := Types.Mesh{
11+
Triangles: []*Types.Triangle{
12+
// top
13+
{
14+
V0: &Types.Vertex{Position: v[1], Normal: nil},
15+
V1: &Types.Vertex{Position: v[2], Normal: nil},
16+
V2: &Types.Vertex{Position: v[3], Normal: nil},
17+
},
18+
{
19+
V0: &Types.Vertex{Position: v[0], Normal: nil},
20+
V1: &Types.Vertex{Position: v[1], Normal: nil},
21+
V2: &Types.Vertex{Position: v[3], Normal: nil},
22+
},
23+
// left
24+
{
25+
V0: &Types.Vertex{Position: v[4], Normal: nil},
26+
V1: &Types.Vertex{Position: v[1], Normal: nil},
27+
V2: &Types.Vertex{Position: v[0], Normal: nil},
28+
},
29+
{
30+
V0: &Types.Vertex{Position: v[4], Normal: nil},
31+
V1: &Types.Vertex{Position: v[5], Normal: nil},
32+
V2: &Types.Vertex{Position: v[1], Normal: nil},
33+
},
34+
// front
35+
{
36+
V0: &Types.Vertex{Position: v[5], Normal: nil},
37+
V1: &Types.Vertex{Position: v[2], Normal: nil},
38+
V2: &Types.Vertex{Position: v[1], Normal: nil},
39+
},
40+
{
41+
V0: &Types.Vertex{Position: v[5], Normal: nil},
42+
V1: &Types.Vertex{Position: v[6], Normal: nil},
43+
V2: &Types.Vertex{Position: v[2], Normal: nil},
44+
},
45+
// right
46+
{
47+
V0: &Types.Vertex{Position: v[7], Normal: nil},
48+
V1: &Types.Vertex{Position: v[3], Normal: nil},
49+
V2: &Types.Vertex{Position: v[2], Normal: nil},
50+
},
51+
{
52+
V0: &Types.Vertex{Position: v[7], Normal: nil},
53+
V1: &Types.Vertex{Position: v[2], Normal: nil},
54+
V2: &Types.Vertex{Position: v[6], Normal: nil},
55+
},
56+
// back
57+
{
58+
V0: &Types.Vertex{Position: v[0], Normal: nil},
59+
V1: &Types.Vertex{Position: v[3], Normal: nil},
60+
V2: &Types.Vertex{Position: v[4], Normal: nil},
61+
},
62+
{
63+
V0: &Types.Vertex{Position: v[7], Normal: nil},
64+
V1: &Types.Vertex{Position: v[4], Normal: nil},
65+
V2: &Types.Vertex{Position: v[3], Normal: nil},
66+
},
67+
// bottom
68+
{
69+
V0: &Types.Vertex{Position: v[6], Normal: nil},
70+
V1: &Types.Vertex{Position: v[5], Normal: nil},
71+
V2: &Types.Vertex{Position: v[4], Normal: nil},
72+
},
73+
{
74+
V0: &Types.Vertex{Position: v[7], Normal: nil},
75+
V1: &Types.Vertex{Position: v[6], Normal: nil},
76+
V2: &Types.Vertex{Position: v[4], Normal: nil},
77+
},
78+
},
79+
}
80+
return mesh
81+
}

pkg/primitives/cylinder.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package Primitives
2+
3+
// Copyright (c) 2025 Michael Fogleman
4+
// Portions adapted from FauxGL (https://github.com/fogleman/fauxgl)
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the “Software”), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
24+
import (
25+
"math"
26+
27+
Types "github.com/Patch2PDF/GDTF-Mesh-Reader/pkg/MeshTypes"
28+
)
29+
30+
func radians(degrees float64) float64 {
31+
return degrees * math.Pi / 180
32+
}
33+
34+
func NewCylinder(step int, capped bool) Types.Mesh {
35+
var triangles []*Types.Triangle
36+
for a0 := 0; a0 < 360; a0 += step {
37+
a1 := (a0 + step) % 360
38+
r0 := radians(float64(a0))
39+
r1 := radians(float64(a1))
40+
x0 := math.Cos(r0)
41+
y0 := math.Sin(r0)
42+
x1 := math.Cos(r1)
43+
y1 := math.Sin(r1)
44+
p00 := Types.Vector{X: x0, Y: y0, Z: -0.5}
45+
p10 := Types.Vector{X: x1, Y: y1, Z: -0.5}
46+
p11 := Types.Vector{X: x1, Y: y1, Z: 0.5}
47+
p01 := Types.Vector{X: x0, Y: y0, Z: 0.5}
48+
t1 := &Types.Triangle{V0: p00.ToVertex(nil), V1: p10.ToVertex(nil), V2: p11.ToVertex(nil)}
49+
t2 := &Types.Triangle{V0: p00.ToVertex(nil), V1: p11.ToVertex(nil), V2: p01.ToVertex(nil)}
50+
triangles = append(triangles, t1)
51+
triangles = append(triangles, t2)
52+
if capped {
53+
p0 := Types.Vector{X: 0, Y: 0, Z: -0.5}
54+
p1 := Types.Vector{X: 0, Y: 0, Z: 0.5}
55+
t3 := &Types.Triangle{V0: p0.ToVertex(nil), V1: p10.ToVertex(nil), V2: p00.ToVertex(nil)}
56+
t4 := &Types.Triangle{V0: p1.ToVertex(nil), V1: p01.ToVertex(nil), V2: p11.ToVertex(nil)}
57+
triangles = append(triangles, t3)
58+
triangles = append(triangles, t4)
59+
}
60+
}
61+
return Types.Mesh{Triangles: triangles}
62+
}

pkg/primitives/primitives.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,30 @@ var Primitives = map[string]*Types.Mesh{}
3030

3131
func LoadPrimitives() error {
3232
for primitiveType, path := range primitivePaths {
33-
if path == "" {
34-
continue
35-
}
36-
data, err := modelFS.ReadFile(path)
37-
if err != nil {
38-
return err
39-
}
40-
Primitives[primitiveType], err = FileHandlers.Load3DS(&data, nil)
41-
if err != nil {
42-
return err
33+
switch primitiveType {
34+
case "Cube":
35+
mesh := NewCube()
36+
Primitives[primitiveType] = &mesh
37+
case "Cylinder":
38+
mesh := NewCylinder(10, true)
39+
Primitives[primitiveType] = &mesh
40+
case "Sphere":
41+
mesh := NewSphere(2)
42+
Primitives[primitiveType] = &mesh
43+
case "Pigtail":
44+
Primitives[primitiveType] = &Types.Mesh{} // empty mesh
45+
default:
46+
if path == "" {
47+
continue
48+
}
49+
data, err := modelFS.ReadFile(path)
50+
if err != nil {
51+
return err
52+
}
53+
Primitives[primitiveType], err = FileHandlers.Load3DS(&data, nil)
54+
if err != nil {
55+
return err
56+
}
4357
}
4458
}
4559
return nil

pkg/primitives/sphere.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package Primitives
2+
3+
// Copyright (c) 2025 Michael Fogleman
4+
// Portions adapted from FauxGL (https://github.com/fogleman/fauxgl)
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the “Software”), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
24+
import Types "github.com/Patch2PDF/GDTF-Mesh-Reader/pkg/MeshTypes"
25+
26+
func NewSphere(detail int) Types.Mesh {
27+
var triangles []*Types.Triangle
28+
ico := NewIcosahedron()
29+
for _, t := range ico.Triangles {
30+
v1 := t.V0.Position
31+
v2 := t.V1.Position
32+
v3 := t.V2.Position
33+
triangles = append(triangles, newSphereHelper(detail, v1, v2, v3)...)
34+
}
35+
return Types.Mesh{Triangles: triangles}
36+
}
37+
38+
func newSphereHelper(detail int, v1, v2, v3 Types.Vector) []*Types.Triangle {
39+
if detail == 0 {
40+
t := &Types.Triangle{
41+
V0: v1.ToVertex(nil),
42+
V1: v2.ToVertex(nil),
43+
V2: v3.ToVertex(nil),
44+
}
45+
return []*Types.Triangle{t}
46+
}
47+
var triangles []*Types.Triangle
48+
v12 := v1.Add(v2).DivScalar(2).Normalize()
49+
v13 := v1.Add(v3).DivScalar(2).Normalize()
50+
v23 := v2.Add(v3).DivScalar(2).Normalize()
51+
triangles = append(triangles, newSphereHelper(detail-1, v1, v12, v13)...)
52+
triangles = append(triangles, newSphereHelper(detail-1, v2, v23, v12)...)
53+
triangles = append(triangles, newSphereHelper(detail-1, v3, v13, v23)...)
54+
triangles = append(triangles, newSphereHelper(detail-1, v12, v23, v13)...)
55+
return triangles
56+
}
57+
58+
func NewIcosahedron() *Types.Mesh {
59+
const a = 0.8506507174597755
60+
const b = 0.5257312591858783
61+
vertices := []Types.Vector{
62+
{X: -a, Y: -b, Z: 0},
63+
{X: -a, Y: b, Z: 0},
64+
{X: -b, Y: 0, Z: -a},
65+
{X: -b, Y: 0, Z: a},
66+
{X: 0, Y: -a, Z: -b},
67+
{X: 0, Y: -a, Z: b},
68+
{X: 0, Y: a, Z: -b},
69+
{X: 0, Y: a, Z: b},
70+
{X: b, Y: 0, Z: -a},
71+
{X: b, Y: 0, Z: a},
72+
{X: a, Y: -b, Z: 0},
73+
{X: a, Y: b, Z: 0},
74+
}
75+
indices := [][3]int{
76+
{0, 3, 1},
77+
{1, 3, 7},
78+
{2, 0, 1},
79+
{2, 1, 6},
80+
{4, 0, 2},
81+
{4, 5, 0},
82+
{5, 3, 0},
83+
{6, 1, 7},
84+
{6, 7, 11},
85+
{7, 3, 9},
86+
{8, 2, 6},
87+
{8, 4, 2},
88+
{8, 6, 11},
89+
{8, 10, 4},
90+
{8, 11, 10},
91+
{9, 3, 5},
92+
{10, 5, 4},
93+
{10, 9, 5},
94+
{11, 7, 9},
95+
{11, 9, 10},
96+
}
97+
triangles := make([]*Types.Triangle, len(indices))
98+
for i, idx := range indices {
99+
p1 := vertices[idx[0]]
100+
p2 := vertices[idx[1]]
101+
p3 := vertices[idx[2]]
102+
triangles[i] = &Types.Triangle{
103+
V0: p1.ToVertex(nil),
104+
V1: p2.ToVertex(nil),
105+
V2: p3.ToVertex(nil),
106+
}
107+
}
108+
return &Types.Mesh{Triangles: triangles}
109+
}

tests/MeshTypes/vector_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ import (
77
"github.com/Patch2PDF/GDTF-Mesh-Reader/pkg/MeshTypes"
88
)
99

10+
func TestVectorDivScalar(t *testing.T) {
11+
a := MeshTypes.Vector{
12+
X: 2, Y: 4, Z: 6,
13+
}
14+
b := 2.0
15+
want := MeshTypes.Vector{
16+
X: 1, Y: 2, Z: 3,
17+
}
18+
if !reflect.DeepEqual(a.DivScalar(b), want) {
19+
t.Error("Vector DivScalar() returned value does not match expected value")
20+
}
21+
}
22+
1023
func TestVectorAdd(t *testing.T) {
1124
a := MeshTypes.Vector{
1225
X: 1, Y: 2, Z: 3,

0 commit comments

Comments
 (0)