| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215 |
- // Copyright 2016 The G3N Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package gltf
- import (
- "bytes"
- "encoding/base64"
- "encoding/binary"
- "encoding/json"
- "fmt"
- "image"
- "image/draw"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "unsafe"
- "github.com/g3n/engine/animation"
- "github.com/g3n/engine/camera"
- "github.com/g3n/engine/core"
- "github.com/g3n/engine/geometry"
- "github.com/g3n/engine/gls"
- "github.com/g3n/engine/graphic"
- "github.com/g3n/engine/material"
- "github.com/g3n/engine/math32"
- "github.com/g3n/engine/texture"
- )
- // ParseJSON parses the glTF data from the specified JSON file
- // and returns a pointer to the parsed structure.
- func ParseJSON(filename string) (*GLTF, error) {
- // Open file
- f, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- // Extract path from file
- path := filepath.Dir(filename)
- defer f.Close()
- return ParseJSONReader(f, path)
- }
- // ParseJSONReader parses the glTF JSON data from the specified reader
- // and returns a pointer to the parsed structure
- func ParseJSONReader(r io.Reader, path string) (*GLTF, error) {
- g := new(GLTF)
- g.path = path
- dec := json.NewDecoder(r)
- err := dec.Decode(g)
- if err != nil {
- return nil, err
- }
- // TODO Check for extensions used and extensions required
- return g, nil
- }
- // ParseBin parses the glTF data from the specified binary file
- // and returns a pointer to the parsed structure.
- func ParseBin(filename string) (*GLTF, error) {
- // Open file
- f, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- // Extract path from file
- path := filepath.Dir(filename)
- defer f.Close()
- return ParseBinReader(f, path)
- }
- // ParseBinReader parses the glTF data from the specified binary reader
- // and returns a pointer to the parsed structure
- func ParseBinReader(r io.Reader, path string) (*GLTF, error) {
- // Read header
- var header GLBHeader
- err := binary.Read(r, binary.LittleEndian, &header)
- if err != nil {
- return nil, err
- }
- // Check magic and version
- if header.Magic != GLBMagic {
- return nil, fmt.Errorf("invalid GLB Magic field")
- }
- if header.Version < 2 {
- return nil, fmt.Errorf("GLB version:%v not supported", header.Version)
- }
- // Read first chunk (JSON)
- buf, err := readChunk(r, GLBJson)
- if err != nil {
- return nil, err
- }
- // Parse JSON into gltf object
- bb := bytes.NewBuffer(buf)
- gltf, err := ParseJSONReader(bb, path)
- if err != nil {
- return nil, err
- }
- // Check for and read second chunk (binary, optional)
- data, err := readChunk(r, GLBBin)
- if err != nil {
- return nil, err
- }
- gltf.data = data
- return gltf, nil
- }
- // readChunk reads a GLB chunk with the specified type and returns the data in a byte array.
- func readChunk(r io.Reader, chunkType uint32) ([]byte, error) {
- // Read chunk header
- var chunk GLBChunk
- err := binary.Read(r, binary.LittleEndian, &chunk)
- if err != nil {
- if err == io.EOF {
- return nil, nil
- }
- return nil, err
- }
- // Check chunk type
- if chunk.Type != chunkType {
- return nil, fmt.Errorf("expected GLB chunk type [%v] but found [%v]", chunkType, chunk.Type)
- }
- // Read chunk data
- data := make([]byte, chunk.Length)
- err = binary.Read(r, binary.LittleEndian, &data)
- if err != nil {
- return nil, err
- }
- return data, nil
- }
- // LoadScene creates a parent Node which contains all nodes contained by
- // the specified scene index from the GLTF Scenes array.
- func (g *GLTF) LoadScene(sceneIdx int) (core.INode, error) {
- // Check if provided scene index is valid
- if sceneIdx < 0 || sceneIdx >= len(g.Scenes) {
- return nil, fmt.Errorf("invalid scene index")
- }
- log.Debug("Loading Scene %d", sceneIdx)
- sceneData := g.Scenes[sceneIdx]
- scene := core.NewNode()
- scene.SetName(sceneData.Name)
- // Load all nodes
- for _, ni := range sceneData.Nodes {
- child, err := g.LoadNode(ni)
- if err != nil {
- return nil, err
- }
- scene.Add(child)
- }
- return scene, nil
- }
- // LoadNode creates and returns a new Node described by the specified index
- // in the decoded GLTF Nodes array.
- func (g *GLTF) LoadNode(nodeIdx int) (core.INode, error) {
- // Check if provided node index is valid
- if nodeIdx < 0 || nodeIdx >= len(g.Nodes) {
- return nil, fmt.Errorf("invalid node index")
- }
- nodeData := g.Nodes[nodeIdx]
- // Return cached if available
- if nodeData.cache != nil {
- log.Debug("Fetching Node %d (cached)", nodeIdx)
- return nodeData.cache, nil
- }
- log.Debug("Loading Node %d", nodeIdx)
- var in core.INode
- var err error
- // Check if the node is a Mesh (triangles, lines, etc...)
- if nodeData.Mesh != nil {
- in, err = g.LoadMesh(*nodeData.Mesh)
- if err != nil {
- return nil, err
- }
- if nodeData.Skin != nil {
- mesh, ok := in.(*graphic.Mesh)
- if !ok {
- children := in.GetNode().Children()
- if len(children) > 1 {
- return nil, fmt.Errorf("skinning/rigging meshes with more than a single primitive is not supported")
- }
- mesh = children[0].(*graphic.Mesh)
- }
- // Create RiggedMesh
- rm := graphic.NewRiggedMesh(mesh)
- skeleton, err := g.LoadSkin(*nodeData.Skin)
- if err != nil {
- return nil, err
- }
- rm.SetSkeleton(skeleton)
- in = rm
- }
- // Check if the node is Camera
- } else if nodeData.Camera != nil {
- in, err = g.LoadCamera(*nodeData.Camera)
- if err != nil {
- return nil, err
- }
- // Other cases, return empty node
- } else {
- log.Debug("Empty Node")
- in = core.NewNode()
- }
- // Get *core.Node from core.INode
- node := in.GetNode()
- node.SetName(nodeData.Name)
- // If defined, set node local transformation matrix
- if nodeData.Matrix != nil {
- node.SetMatrix((*math32.Matrix4)(nodeData.Matrix))
- // Otherwise, check rotation, scale and translation fields
- } else {
- // Rotation quaternion
- if nodeData.Rotation != nil {
- node.SetQuaternion(nodeData.Rotation[0], nodeData.Rotation[1], nodeData.Rotation[2], nodeData.Rotation[3])
- }
- // Scale
- if nodeData.Scale != nil {
- node.SetScale(nodeData.Scale[0], nodeData.Scale[1], nodeData.Scale[2])
- }
- // Translation
- if nodeData.Translation != nil {
- node.SetPosition(nodeData.Translation[0], nodeData.Translation[1], nodeData.Translation[2])
- }
- }
- // Cache node
- g.Nodes[nodeIdx].cache = in
- // Recursively load node children and add them to the parent
- for _, ci := range nodeData.Children {
- child, err := g.LoadNode(ci)
- if err != nil {
- return nil, err
- }
- node.Add(child)
- }
- return in, nil
- }
- // LoadSkin loads the skin with specified index.
- func (g *GLTF) LoadSkin(skinIdx int) (*graphic.Skeleton, error) {
- // Check if provided skin index is valid
- if skinIdx < 0 || skinIdx >= len(g.Skins) {
- return nil, fmt.Errorf("invalid skin index")
- }
- skinData := g.Skins[skinIdx]
- // Return cached if available
- if skinData.cache != nil {
- log.Debug("Fetching Skin %d (cached)", skinIdx)
- return skinData.cache, nil
- }
- log.Debug("Loading Skin %d", skinIdx)
- // Create Skeleton and set it on Rigged mesh
- skeleton := graphic.NewSkeleton()
- // Load inverseBindMatrices
- ibmData, err := g.loadAccessorF32(skinData.InverseBindMatrices, "ibm", []string{MAT4}, []int{FLOAT})
- if err != nil {
- return nil, err
- }
- // Add bones
- for i := range skinData.Joints {
- jointNode, err := g.LoadNode(skinData.Joints[i])
- if err != nil {
- return nil, err
- }
- var ibm math32.Matrix4
- ibmData.GetMatrix4(16*i, &ibm)
- skeleton.AddBone(jointNode.GetNode(), &ibm)
- }
- // Cache skin
- g.Skins[skinIdx].cache = skeleton
- return skeleton, nil
- }
- // LoadAnimationByName loads the animations with specified name.
- // If there are multiple animations with the same name it loads the first occurrence.
- func (g *GLTF) LoadAnimationByName(animName string) (*animation.Animation, error) {
- for i := range g.Animations {
- if g.Animations[i].Name == animName {
- return g.LoadAnimation(i)
- }
- }
- return nil, fmt.Errorf("could not find animation named %v", animName)
- }
- // LoadAnimation creates an Animation for the specified
- // animation index from the GLTF Animations array.
- func (g *GLTF) LoadAnimation(animIdx int) (*animation.Animation, error) {
- // Check if provided animation index is valid
- if animIdx < 0 || animIdx >= len(g.Animations) {
- return nil, fmt.Errorf("invalid animation index")
- }
- log.Debug("Loading Animation %d", animIdx)
- animData := g.Animations[animIdx]
- anim := animation.NewAnimation()
- anim.SetName(animData.Name)
- for i := 0; i < len(animData.Channels); i++ {
- chData := animData.Channels[i]
- target := chData.Target
- sampler := animData.Samplers[chData.Sampler]
- node, err := g.LoadNode(target.Node)
- if err != nil {
- return nil, err
- }
- var validTypes []string
- var validComponentTypes []int
- var ch animation.IChannel
- if target.Path == "translation" {
- validTypes = []string{VEC3}
- validComponentTypes = []int{FLOAT}
- ch = animation.NewPositionChannel(node)
- } else if target.Path == "rotation" {
- validTypes = []string{VEC4}
- validComponentTypes = []int{FLOAT, BYTE, UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT}
- ch = animation.NewRotationChannel(node)
- } else if target.Path == "scale" {
- validTypes = []string{VEC3}
- validComponentTypes = []int{FLOAT}
- ch = animation.NewScaleChannel(node)
- } else if target.Path == "weights" {
- validTypes = []string{SCALAR}
- validComponentTypes = []int{FLOAT, BYTE, UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT}
- children := node.GetNode().Children()
- if len(children) > 1 {
- return nil, fmt.Errorf("animating meshes with more than a single primitive is not supported")
- }
- morphGeom := children[0].(graphic.IGraphic).IGeometry().(*geometry.MorphGeometry)
- ch = animation.NewMorphChannel(morphGeom)
- }
- // TODO what if Input and Output accessors are interleaved? probably de-interleave in these 2 cases
- keyframes, err := g.loadAccessorF32(sampler.Input, "Input", []string{SCALAR}, []int{FLOAT})
- if err != nil {
- return nil, err
- }
- values, err := g.loadAccessorF32(sampler.Output, "Output", validTypes, validComponentTypes)
- if err != nil {
- return nil, err
- }
- ch.SetBuffers(keyframes, values)
- ch.SetInterpolationType(animation.InterpolationType(sampler.Interpolation))
- anim.AddChannel(ch)
- }
- return anim, nil
- }
- // LoadCamera creates and returns a Camera Node
- // from the specified GLTF.Cameras index.
- func (g *GLTF) LoadCamera(camIdx int) (core.INode, error) {
- // Check if provided camera index is valid
- if camIdx < 0 || camIdx >= len(g.Cameras) {
- return nil, fmt.Errorf("invalid camera index")
- }
- log.Debug("Loading Camera %d", camIdx)
- camData := g.Cameras[camIdx]
- aspect := float32(2) // TODO how to get the current aspect ratio of the viewport from here ?
- if camData.Type == "perspective" {
- desc := camData.Perspective
- fov := 360 * (desc.Yfov) / 2 * math32.Pi
- if desc.AspectRatio != nil {
- aspect = *desc.AspectRatio
- }
- far := float32(2e6)
- if desc.Zfar != nil {
- far = *desc.Zfar
- }
- cam := camera.NewPerspective(aspect, desc.Znear, far, fov, camera.Vertical)
- return cam, nil
- }
- if camData.Type == "orthographic" {
- desc := camData.Orthographic
- cam := camera.NewOrthographic(aspect, desc.Znear, desc.Zfar, desc.Ymag, camera.Vertical)
- return cam, nil
- }
- return nil, fmt.Errorf("unsupported camera type: %s", camData.Type)
- }
- // LoadMesh creates and returns a Graphic Node (graphic.Mesh, graphic.Lines, graphic.Points, etc)
- // from the specified GLTF.Meshes index.
- func (g *GLTF) LoadMesh(meshIdx int) (core.INode, error) {
- // Check if provided mesh index is valid
- if meshIdx < 0 || meshIdx >= len(g.Meshes) {
- return nil, fmt.Errorf("invalid mesh index")
- }
- meshData := g.Meshes[meshIdx]
- // Return cached if available
- if meshData.cache != nil {
- // TODO CLONE/REINSTANCE INSTEAD
- //log.Debug("Instancing Mesh %d (from cached)", meshIdx)
- //return meshData.cache, nil
- }
- log.Debug("Loading Mesh %d", meshIdx)
- var err error
- // Create container node
- var meshNode core.INode
- meshNode = core.NewNode()
- for i := 0; i < len(meshData.Primitives); i++ {
- // Get primitive information
- p := meshData.Primitives[i]
- // Indexed Geometry
- indices := math32.NewArrayU32(0, 0)
- if p.Indices != nil {
- pidx, err := g.loadIndices(*p.Indices)
- if err != nil {
- return nil, err
- }
- indices = append(indices, pidx...)
- } else {
- // Non-indexed primitive
- // indices array stay empty
- }
- // Load primitive material
- var grMat material.IMaterial
- if p.Material != nil {
- grMat, err = g.LoadMaterial(*p.Material)
- if err != nil {
- return nil, err
- }
- } else {
- grMat = g.newDefaultMaterial()
- }
- // Create geometry
- var igeom geometry.IGeometry
- igeom = geometry.NewGeometry()
- geom := igeom.GetGeometry()
- err = g.loadAttributes(geom, p.Attributes, indices)
- if err != nil {
- return nil, err
- }
- // If primitive has targets then the geometry should be a morph geometry
- if len(p.Targets) > 0 {
- morphGeom := geometry.NewMorphGeometry(geom)
- // TODO Load morph target names if present in extras under "targetNames"
- // TODO Update morph target weights if present in Mesh.Weights
- // Load targets
- for i := range p.Targets {
- tGeom := geometry.NewGeometry()
- attributes := p.Targets[i]
- err = g.loadAttributes(tGeom, attributes, indices)
- if err != nil {
- return nil, err
- }
- morphGeom.AddMorphTargetDeltas(tGeom)
- }
- igeom = morphGeom
- }
- // Default mode is 4 (TRIANGLES)
- mode := TRIANGLES
- if p.Mode != nil {
- mode = *p.Mode
- }
- // Create Mesh
- // TODO materials for LINES, etc need to be different...
- if mode == TRIANGLES {
- meshNode.GetNode().Add(graphic.NewMesh(igeom, grMat))
- } else if mode == LINES {
- meshNode.GetNode().Add(graphic.NewLines(igeom, grMat))
- } else if mode == LINE_STRIP {
- meshNode.GetNode().Add(graphic.NewLineStrip(igeom, grMat))
- } else if mode == POINTS {
- meshNode.GetNode().Add(graphic.NewPoints(igeom, grMat))
- } else {
- return nil, fmt.Errorf("unsupported primitive:%v", mode)
- }
- }
- children := meshNode.GetNode().Children()
- if len(children) == 1 {
- meshNode = children[0]
- }
- // Cache mesh
- g.Meshes[meshIdx].cache = meshNode
- return meshNode, nil
- }
- // loadAttributes loads the provided list of vertex attributes as VBO(s) into the specified geometry.
- func (g *GLTF) loadAttributes(geom *geometry.Geometry, attributes map[string]int, indices math32.ArrayU32) error {
- // Indices of buffer views
- interleavedVBOs := make(map[int]*gls.VBO, 0)
- // Load primitive attributes
- for name, aci := range attributes {
- accessor := g.Accessors[aci]
- // Validate that accessor is compatible with attribute
- err := g.validateAccessorAttribute(accessor, name)
- if err != nil {
- return err
- }
- // Load data and add it to geometry's VBO
- if g.isInterleaved(accessor) {
- bvIdx := *accessor.BufferView
- // Check if we already loaded this buffer view
- vbo, ok := interleavedVBOs[bvIdx]
- if ok {
- // Already created VBO for this buffer view
- // Add attribute with correct byteOffset
- g.addAttributeToVBO(vbo, name, uint32(*accessor.ByteOffset))
- } else {
- // Load data and create vbo
- buf, err := g.loadBufferView(bvIdx)
- if err != nil {
- return err
- }
- //
- // TODO: BUG HERE
- // If buffer view has accessors with different component type then this will have a read alignment problem!
- //
- data, err := g.bytesToArrayF32(buf, accessor.ComponentType, accessor.Count*TypeSizes[accessor.Type])
- if err != nil {
- return err
- }
- vbo := gls.NewVBO(data)
- g.addAttributeToVBO(vbo, name, 0)
- // Save reference to VBO keyed by index of the buffer view
- interleavedVBOs[bvIdx] = vbo
- // Add VBO to geometry
- geom.AddVBO(vbo)
- }
- } else {
- buf, err := g.loadAccessorBytes(accessor)
- if err != nil {
- return err
- }
- data, err := g.bytesToArrayF32(buf, accessor.ComponentType, accessor.Count*TypeSizes[accessor.Type])
- if err != nil {
- return err
- }
- vbo := gls.NewVBO(data)
- g.addAttributeToVBO(vbo, name, 0)
- // Add VBO to geometry
- geom.AddVBO(vbo)
- }
- }
- // Set indices
- if len(indices) > 0 {
- geom.SetIndices(indices)
- }
- return nil
- }
- // loadIndices loads the indices stored in the specified accessor.
- func (g *GLTF) loadIndices(ai int) (math32.ArrayU32, error) {
- return g.loadAccessorU32(ai, "indices", []string{SCALAR}, []int{UNSIGNED_BYTE, UNSIGNED_SHORT, UNSIGNED_INT}) // TODO verify that it's ELEMENT_ARRAY_BUFFER
- }
- // addAttributeToVBO adds the appropriate attribute to the provided vbo based on the glTF attribute name.
- func (g *GLTF) addAttributeToVBO(vbo *gls.VBO, attribName string, byteOffset uint32) {
- aType, ok := AttributeName[attribName]
- if !ok {
- log.Warn(fmt.Sprintf("Attribute %v is not supported!", attribName))
- return
- }
- vbo.AddAttribOffset(aType, byteOffset)
- }
- // validateAccessorAttribute validates the specified accessor for the given attribute name.
- func (g *GLTF) validateAccessorAttribute(ac Accessor, attribName string) error {
- parts := strings.Split(attribName, "_")
- semantic := parts[0]
- usage := "attribute " + attribName
- if attribName == "POSITION" {
- return g.validateAccessor(ac, usage, []string{VEC3}, []int{FLOAT})
- } else if attribName == "NORMAL" {
- return g.validateAccessor(ac, usage, []string{VEC3}, []int{FLOAT})
- } else if attribName == "TANGENT" {
- // Note that morph targets only support VEC3 whereas normal attributes only support VEC4.
- return g.validateAccessor(ac, usage, []string{VEC3, VEC4}, []int{FLOAT})
- } else if semantic == "TEXCOORD" {
- return g.validateAccessor(ac, usage, []string{VEC2}, []int{FLOAT, UNSIGNED_BYTE, UNSIGNED_SHORT})
- } else if semantic == "COLOR" {
- return g.validateAccessor(ac, usage, []string{VEC3, VEC4}, []int{FLOAT, UNSIGNED_BYTE, UNSIGNED_SHORT})
- } else if semantic == "JOINTS" {
- return g.validateAccessor(ac, usage, []string{VEC4}, []int{UNSIGNED_BYTE, UNSIGNED_SHORT})
- } else if semantic == "WEIGHTS" {
- return g.validateAccessor(ac, usage, []string{VEC4}, []int{FLOAT, UNSIGNED_BYTE, UNSIGNED_SHORT})
- } else {
- return fmt.Errorf("attribute %v is not supported", attribName)
- }
- }
- // validateAccessor validates the specified attribute accessor with the specified allowed types and component types.
- func (g *GLTF) validateAccessor(ac Accessor, usage string, validTypes []string, validComponentTypes []int) error {
- // Validate accessor type
- validType := false
- for _, vType := range validTypes {
- if ac.Type == vType {
- validType = true
- break
- }
- }
- if !validType {
- return fmt.Errorf("invalid Accessor.Type %v for %s", ac.Type, usage)
- }
- // Validate accessor component type
- validComponentType := false
- for _, vComponentType := range validComponentTypes {
- if ac.ComponentType == vComponentType {
- validComponentType = true
- break
- }
- }
- if !validComponentType {
- return fmt.Errorf("invalid Accessor.ComponentType %v for %s", ac.ComponentType, usage)
- }
- return nil
- }
- // newDefaultMaterial creates and returns the default material.
- func (g *GLTF) newDefaultMaterial() material.IMaterial {
- return material.NewStandard(&math32.Color{0.5, 0.5, 0.5})
- }
- // LoadMaterial creates and returns a new material based on the material data with the specified index.
- func (g *GLTF) LoadMaterial(matIdx int) (material.IMaterial, error) {
- // Check if provided material index is valid
- if matIdx < 0 || matIdx >= len(g.Materials) {
- return nil, fmt.Errorf("invalid material index")
- }
- matData := g.Materials[matIdx]
- // Return cached if available
- if matData.cache != nil {
- log.Debug("Fetching Material %d (cached)", matIdx)
- return matData.cache, nil
- }
- log.Debug("Loading Material %d", matIdx)
- var err error
- var imat material.IMaterial
- // Check for material extensions
- if matData.Extensions != nil {
- for ext, extData := range matData.Extensions {
- if ext == KhrMaterialsCommon {
- imat, err = g.loadMaterialCommon(extData)
- } else if ext == KhrMaterialsUnlit {
- //imat, err = g.loadMaterialUnlit(matData, extData)
- //} else if ext == KhrMaterialsPbrSpecularGlossiness {
- } else {
- return nil, fmt.Errorf("unsupported extension:%s", ext)
- }
- }
- } else {
- // Material is normally PBR
- imat, err = g.loadMaterialPBR(&matData)
- }
- // Cache material
- g.Materials[matIdx].cache = imat
- return imat, err
- }
- // LoadTexture loads the texture specified by its index.
- func (g *GLTF) LoadTexture(texIdx int) (*texture.Texture2D, error) {
- // Check if provided texture index is valid
- if texIdx < 0 || texIdx >= len(g.Textures) {
- return nil, fmt.Errorf("invalid texture index")
- }
- texData := g.Textures[texIdx]
- // NOTE: Textures can't be cached because they have their own uniforms
- log.Debug("Loading Texture %d", texIdx)
- // Load texture image
- img, err := g.LoadImage(texData.Source)
- if err != nil {
- return nil, err
- }
- tex := texture.NewTexture2DFromRGBA(img)
- // Get sampler and apply texture parameters
- if texData.Sampler != nil {
- err = g.applySampler(*texData.Sampler, tex)
- if err != nil {
- return nil, err
- }
- }
- return tex, nil
- }
- // applySamplers applies the specified Sampler to the provided texture.
- func (g *GLTF) applySampler(samplerIdx int, tex *texture.Texture2D) error {
- log.Debug("Applying Sampler %d", samplerIdx)
- // Check if provided sampler index is valid
- if samplerIdx < 0 || samplerIdx >= len(g.Samplers) {
- return fmt.Errorf("invalid sampler index")
- }
- sampler := g.Samplers[samplerIdx]
- // Magnification filter
- magFilter := gls.LINEAR
- if sampler.MagFilter != nil {
- magFilter = *sampler.MagFilter
- }
- tex.SetMagFilter(uint32(magFilter))
- // Minification filter
- minFilter := gls.LINEAR_MIPMAP_LINEAR
- if sampler.MinFilter != nil {
- minFilter = *sampler.MinFilter
- }
- tex.SetMinFilter(uint32(minFilter))
- // S coordinate wrapping mode
- wrapS := gls.REPEAT
- if sampler.WrapS != nil {
- wrapS = *sampler.WrapS
- }
- tex.SetWrapS(uint32(wrapS))
- // T coordinate wrapping mode
- wrapT := gls.REPEAT
- if sampler.WrapT != nil {
- wrapT = *sampler.WrapT
- }
- tex.SetWrapT(uint32(wrapT))
- return nil
- }
- // LoadImage loads the image specified by the index of GLTF.Images.
- // Image can be loaded from binary chunk file or data URI or external file..
- func (g *GLTF) LoadImage(imgIdx int) (*image.RGBA, error) {
- // Check if provided image index is valid
- if imgIdx < 0 || imgIdx >= len(g.Images) {
- return nil, fmt.Errorf("invalid image index")
- }
- imgData := g.Images[imgIdx]
- // Return cached if available
- if imgData.cache != nil {
- log.Debug("Fetching Image %d (cached)", imgIdx)
- return imgData.cache, nil
- }
- log.Debug("Loading Image %d", imgIdx)
- var data []byte
- var err error
- // If Uri is empty, load image from GLB binary chunk
- if imgData.Uri == "" {
- if imgData.BufferView == nil {
- return nil, fmt.Errorf("image has empty URI and no BufferView")
- }
- data, err = g.loadBufferView(*imgData.BufferView)
- } else if isDataURL(imgData.Uri) {
- // Checks if image URI is data URL
- data, err = loadDataURL(imgData.Uri)
- } else {
- // Load image data from file
- data, err = g.loadFileBytes(imgData.Uri)
- }
- if err != nil {
- return nil, err
- }
- // Decodes image data
- bb := bytes.NewBuffer(data)
- img, _, err := image.Decode(bb)
- if err != nil {
- return nil, err
- }
- // Converts image to RGBA format
- rgba := image.NewRGBA(img.Bounds())
- if rgba.Stride != rgba.Rect.Size().X*4 {
- return nil, fmt.Errorf("unsupported stride")
- }
- draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
- // Cache image
- g.Images[imgIdx].cache = rgba
- return rgba, nil
- }
- // bytesToArrayU32 converts a byte array to ArrayU32.
- func (g *GLTF) bytesToArrayU32(data []byte, componentType, count int) (math32.ArrayU32, error) {
- // If component is UNSIGNED_INT nothing to do
- if componentType == UNSIGNED_INT {
- arr := (*[1 << 30]uint32)(unsafe.Pointer(&data[0]))[:count]
- return math32.ArrayU32(arr), nil
- }
- // Converts UNSIGNED_SHORT or SHORT to UNSIGNED_INT
- if componentType == UNSIGNED_SHORT || componentType == SHORT {
- out := math32.NewArrayU32(count, count)
- for i := 0; i < count; i++ {
- out[i] = uint32(data[i*2]) + uint32(data[i*2+1])*256
- }
- return out, nil
- }
- // Converts UNSIGNED_BYTE or BYTE to UNSIGNED_INT
- if componentType == UNSIGNED_BYTE || componentType == BYTE {
- out := math32.NewArrayU32(count, count)
- for i := 0; i < count; i++ {
- out[i] = uint32(data[i])
- }
- return out, nil
- }
- return nil, fmt.Errorf("unsupported Accessor ComponentType:%v", componentType)
- }
- // bytesToArrayF32 converts a byte array to ArrayF32.
- func (g *GLTF) bytesToArrayF32(data []byte, componentType, count int) (math32.ArrayF32, error) {
- // If component is UNSIGNED_INT nothing to do
- if componentType == UNSIGNED_INT {
- arr := (*[1 << 30]float32)(unsafe.Pointer(&data[0]))[:count]
- return math32.ArrayF32(arr), nil
- }
- // Converts UNSIGNED_SHORT or SHORT to UNSIGNED_INT
- if componentType == UNSIGNED_SHORT || componentType == SHORT {
- out := math32.NewArrayF32(count, count)
- for i := 0; i < count; i++ {
- out[i] = float32(data[i*2]) + float32(data[i*2+1])*256
- }
- return out, nil
- }
- // Converts UNSIGNED_BYTE or BYTE to UNSIGNED_INT
- if componentType == UNSIGNED_BYTE || componentType == BYTE {
- out := math32.NewArrayF32(count, count)
- for i := 0; i < count; i++ {
- out[i] = float32(data[i])
- }
- return out, nil
- }
- return (*[1 << 30]float32)(unsafe.Pointer(&data[0]))[:count], nil
- }
- // loadAccessorU32 loads data from the specified accessor and performs validation of the Type and ComponentType.
- func (g *GLTF) loadAccessorU32(ai int, usage string, validTypes []string, validComponentTypes []int) (math32.ArrayU32, error) {
- // Get Accessor for the specified index
- ac := g.Accessors[ai]
- if ac.BufferView == nil {
- return nil, fmt.Errorf("accessor.BufferView == nil NOT SUPPORTED YET") // TODO
- }
- // Validate type and component type
- err := g.validateAccessor(ac, usage, validTypes, validComponentTypes)
- if err != nil {
- return nil, err
- }
- // Load bytes
- data, err := g.loadAccessorBytes(ac)
- if err != nil {
- return nil, err
- }
- return g.bytesToArrayU32(data, ac.ComponentType, ac.Count*TypeSizes[ac.Type])
- }
- // loadAccessorF32 loads data from the specified accessor and performs validation of the Type and ComponentType.
- func (g *GLTF) loadAccessorF32(ai int, usage string, validTypes []string, validComponentTypes []int) (math32.ArrayF32, error) {
- // Get Accessor for the specified index
- ac := g.Accessors[ai]
- if ac.BufferView == nil {
- return nil, fmt.Errorf("accessor.BufferView == nil NOT SUPPORTED YET") // TODO
- }
- // Validate type and component type
- err := g.validateAccessor(ac, usage, validTypes, validComponentTypes)
- if err != nil {
- return nil, err
- }
- // Load bytes
- data, err := g.loadAccessorBytes(ac)
- if err != nil {
- return nil, err
- }
- return g.bytesToArrayF32(data, ac.ComponentType, ac.Count*TypeSizes[ac.Type])
- }
- // loadAccessorBytes returns the base byte array used by an accessor.
- func (g *GLTF) loadAccessorBytes(ac Accessor) ([]byte, error) {
- // Get the Accessor's BufferView
- if ac.BufferView == nil {
- return nil, fmt.Errorf("accessor.BufferView == nil NOT SUPPORTED YET") // TODO
- }
- bv := g.BufferViews[*ac.BufferView]
- // Loads data from associated BufferView
- data, err := g.loadBufferView(*ac.BufferView)
- if err != nil {
- return nil, err
- }
- // Accessor offset into BufferView
- offset := 0
- if ac.ByteOffset != nil {
- offset = *ac.ByteOffset
- }
- data = data[offset:]
- // TODO check if interleaved and de-interleave if necessary?
- // Calculate the size in bytes of a complete attribute
- itemSize := TypeSizes[ac.Type]
- itemBytes := int(gls.FloatSize) * itemSize
- // If the BufferView stride is equal to the item size, the buffer is not interleaved
- if (bv.ByteStride != nil) && (*bv.ByteStride != itemBytes) {
- // BufferView data is interleaved, de-interleave
- // TODO
- return nil, fmt.Errorf("data is interleaved - not supported for animation yet")
- }
- // TODO Sparse accessor
- return data, nil
- }
- // isInterleaves returns whether the BufferView used by the provided accessor is interleaved.
- func (g *GLTF) isInterleaved(accessor Accessor) bool {
- // Get the Accessor's BufferView
- if accessor.BufferView == nil {
- return false
- }
- bv := g.BufferViews[*accessor.BufferView]
- // Calculates the size in bytes of a complete attribute
- itemSize := TypeSizes[accessor.Type]
- itemBytes := int(gls.FloatSize) * itemSize
- // If the BufferView stride is equal to the item size, the buffer is not interleaved
- if bv.ByteStride == nil || *bv.ByteStride == itemBytes {
- return false
- }
- return true
- }
- // loadBufferView loads and returns a byte slice with data from the specified BufferView.
- func (g *GLTF) loadBufferView(bvIdx int) ([]byte, error) {
- // Check if provided buffer view index is valid
- if bvIdx < 0 || bvIdx >= len(g.BufferViews) {
- return nil, fmt.Errorf("invalid buffer view index")
- }
- bvData := g.BufferViews[bvIdx]
- // Return cached if available
- if bvData.cache != nil {
- log.Debug("Fetching BufferView %d (cached)", bvIdx)
- return bvData.cache, nil
- }
- log.Debug("Loading BufferView %d", bvIdx)
- // Load buffer view buffer
- buf, err := g.loadBuffer(bvData.Buffer)
- if err != nil {
- return nil, err
- }
- // Establish offset
- offset := 0
- if bvData.ByteOffset != nil {
- offset = *bvData.ByteOffset
- }
- // Compute and return offset slice
- bvBytes := buf[offset : offset+bvData.ByteLength]
- // Cache buffer view
- g.BufferViews[bvIdx].cache = bvBytes
- return bvBytes, nil
- }
- // loadBuffer loads and returns the data from the specified GLTF Buffer index
- func (g *GLTF) loadBuffer(bufIdx int) ([]byte, error) {
- // Check if provided buffer index is valid
- if bufIdx < 0 || bufIdx >= len(g.Buffers) {
- return nil, fmt.Errorf("invalid buffer index")
- }
- bufData := &g.Buffers[bufIdx]
- // Return cached if available
- if bufData.cache != nil {
- log.Debug("Fetching Buffer %d (cached)", bufIdx)
- return bufData.cache, nil
- }
- log.Debug("Loading Buffer %d", bufIdx)
- // If buffer URI use the chunk data field
- if bufData.Uri == "" {
- return g.data, nil
- }
- // Checks if buffer URI is a data URI
- var data []byte
- var err error
- if isDataURL(bufData.Uri) {
- data, err = loadDataURL(bufData.Uri)
- } else {
- // Try to load buffer from file
- data, err = g.loadFileBytes(bufData.Uri)
- }
- if err != nil {
- return nil, err
- }
- // Checks data length
- if len(data) != bufData.ByteLength {
- return nil, fmt.Errorf("buffer:%d read data length:%d expected:%d", bufIdx, len(data), bufData.ByteLength)
- }
- // Cache buffer data
- g.Buffers[bufIdx].cache = data
- log.Debug("cache data:%v", len(bufData.cache))
- return data, nil
- }
- // loadFileBytes loads the file with specified path as a byte array.
- func (g *GLTF) loadFileBytes(uri string) ([]byte, error) {
- log.Debug("Loading File: %v", uri)
- fpath := filepath.Join(g.path, uri)
- f, err := os.Open(fpath)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- data, err := ioutil.ReadAll(f)
- if err != nil {
- return nil, err
- }
- return data, nil
- }
- // dataURL describes a decoded data url string.
- type dataURL struct {
- MediaType string
- Encoding string
- Data string
- }
- const (
- dataURLprefix = "data:"
- mimeBIN = "application/octet-stream"
- mimePNG = "image/png"
- mimeJPEG = "image/jpeg"
- )
- var validMediaTypes = []string{mimeBIN, mimePNG, mimeJPEG}
- // isDataURL checks if the specified string has the prefix of data URL.
- func isDataURL(url string) bool {
- if strings.HasPrefix(url, dataURLprefix) {
- return true
- }
- return false
- }
- // loadDataURL decodes the specified data URI string (base64).
- func loadDataURL(url string) ([]byte, error) {
- var du dataURL
- err := parseDataURL(url, &du)
- if err != nil {
- return nil, err
- }
- // Checks for valid media type
- found := false
- for i := 0; i < len(validMediaTypes); i++ {
- if validMediaTypes[i] == du.MediaType {
- found = true
- break
- }
- }
- if !found {
- return nil, fmt.Errorf("data URI media type:%s not supported", du.MediaType)
- }
- // Checks encoding
- if du.Encoding != "base64" {
- return nil, fmt.Errorf("data URI encoding:%s not supported", du.Encoding)
- }
- // Decodes data from BASE64
- data, err := base64.StdEncoding.DecodeString(du.Data)
- if err != nil {
- return nil, err
- }
- return data, nil
- }
- // parseDataURL tries to parse the specified string as a data URL with the format:
- // data:[<mediatype>][;base64],<data>
- // and if successfull returns true and updates the specified pointer with the parsed fields.
- func parseDataURL(url string, du *dataURL) error {
- // Check prefix
- if !isDataURL(url) {
- return fmt.Errorf("specified string is not a data URL")
- }
- // Separate header from data
- body := url[len(dataURLprefix):]
- parts := strings.Split(body, ",")
- if len(parts) != 2 {
- return fmt.Errorf("data URI contains more than one ','")
- }
- du.Data = parts[1]
- // Separate media type from optional encoding
- res := strings.Split(parts[0], ";")
- du.MediaType = res[0]
- if len(res) < 2 {
- return nil
- }
- if len(res) >= 2 {
- du.Encoding = res[1]
- }
- return nil
- }
|