Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 106 additions & 35 deletions pkg/file_handlers/gltf.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ import (
"github.com/qmuntal/gltf"
)

type gltfNode struct {
matrix Types.Matrix
mesh *gltf.Mesh
}

var conversion = Types.Matrix{ // coordinate system conversion matrix
X00: 1, X01: 0, X02: 0, X03: 0,
X10: 0, X11: 0, X12: -1, X13: 0,
X20: 0, X21: 1, X22: 0, X23: 0,
X30: 0, X31: 0, X32: 0, X33: 1,
}

func LoadGLTF(file io.Reader, desiredSize *Types.Vector) (*Types.Mesh, error) {
var doc gltf.Document
gltf.NewDecoder(file).Decode(&doc)
Expand All @@ -18,39 +30,48 @@ func LoadGLTF(file io.Reader, desiredSize *Types.Vector) (*Types.Mesh, error) {

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

for _, node := range doc.Nodes {
if node.Mesh == nil {
continue
}
matrix := node.MatrixOrDefault()
transformationMatrices[*node.Mesh] = Types.Matrix{
X00: matrix[0], X01: matrix[4], X02: matrix[8], X03: matrix[12],
X10: matrix[1], X11: matrix[5], X12: matrix[9], X13: matrix[13],
X20: matrix[2], X21: matrix[6], X22: matrix[10], X23: matrix[14],
X30: matrix[3], X31: matrix[7], X32: matrix[11], X33: matrix[15],
gltfNodes := map[int]gltfNode{}

for _, scene := range doc.Scenes {
for _, node := range scene.Nodes {
handleGLTFNode(gltfNodes, doc, node, transformationMatrices, Types.IdentityMatrix())
}
}

// calculate outer dimensions
min := Types.Vector{}
max := Types.Vector{}
for meshindex, m := range doc.Meshes {
min := Types.Vector{X: math.Inf(1), Y: math.Inf(1), Z: math.Inf(1)}
max := Types.Vector{X: math.Inf(-1), Y: math.Inf(-1), Z: math.Inf(-1)}
for node_index, node := range gltfNodes {
m := node.mesh
for _, p := range m.Primitives {
// contains Min and Max attr (for dimension calc)
posAccessor := doc.Accessors[p.Attributes[gltf.POSITION]]
// do transformation before getting the outer dimensions
tempMin := transformationMatrices[meshindex].MulPosition(Types.Vector{
tempMin := Types.Vector{
X: posAccessor.Min[0],
Y: posAccessor.Min[2],
Z: posAccessor.Min[1],
})
tempMax := transformationMatrices[meshindex].MulPosition(Types.Vector{
Y: posAccessor.Min[1],
Z: posAccessor.Min[2],
}
tempMax := Types.Vector{
X: posAccessor.Max[0],
Y: posAccessor.Max[2],
Z: posAccessor.Max[1],
})
min = min.Min(tempMin)
max = max.Max(tempMax)
Y: posAccessor.Max[1],
Z: posAccessor.Max[2],
}
// determine all outer points as min/max might switch due to transformation
outer_points := [8]Types.Vector{
{X: tempMin.X, Y: tempMax.Y, Z: tempMax.Z}, // up front left
{X: tempMin.X, Y: tempMax.Y, Z: tempMin.Z}, // up back left
{X: tempMin.X, Y: tempMin.Y, Z: tempMax.Z}, // down front left
{X: tempMin.X, Y: tempMin.Y, Z: tempMin.Z}, // down back left
{X: tempMax.X, Y: tempMax.Y, Z: tempMax.Z}, // up front right
{X: tempMax.X, Y: tempMax.Y, Z: tempMin.Z}, // up back right
{X: tempMax.X, Y: tempMin.Y, Z: tempMax.Z}, // down front right
{X: tempMax.X, Y: tempMin.Y, Z: tempMin.Z}, // down back right
}
for _, vec := range outer_points {
vec = transformationMatrices[node_index].MulPosition(vec)
min = min.Min(vec)
max = max.Max(vec)
}
}
}

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

for meshindex, m := range doc.Meshes {
for _, node := range gltfNodes {
m := node.mesh
determinant := node.matrix.Determinant()
for _, p := range m.Primitives {
posAccessor := doc.Accessors[p.Attributes[gltf.POSITION]]
positions, err := gltfVec3(&doc, posAccessor, transformationMatrices[meshindex], scaling)
positions, err := gltfVec3(&doc, posAccessor, node.matrix, scaling)
if err != nil {
return nil, err
}

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

mesh.AddTriangle(Types.Triangle{V0: v0, V1: v1, V2: v2})
if determinant < 0 {
mesh.AddTriangle(Types.Triangle{V0: v0, V1: v2, V2: v1})
} else {
mesh.AddTriangle(Types.Triangle{V0: v0, V1: v1, V2: v2})
}
}

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

func handleGLTFNode(nodes map[int]gltfNode, doc gltf.Document, node_id int, transformationMatrices map[int]Types.Matrix, parentMatrix Types.Matrix) {
node := doc.Nodes[node_id]
gltf_matrix := node.MatrixOrDefault()
var matrix Types.Matrix = parentMatrix
if gltf_matrix != gltf.DefaultMatrix {
matrix = matrix.Mul(Types.Matrix{
X00: gltf_matrix[0], X01: gltf_matrix[4], X02: gltf_matrix[8], X03: gltf_matrix[12],
X10: gltf_matrix[1], X11: gltf_matrix[5], X12: gltf_matrix[9], X13: gltf_matrix[13],
X20: gltf_matrix[2], X21: gltf_matrix[6], X22: gltf_matrix[10], X23: gltf_matrix[14],
X30: gltf_matrix[3], X31: gltf_matrix[7], X32: gltf_matrix[11], X33: gltf_matrix[15],
})
} else {
matrix = matrix.Mul(gltfParseScaleRotationTranslation(node.RotationOrDefault(), node.ScaleOrDefault(), node.TranslationOrDefault()))
}
if node.Mesh != nil {
world_matrix := conversion.Mul(matrix)
transformationMatrices[node_id] = world_matrix
nodes[node_id] = gltfNode{
matrix: world_matrix,
mesh: doc.Meshes[*node.Mesh],
}
}
for _, child_node := range node.Children {
handleGLTFNode(nodes, doc, child_node, transformationMatrices, matrix)
}
}

func gltfVec3(doc *gltf.Document, acc *gltf.Accessor, transformationMatrix Types.Matrix, scaling Types.Vector) ([]Types.Vector, error) {
bufView := doc.BufferViews[*acc.BufferView]
buffer := doc.Buffers[bufView.Buffer]
Expand All @@ -119,18 +173,12 @@ func gltfVec3(doc *gltf.Document, acc *gltf.Accessor, transformationMatrix Types
vectors := make([]Types.Vector, acc.Count)
for i := 0; i < acc.Count; i++ {
base := i * 12
// axes inverted to convert to correct coordinate system
vec := Types.Vector{
X: float64(math.Float32frombits(binary.LittleEndian.Uint32(raw[base+0:]))),
Y: float64(math.Float32frombits(binary.LittleEndian.Uint32(raw[base+4:]))),
Z: float64(math.Float32frombits(binary.LittleEndian.Uint32(raw[base+8:]))),
}
transformed := transformationMatrix.MulPosition(vec)
scaled := Types.Vector{
X: transformed.X,
Y: -transformed.Z,
Z: transformed.Y,
}.Mult(scaling)
scaled := transformationMatrix.MulPosition(vec).Mult(scaling)
vectors[i] = scaled // vec.Mult(scaling)
}

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

return out, nil
}

func normalizeQuaternion(q [4]float64) [4]float64 {
// Calculate mag squared
magSq := q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]

// Check for near-zero
if magSq < 1e-12 {
return [4]float64{0, 0, 0, 1}
}

mag := math.Sqrt(magSq)
return [4]float64{q[0] / mag, q[1] / mag, q[2] / mag, q[3] / mag}
}

func gltfParseScaleRotationTranslation(rotation [4]float64, scale [3]float64, translation [3]float64) Types.Matrix {
rotation = normalizeQuaternion(rotation)
return Types.Matrix{
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],
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],
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],
X30: 0, X31: 0, X32: 0, X33: 1,
}
}