Skip to content

Commit 6532c76

Browse files
authored
feat(gltf): node hierarchy + non matrix transformations
Gltf enhancements
2 parents ecfef94 + b4f4dad commit 6532c76

1 file changed

Lines changed: 106 additions & 35 deletions

File tree

pkg/file_handlers/gltf.go

Lines changed: 106 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ import (
1010
"github.com/qmuntal/gltf"
1111
)
1212

13+
type gltfNode struct {
14+
matrix Types.Matrix
15+
mesh *gltf.Mesh
16+
}
17+
18+
var conversion = Types.Matrix{ // coordinate system conversion matrix
19+
X00: 1, X01: 0, X02: 0, X03: 0,
20+
X10: 0, X11: 0, X12: -1, X13: 0,
21+
X20: 0, X21: 1, X22: 0, X23: 0,
22+
X30: 0, X31: 0, X32: 0, X33: 1,
23+
}
24+
1325
func LoadGLTF(file io.Reader, desiredSize *Types.Vector) (*Types.Mesh, error) {
1426
var doc gltf.Document
1527
gltf.NewDecoder(file).Decode(&doc)
@@ -18,39 +30,48 @@ func LoadGLTF(file io.Reader, desiredSize *Types.Vector) (*Types.Mesh, error) {
1830

1931
transformationMatrices := map[int]Types.Matrix{}
2032

21-
for _, node := range doc.Nodes {
22-
if node.Mesh == nil {
23-
continue
24-
}
25-
matrix := node.MatrixOrDefault()
26-
transformationMatrices[*node.Mesh] = Types.Matrix{
27-
X00: matrix[0], X01: matrix[4], X02: matrix[8], X03: matrix[12],
28-
X10: matrix[1], X11: matrix[5], X12: matrix[9], X13: matrix[13],
29-
X20: matrix[2], X21: matrix[6], X22: matrix[10], X23: matrix[14],
30-
X30: matrix[3], X31: matrix[7], X32: matrix[11], X33: matrix[15],
33+
gltfNodes := map[int]gltfNode{}
34+
35+
for _, scene := range doc.Scenes {
36+
for _, node := range scene.Nodes {
37+
handleGLTFNode(gltfNodes, doc, node, transformationMatrices, Types.IdentityMatrix())
3138
}
3239
}
3340

3441
// calculate outer dimensions
35-
min := Types.Vector{}
36-
max := Types.Vector{}
37-
for meshindex, m := range doc.Meshes {
42+
min := Types.Vector{X: math.Inf(1), Y: math.Inf(1), Z: math.Inf(1)}
43+
max := Types.Vector{X: math.Inf(-1), Y: math.Inf(-1), Z: math.Inf(-1)}
44+
for node_index, node := range gltfNodes {
45+
m := node.mesh
3846
for _, p := range m.Primitives {
3947
// contains Min and Max attr (for dimension calc)
4048
posAccessor := doc.Accessors[p.Attributes[gltf.POSITION]]
41-
// do transformation before getting the outer dimensions
42-
tempMin := transformationMatrices[meshindex].MulPosition(Types.Vector{
49+
tempMin := Types.Vector{
4350
X: posAccessor.Min[0],
44-
Y: posAccessor.Min[2],
45-
Z: posAccessor.Min[1],
46-
})
47-
tempMax := transformationMatrices[meshindex].MulPosition(Types.Vector{
51+
Y: posAccessor.Min[1],
52+
Z: posAccessor.Min[2],
53+
}
54+
tempMax := Types.Vector{
4855
X: posAccessor.Max[0],
49-
Y: posAccessor.Max[2],
50-
Z: posAccessor.Max[1],
51-
})
52-
min = min.Min(tempMin)
53-
max = max.Max(tempMax)
56+
Y: posAccessor.Max[1],
57+
Z: posAccessor.Max[2],
58+
}
59+
// determine all outer points as min/max might switch due to transformation
60+
outer_points := [8]Types.Vector{
61+
{X: tempMin.X, Y: tempMax.Y, Z: tempMax.Z}, // up front left
62+
{X: tempMin.X, Y: tempMax.Y, Z: tempMin.Z}, // up back left
63+
{X: tempMin.X, Y: tempMin.Y, Z: tempMax.Z}, // down front left
64+
{X: tempMin.X, Y: tempMin.Y, Z: tempMin.Z}, // down back left
65+
{X: tempMax.X, Y: tempMax.Y, Z: tempMax.Z}, // up front right
66+
{X: tempMax.X, Y: tempMax.Y, Z: tempMin.Z}, // up back right
67+
{X: tempMax.X, Y: tempMin.Y, Z: tempMax.Z}, // down front right
68+
{X: tempMax.X, Y: tempMin.Y, Z: tempMin.Z}, // down back right
69+
}
70+
for _, vec := range outer_points {
71+
vec = transformationMatrices[node_index].MulPosition(vec)
72+
min = min.Min(vec)
73+
max = max.Max(vec)
74+
}
5475
}
5576
}
5677

@@ -59,18 +80,20 @@ func LoadGLTF(file io.Reader, desiredSize *Types.Vector) (*Types.Mesh, error) {
5980
scaling = desiredSize.Div(max.Sub(min))
6081
}
6182

62-
for meshindex, m := range doc.Meshes {
83+
for _, node := range gltfNodes {
84+
m := node.mesh
85+
determinant := node.matrix.Determinant()
6386
for _, p := range m.Primitives {
6487
posAccessor := doc.Accessors[p.Attributes[gltf.POSITION]]
65-
positions, err := gltfVec3(&doc, posAccessor, transformationMatrices[meshindex], scaling)
88+
positions, err := gltfVec3(&doc, posAccessor, node.matrix, scaling)
6689
if err != nil {
6790
return nil, err
6891
}
6992

7093
var normals []Types.Vector
7194
if nIdx, ok := p.Attributes[gltf.NORMAL]; ok {
7295
normalAccessor := doc.Accessors[nIdx]
73-
normals, err = gltfVec3(&doc, normalAccessor, transformationMatrices[meshindex], Types.Vector{X: 1, Y: 1, Z: 1})
96+
normals, err = gltfVec3(&doc, normalAccessor, node.matrix, Types.Vector{X: 1, Y: 1, Z: 1})
7497
if err != nil {
7598
return nil, err
7699
}
@@ -98,7 +121,11 @@ func LoadGLTF(file io.Reader, desiredSize *Types.Vector) (*Types.Mesh, error) {
98121
v1 := positions[indices[i+1]].ToVertex(n1)
99122
v2 := positions[indices[i+2]].ToVertex(n2)
100123

101-
mesh.AddTriangle(Types.Triangle{V0: v0, V1: v1, V2: v2})
124+
if determinant < 0 {
125+
mesh.AddTriangle(Types.Triangle{V0: v0, V1: v2, V2: v1})
126+
} else {
127+
mesh.AddTriangle(Types.Triangle{V0: v0, V1: v1, V2: v2})
128+
}
102129
}
103130

104131
meshes.Add(&mesh)
@@ -108,6 +135,33 @@ func LoadGLTF(file io.Reader, desiredSize *Types.Vector) (*Types.Mesh, error) {
108135
return meshes, nil
109136
}
110137

138+
func handleGLTFNode(nodes map[int]gltfNode, doc gltf.Document, node_id int, transformationMatrices map[int]Types.Matrix, parentMatrix Types.Matrix) {
139+
node := doc.Nodes[node_id]
140+
gltf_matrix := node.MatrixOrDefault()
141+
var matrix Types.Matrix = parentMatrix
142+
if gltf_matrix != gltf.DefaultMatrix {
143+
matrix = matrix.Mul(Types.Matrix{
144+
X00: gltf_matrix[0], X01: gltf_matrix[4], X02: gltf_matrix[8], X03: gltf_matrix[12],
145+
X10: gltf_matrix[1], X11: gltf_matrix[5], X12: gltf_matrix[9], X13: gltf_matrix[13],
146+
X20: gltf_matrix[2], X21: gltf_matrix[6], X22: gltf_matrix[10], X23: gltf_matrix[14],
147+
X30: gltf_matrix[3], X31: gltf_matrix[7], X32: gltf_matrix[11], X33: gltf_matrix[15],
148+
})
149+
} else {
150+
matrix = matrix.Mul(gltfParseScaleRotationTranslation(node.RotationOrDefault(), node.ScaleOrDefault(), node.TranslationOrDefault()))
151+
}
152+
if node.Mesh != nil {
153+
world_matrix := conversion.Mul(matrix)
154+
transformationMatrices[node_id] = world_matrix
155+
nodes[node_id] = gltfNode{
156+
matrix: world_matrix,
157+
mesh: doc.Meshes[*node.Mesh],
158+
}
159+
}
160+
for _, child_node := range node.Children {
161+
handleGLTFNode(nodes, doc, child_node, transformationMatrices, matrix)
162+
}
163+
}
164+
111165
func gltfVec3(doc *gltf.Document, acc *gltf.Accessor, transformationMatrix Types.Matrix, scaling Types.Vector) ([]Types.Vector, error) {
112166
bufView := doc.BufferViews[*acc.BufferView]
113167
buffer := doc.Buffers[bufView.Buffer]
@@ -119,18 +173,12 @@ func gltfVec3(doc *gltf.Document, acc *gltf.Accessor, transformationMatrix Types
119173
vectors := make([]Types.Vector, acc.Count)
120174
for i := 0; i < acc.Count; i++ {
121175
base := i * 12
122-
// axes inverted to convert to correct coordinate system
123176
vec := Types.Vector{
124177
X: float64(math.Float32frombits(binary.LittleEndian.Uint32(raw[base+0:]))),
125178
Y: float64(math.Float32frombits(binary.LittleEndian.Uint32(raw[base+4:]))),
126179
Z: float64(math.Float32frombits(binary.LittleEndian.Uint32(raw[base+8:]))),
127180
}
128-
transformed := transformationMatrix.MulPosition(vec)
129-
scaled := Types.Vector{
130-
X: transformed.X,
131-
Y: -transformed.Z,
132-
Z: transformed.Y,
133-
}.Mult(scaling)
181+
scaled := transformationMatrix.MulPosition(vec).Mult(scaling)
134182
vectors[i] = scaled // vec.Mult(scaling)
135183
}
136184

@@ -164,3 +212,26 @@ func gltfIndices(doc *gltf.Document, acc *gltf.Accessor) ([]int, error) {
164212

165213
return out, nil
166214
}
215+
216+
func normalizeQuaternion(q [4]float64) [4]float64 {
217+
// Calculate mag squared
218+
magSq := q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]
219+
220+
// Check for near-zero
221+
if magSq < 1e-12 {
222+
return [4]float64{0, 0, 0, 1}
223+
}
224+
225+
mag := math.Sqrt(magSq)
226+
return [4]float64{q[0] / mag, q[1] / mag, q[2] / mag, q[3] / mag}
227+
}
228+
229+
func gltfParseScaleRotationTranslation(rotation [4]float64, scale [3]float64, translation [3]float64) Types.Matrix {
230+
rotation = normalizeQuaternion(rotation)
231+
return Types.Matrix{
232+
X00: (1 - 2*(rotation[1]*rotation[1]+rotation[2]*rotation[2])) * scale[0], X01: 2 * (rotation[0]*rotation[1] - rotation[3]*rotation[2]) * scale[1], X02: 2 * (rotation[0]*rotation[2] + rotation[3]*rotation[1]) * scale[2], X03: translation[0],
233+
X10: 2 * (rotation[0]*rotation[1] + rotation[3]*rotation[2]) * scale[0], X11: (1 - 2*(rotation[0]*rotation[0]+rotation[2]*rotation[2])) * scale[1], X12: 2 * (rotation[1]*rotation[2] - rotation[3]*rotation[0]) * scale[2], X13: translation[1],
234+
X20: 2 * (rotation[0]*rotation[2] - rotation[3]*rotation[1]) * scale[0], X21: 2 * (rotation[1]*rotation[2] + rotation[3]*rotation[0]) * scale[1], X22: (1 - 2*(rotation[0]*rotation[0]+rotation[1]*rotation[1])) * scale[2], X23: translation[2],
235+
X30: 0, X31: 0, X32: 0, X33: 1,
236+
}
237+
}

0 commit comments

Comments
 (0)