| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915 |
- // 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 obj is used to parse the Wavefront OBJ file format (*.obj), including
- // associated materials (*.mtl). Not all features of the OBJ format are
- // supported. Basic format info: https://en.wikipedia.org/wiki/Wavefront_.obj_file
- package obj
- import (
- "bufio"
- "errors"
- "fmt"
- "io"
- "math"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "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"
- )
- // Decoder contains all decoded data from the obj and mtl files
- type Decoder struct {
- Objects []Object // decoded objects
- Matlib string // name of the material lib
- Materials map[string]*Material // maps material name to object
- Vertices math32.ArrayF32 // vertices positions array
- Normals math32.ArrayF32 // vertices normals
- Uvs math32.ArrayF32 // vertices texture coordinates
- Warnings []string // warning messages
- line uint // current line number
- objCurrent *Object // current object
- matCurrent *Material // current material
- smoothCurrent bool // current smooth state
- mtlDir string // Directory of material file
- }
- // Object contains all information about one decoded object
- type Object struct {
- Name string // Object name
- Faces []Face // Faces
- materials []string // Materials used in this object
- }
- // Face contains all information about an object face
- type Face struct {
- Vertices []int // Indices to the face vertices
- Uvs []int // Indices to the face UV coordinates
- Normals []int // Indices to the face normals
- Material string // Material name
- Smooth bool // Smooth face
- }
- // Material contains all information about an object material
- type Material struct {
- Name string // Material name
- Illum int // Illumination model
- Opacity float32 // Opacity factor
- Refraction float32 // Refraction factor
- Shininess float32 // Shininess (specular exponent)
- Ambient math32.Color // Ambient color reflectivity
- Diffuse math32.Color // Diffuse color reflectivity
- Specular math32.Color // Specular color reflectivity
- Emissive math32.Color // Emissive color
- MapKd string // Texture file linked to diffuse color
- }
- // Light gray default material used as when other materials cannot be loaded.
- var defaultMat = &Material{
- Diffuse: math32.Color{R: 0.7, G: 0.7, B: 0.7},
- Ambient: math32.Color{R: 0.7, G: 0.7, B: 0.7},
- Specular: math32.Color{R: 0.5, G: 0.5, B: 0.5},
- Shininess: 30.0,
- }
- // Local constants
- const (
- blanks = "\r\n\t "
- invINDEX = math.MaxUint32
- objType = "obj"
- mtlType = "mtl"
- )
- // Decode decodes the specified obj and mtl files returning a decoder
- // object and an error. Passing an empty string (or otherwise invalid path)
- // to mtlpath will cause the decoder to check the 'mtllib' file in the OBJ if
- // present, and fall back to a default material as a last resort.
- func Decode(objpath string, mtlpath string) (*Decoder, error) {
- // Opens obj file
- fobj, err := os.Open(objpath)
- if err != nil {
- return nil, err
- }
- defer fobj.Close()
- // Opens mtl file
- // if mtlpath=="", then os.Open() will produce an error,
- // causing fmtl to be nil
- fmtl, err := os.Open(mtlpath)
- defer fmtl.Close() // will produce (ignored) err if fmtl==nil
- // if fmtl==nil, the io.Reader in DecodeReader() will be (T=*os.File, V=nil)
- // which is NOT equal to plain nil or (io.Reader, nil) but will produce
- // the desired result of passing nil to DecodeReader() per it's func comment.
- dec, err := DecodeReader(fobj, fmtl)
- if err != nil {
- return nil, err
- }
- dec.mtlDir = filepath.Dir(objpath)
- return dec, nil
- }
- // DecodeReader decodes the specified obj and mtl readers returning a decoder
- // object and an error if a problem was encoutered while parsing the OBJ.
- //
- // Pass a valid io.Reader to override the materials defined in the OBJ file,
- // or `nil` to use the materials listed in the OBJ's "mtllib" line (if present),
- // a ".mtl" file with the same name as the OBJ file if presemt, or a default
- // material as a last resort. No error will be returned for problems
- // with materials--a gray default material will be used if nothing else works.
- func DecodeReader(objreader, mtlreader io.Reader) (*Decoder, error) {
- dec := new(Decoder)
- dec.Objects = make([]Object, 0)
- dec.Warnings = make([]string, 0)
- dec.Materials = make(map[string]*Material)
- dec.Vertices = math32.NewArrayF32(0, 0)
- dec.Normals = math32.NewArrayF32(0, 0)
- dec.Uvs = math32.NewArrayF32(0, 0)
- dec.line = 1
- // Parses obj lines
- err := dec.parse(objreader, dec.parseObjLine)
- if err != nil {
- return nil, err
- }
- // Parses mtl lines
- // 1) try passed in mtlreader,
- // 2) try file in mtllib line
- // 3) try <obj_filename>.mtl
- // 4) use default material as last resort
- dec.matCurrent = nil
- dec.line = 1
- // first try: use the material file passed in as an io.Reader
- err = dec.parse(mtlreader, dec.parseMtlLine)
- if err != nil {
- // 2) if mtlreader produces an error (eg. it's nil), try the file listed
- // in the OBJ's matlib line, if it exists.
- if dec.Matlib != "" {
- // ... first need to get the path of the OBJ, since mtllib is relative
- var mtllibPath string
- if objf, ok := objreader.(*os.File); ok {
- // NOTE (quillaja): this is a hack because we need the directory of
- // the OBJ, but can't get it any other way (dec.mtlDir isn't set
- // until AFTER this function is finished).
- objdir := filepath.Dir(objf.Name())
- mtllibPath = filepath.Join(objdir, dec.Matlib)
- dec.mtlDir = objdir // NOTE (quillaja): should this be set?
- }
- mtlf, errMTL := os.Open(mtllibPath)
- defer mtlf.Close()
- if errMTL == nil {
- err = dec.parse(mtlf, dec.parseMtlLine) // will set err to nil if successful
- }
- }
- // 3) if the mtllib line fails try <obj_filename>.mtl in the same directory.
- // process is basically identical to the above code block.
- if err != nil {
- var mtlpath string
- if objf, ok := objreader.(*os.File); ok {
- objdir := strings.TrimSuffix(objf.Name(), ".obj")
- mtlpath = objdir + ".mtl"
- dec.mtlDir = objdir // NOTE (quillaja): should this be set?
- }
- mtlf, errMTL := os.Open(mtlpath)
- defer mtlf.Close()
- if errMTL == nil {
- err = dec.parse(mtlf, dec.parseMtlLine) // will set err to nil if successful
- if err == nil {
- // log a warning
- msg := fmt.Sprintf("using material file %s", mtlpath)
- dec.appendWarn(mtlType, msg)
- }
- }
- }
- // 4) handle error(s) instead of simply passing it up the call stack.
- // range over the materials named in the OBJ file and substitute a default
- // But log that an error occured.
- if err != nil {
- for key := range dec.Materials {
- dec.Materials[key] = defaultMat
- }
- // NOTE (quillaja): could be an error of some custom type. But people
- // tend to ignore errors and pass them up the call stack instead
- // of handling them... so all this work would probably be wasted.
- dec.appendWarn(mtlType, "unable to parse a material file for obj. using default material instead.")
- }
- }
- return dec, nil
- }
- // NewGroup creates and returns a group containing as children meshes
- // with all the decoded objects.
- // A group is returned even if there is only one object decoded.
- func (dec *Decoder) NewGroup() (*core.Node, error) {
- group := core.NewNode()
- for i := 0; i < len(dec.Objects); i++ {
- mesh, err := dec.NewMesh(&dec.Objects[i])
- if err != nil {
- return nil, err
- }
- group.Add(mesh)
- }
- return group, nil
- }
- // NewMesh creates and returns a mesh from an specified decoded object.
- func (dec *Decoder) NewMesh(obj *Object) (*graphic.Mesh, error) {
- // Creates object geometry
- geom, err := dec.NewGeometry(obj)
- if err != nil {
- return nil, err
- }
- // Single material
- if geom.GroupCount() == 1 {
- // get Material info from mtl file and ensure it's valid.
- // substitute default material if it is not.
- var matDesc *Material
- var matName string
- if len(obj.materials) > 0 {
- matName = obj.materials[0]
- }
- matDesc = dec.Materials[matName]
- if matDesc == nil {
- matDesc = defaultMat
- // log warning
- msg := fmt.Sprintf("could not find material for %s. using default material.", obj.Name)
- dec.appendWarn(objType, msg)
- }
- // Creates material for mesh
- mat := material.NewPhong(&matDesc.Diffuse)
- ambientColor := mat.AmbientColor()
- mat.SetAmbientColor(ambientColor.Multiply(&matDesc.Ambient))
- mat.SetSpecularColor(&matDesc.Specular)
- mat.SetShininess(matDesc.Shininess)
- // Loads material textures if specified
- err = dec.loadTex(&mat.Material, matDesc)
- if err != nil {
- return nil, err
- }
- return graphic.NewMesh(geom, mat), nil
- }
- // Multi material
- mesh := graphic.NewMesh(geom, nil)
- for idx := 0; idx < geom.GroupCount(); idx++ {
- group := geom.GroupAt(idx)
- // get Material info from mtl file and ensure it's valid.
- // substitute default material if it is not.
- var matDesc *Material
- var matName string
- if len(obj.materials) > group.Matindex {
- matName = obj.materials[group.Matindex]
- }
- matDesc = dec.Materials[matName]
- if matDesc == nil {
- matDesc = defaultMat
- // log warning
- msg := fmt.Sprintf("could not find material for %s. using default material.", obj.Name)
- dec.appendWarn(objType, msg)
- }
- // Creates material for mesh
- matGroup := material.NewPhong(&matDesc.Diffuse)
- ambientColor := matGroup.AmbientColor()
- matGroup.SetAmbientColor(ambientColor.Multiply(&matDesc.Ambient))
- matGroup.SetSpecularColor(&matDesc.Specular)
- matGroup.SetShininess(matDesc.Shininess)
- // Loads material textures if specified
- err = dec.loadTex(&matGroup.Material, matDesc)
- if err != nil {
- return nil, err
- }
- mesh.AddGroupMaterial(matGroup, idx)
- }
- return mesh, nil
- }
- // NewGeometry generates and returns a geometry from the specified object
- func (dec *Decoder) NewGeometry(obj *Object) (*geometry.Geometry, error) {
- geom := geometry.NewGeometry()
- // Create buffers
- positions := math32.NewArrayF32(0, 0)
- normals := math32.NewArrayF32(0, 0)
- uvs := math32.NewArrayF32(0, 0)
- indices := math32.NewArrayU32(0, 0)
- // copy all vertex info from the decoded Object, face and index to the geometry
- copyVertex := func(face *Face, idx int) {
- var vec3 math32.Vector3
- var vec2 math32.Vector2
- pos := positions.Size() / 3
- // Copy vertex position and append to geometry
- dec.Vertices.GetVector3(3*face.Vertices[idx], &vec3)
- positions.AppendVector3(&vec3)
- // Copy vertex normal and append to geometry
- if face.Normals[idx] != invINDEX {
- dec.Normals.GetVector3(3*face.Normals[idx], &vec3)
- normals.AppendVector3(&vec3)
- }
- // Copy vertex uv and append to geometry
- if face.Uvs[idx] != invINDEX {
- dec.Uvs.GetVector2(2*face.Uvs[idx], &vec2)
- uvs.AppendVector2(&vec2)
- }
- indices.Append(uint32(pos))
- }
- var group *geometry.Group
- matName := ""
- matIndex := 0
- for _, face := range obj.Faces {
- // If face material changed, starts a new group
- if face.Material != matName {
- group = geom.AddGroup(indices.Size(), 0, matIndex)
- matName = face.Material
- matIndex++
- }
- // Copy face vertices to geometry
- for idx := 1; idx < len(face.Vertices)-1; idx++ {
- copyVertex(&face, 0)
- copyVertex(&face, idx)
- copyVertex(&face, idx+1)
- group.Count += 3
- }
- }
- geom.SetIndices(indices)
- geom.AddVBO(gls.NewVBO(positions).AddAttrib(gls.VertexPosition))
- geom.AddVBO(gls.NewVBO(normals).AddAttrib(gls.VertexNormal))
- geom.AddVBO(gls.NewVBO(uvs).AddAttrib(gls.VertexTexcoord))
- return geom, nil
- }
- // loadTex loads textures described in the material descriptor into the
- // specified material
- func (dec *Decoder) loadTex(mat *material.Material, desc *Material) error {
- // Checks if material descriptor specified texture
- if desc.MapKd == "" {
- return nil
- }
- // Get texture file path
- // If texture file path is not absolute assumes it is relative
- // to the directory of the material file
- var texPath string
- if filepath.IsAbs(desc.MapKd) {
- texPath = desc.MapKd
- } else {
- texPath = filepath.Join(dec.mtlDir, desc.MapKd)
- }
- // Try to load texture from image file
- tex, err := texture.NewTexture2DFromImage(texPath)
- if err != nil {
- return err
- }
- mat.AddTexture(tex)
- return nil
- }
- // parse reads the lines from the specified reader and dispatch them
- // to the specified line parser.
- func (dec *Decoder) parse(reader io.Reader, parseLine func(string) error) error {
- bufin := bufio.NewReader(reader)
- dec.line = 1
- for {
- // Reads next line and abort on errors (not EOF)
- line, err := bufin.ReadString('\n')
- if err != nil && err != io.EOF {
- return err
- }
- // Parses the line
- line = strings.Trim(line, blanks)
- perr := parseLine(line)
- if perr != nil {
- return perr
- }
- // If EOF ends of parsing.
- if err == io.EOF {
- break
- }
- dec.line++
- }
- return nil
- }
- // Parses obj file line, dispatching to specific parsers
- func (dec *Decoder) parseObjLine(line string) error {
- // Ignore empty lines
- fields := strings.Fields(line)
- if len(fields) == 0 {
- return nil
- }
- // Ignore comment lines
- ltype := fields[0]
- if strings.HasPrefix(ltype, "#") {
- return nil
- }
- switch ltype {
- // Material library
- case "mtllib":
- return dec.parseMatlib(fields[1:])
- // Object name
- case "o":
- return dec.parseObject(fields[1:])
- // Group names. We are considering "group" the same as "object"
- // This may not be right
- case "g":
- return dec.parseObject(fields[1:])
- // Vertex coordinate
- case "v":
- return dec.parseVertex(fields[1:])
- // Vertex normal coordinate
- case "vn":
- return dec.parseNormal(fields[1:])
- // Vertex texture coordinate
- case "vt":
- return dec.parseTex(fields[1:])
- // Face vertex
- case "f":
- return dec.parseFace(fields[1:])
- // Use material
- case "usemtl":
- return dec.parseUsemtl(fields[1:])
- // Smooth
- case "s":
- return dec.parseSmooth(fields[1:])
- default:
- dec.appendWarn(objType, "field not supported: "+ltype)
- }
- return nil
- }
- // Parses a mtllib line:
- // mtllib <name>
- func (dec *Decoder) parseMatlib(fields []string) error {
- if len(fields) < 1 {
- return errors.New("Material library (mtllib) with no fields")
- }
- dec.Matlib = fields[0]
- return nil
- }
- // Parses an object line:
- // o <name>
- func (dec *Decoder) parseObject(fields []string) error {
- if len(fields) < 1 {
- return errors.New("Object line (o) with no fields")
- }
- var ob Object
- ob.Name = fields[0]
- ob.Faces = make([]Face, 0)
- ob.materials = make([]string, 0)
- dec.Objects = append(dec.Objects, ob)
- dec.objCurrent = &dec.Objects[len(dec.Objects)-1]
- return nil
- }
- // Parses a vertex position line
- // v <x> <y> <z> [w]
- func (dec *Decoder) parseVertex(fields []string) error {
- if len(fields) < 3 {
- return errors.New("Less than 3 vertices in 'v' line")
- }
- for _, f := range fields[:3] {
- val, err := strconv.ParseFloat(f, 32)
- if err != nil {
- return err
- }
- dec.Vertices.Append(float32(val))
- }
- return nil
- }
- // Parses a vertex normal line
- // vn <x> <y> <z>
- func (dec *Decoder) parseNormal(fields []string) error {
- if len(fields) < 3 {
- return errors.New("Less than 3 normals in 'vn' line")
- }
- for _, f := range fields[:3] {
- val, err := strconv.ParseFloat(f, 32)
- if err != nil {
- return err
- }
- dec.Normals.Append(float32(val))
- }
- return nil
- }
- // Parses a vertex texture coordinate line:
- // vt <u> <v> <w>
- func (dec *Decoder) parseTex(fields []string) error {
- if len(fields) < 2 {
- return errors.New("Less than 2 texture coords. in 'vt' line")
- }
- for _, f := range fields[:2] {
- val, err := strconv.ParseFloat(f, 32)
- if err != nil {
- return err
- }
- dec.Uvs.Append(float32(val))
- }
- return nil
- }
- // parseFace parses a face decription line:
- // f v1[/vt1][/vn1] v2[/vt2][/vn2] v3[/vt3][/vn3] ...
- func (dec *Decoder) parseFace(fields []string) error {
- // If current object has no material, appends last material if defined
- if len(dec.objCurrent.materials) == 0 && dec.matCurrent != nil {
- dec.objCurrent.materials = append(dec.objCurrent.materials, dec.matCurrent.Name)
- }
- if len(fields) < 3 {
- return dec.formatError("Face line with less 3 fields")
- }
- var face Face
- face.Vertices = make([]int, len(fields))
- face.Uvs = make([]int, len(fields))
- face.Normals = make([]int, len(fields))
- if dec.matCurrent != nil {
- face.Material = dec.matCurrent.Name
- } else {
- // TODO (quillaja): do something better than spamming warnings for each line
- // dec.appendWarn(objType, "No material defined")
- face.Material = "internal default" // causes error on in NewGeom() if ""
- // dec.matCurrent = defaultMat
- }
- face.Smooth = dec.smoothCurrent
- for pos, f := range fields {
- // Separate the current field in its components: v vt vn
- vfields := strings.Split(f, "/")
- if len(vfields) < 1 {
- return dec.formatError("Face field with no parts")
- }
- // Get the index of this vertex position (must always exist)
- val, err := strconv.ParseInt(vfields[0], 10, 32)
- if err != nil {
- return err
- }
- // Positive index is an absolute vertex index
- if val > 0 {
- face.Vertices[pos] = int(val - 1)
- // Negative vertex index is relative to the last parsed vertex
- } else if val < 0 {
- current := (len(dec.Vertices) / 3) - 1
- face.Vertices[pos] = current + int(val) + 1
- // Vertex index could never be 0
- } else {
- return dec.formatError("Face vertex index value equal to 0")
- }
- // Get the index of this vertex UV coordinate (optional)
- if len(vfields) > 1 && len(vfields[1]) > 0 {
- val, err := strconv.ParseInt(vfields[1], 10, 32)
- if err != nil {
- return err
- }
- // Positive index is an absolute UV index
- if val > 0 {
- face.Uvs[pos] = int(val - 1)
- // Negative vertex index is relative to the last parsed uv
- } else if val < 0 {
- current := (len(dec.Uvs) / 2) - 1
- face.Uvs[pos] = current + int(val) + 1
- // UV index could never be 0
- } else {
- return dec.formatError("Face uv index value equal to 0")
- }
- } else {
- face.Uvs[pos] = invINDEX
- }
- // Get the index of this vertex normal (optional)
- if len(vfields) >= 3 {
- val, err = strconv.ParseInt(vfields[2], 10, 32)
- if err != nil {
- return err
- }
- // Positive index is an absolute normal index
- if val > 0 {
- face.Normals[pos] = int(val - 1)
- // Negative vertex index is relative to the last parsed normal
- } else if val < 0 {
- current := (len(dec.Normals) / 3) - 1
- face.Normals[pos] = current + int(val) + 1
- // Normal index could never be 0
- } else {
- return dec.formatError("Face normal index value equal to 0")
- }
- } else {
- face.Normals[pos] = invINDEX
- }
- }
- // Appends this face to the current object
- dec.objCurrent.Faces = append(dec.objCurrent.Faces, face)
- return nil
- }
- // parseUsemtl parses a "usemtl" decription line:
- // usemtl <name>
- func (dec *Decoder) parseUsemtl(fields []string) error {
- if len(fields) < 1 {
- return dec.formatError("Usemtl with no fields")
- }
- // Checks if this material has already been parsed
- name := fields[0]
- mat := dec.Materials[name]
- // Creates material descriptor
- if mat == nil {
- mat = new(Material)
- mat.Name = name
- dec.Materials[name] = mat
- }
- dec.objCurrent.materials = append(dec.objCurrent.materials, name)
- // Set this as the current material
- dec.matCurrent = mat
- return nil
- }
- // parseSmooth parses a "s" decription line:
- // s <0|1>
- func (dec *Decoder) parseSmooth(fields []string) error {
- if len(fields) < 1 {
- return dec.formatError("'s' with no fields")
- }
- if fields[0] == "0" || fields[0] == "off" {
- dec.smoothCurrent = false
- return nil
- }
- if fields[0] == "1" || fields[0] == "on" {
- dec.smoothCurrent = true
- return nil
- }
- return dec.formatError("'s' with invalid value")
- }
- /******************************************************************************
- mtl parse functions
- */
- // Parses material file line, dispatching to specific parsers
- func (dec *Decoder) parseMtlLine(line string) error {
- // Ignore empty lines
- fields := strings.Fields(line)
- if len(fields) == 0 {
- return nil
- }
- // Ignore comment lines
- ltype := fields[0]
- if strings.HasPrefix(ltype, "#") {
- return nil
- }
- switch ltype {
- case "newmtl":
- return dec.parseNewmtl(fields[1:])
- case "d":
- return dec.parseDissolve(fields[1:])
- case "Ka":
- return dec.parseKa(fields[1:])
- case "Kd":
- return dec.parseKd(fields[1:])
- case "Ke":
- return dec.parseKe(fields[1:])
- case "Ks":
- return dec.parseKs(fields[1:])
- case "Ni":
- return dec.parseNi(fields[1:])
- case "Ns":
- return dec.parseNs(fields[1:])
- case "illum":
- return dec.parseIllum(fields[1:])
- case "map_Kd":
- return dec.parseMapKd(fields[1:])
- default:
- dec.appendWarn(mtlType, "field not supported: "+ltype)
- }
- return nil
- }
- // Parses new material definition
- // newmtl <mat_name>
- func (dec *Decoder) parseNewmtl(fields []string) error {
- if len(fields) < 1 {
- return dec.formatError("newmtl with no fields")
- }
- // Checks if material has already been seen
- name := fields[0]
- mat := dec.Materials[name]
- // Creates material descriptor
- if mat == nil {
- mat = new(Material)
- mat.Name = name
- dec.Materials[name] = mat
- }
- dec.matCurrent = mat
- return nil
- }
- // Parses the dissolve factor (opacity)
- // d <factor>
- func (dec *Decoder) parseDissolve(fields []string) error {
- if len(fields) < 1 {
- return dec.formatError("'d' with no fields")
- }
- val, err := strconv.ParseFloat(fields[0], 32)
- if err != nil {
- return dec.formatError("'d' parse float error")
- }
- dec.matCurrent.Opacity = float32(val)
- return nil
- }
- // Parses ambient reflectivity:
- // Ka r g b
- func (dec *Decoder) parseKa(fields []string) error {
- if len(fields) < 3 {
- return dec.formatError("'Ka' with less than 3 fields")
- }
- var colors [3]float32
- for pos, f := range fields[:3] {
- val, err := strconv.ParseFloat(f, 32)
- if err != nil {
- return err
- }
- colors[pos] = float32(val)
- }
- dec.matCurrent.Ambient.Set(colors[0], colors[1], colors[2])
- return nil
- }
- // Parses diffuse reflectivity:
- // Kd r g b
- func (dec *Decoder) parseKd(fields []string) error {
- if len(fields) < 3 {
- return dec.formatError("'Kd' with less than 3 fields")
- }
- var colors [3]float32
- for pos, f := range fields[:3] {
- val, err := strconv.ParseFloat(f, 32)
- if err != nil {
- return err
- }
- colors[pos] = float32(val)
- }
- dec.matCurrent.Diffuse.Set(colors[0], colors[1], colors[2])
- return nil
- }
- // Parses emissive color:
- // Ke r g b
- func (dec *Decoder) parseKe(fields []string) error {
- if len(fields) < 3 {
- return dec.formatError("'Ke' with less than 3 fields")
- }
- var colors [3]float32
- for pos, f := range fields[:3] {
- val, err := strconv.ParseFloat(f, 32)
- if err != nil {
- return err
- }
- colors[pos] = float32(val)
- }
- dec.matCurrent.Emissive.Set(colors[0], colors[1], colors[2])
- return nil
- }
- // Parses specular reflectivity:
- // Ks r g b
- func (dec *Decoder) parseKs(fields []string) error {
- if len(fields) < 3 {
- return dec.formatError("'Ks' with less than 3 fields")
- }
- var colors [3]float32
- for pos, f := range fields[:3] {
- val, err := strconv.ParseFloat(f, 32)
- if err != nil {
- return err
- }
- colors[pos] = float32(val)
- }
- dec.matCurrent.Specular.Set(colors[0], colors[1], colors[2])
- return nil
- }
- // Parses optical density, also known as index of refraction
- // Ni <optical_density>
- func (dec *Decoder) parseNi(fields []string) error {
- if len(fields) < 1 {
- return dec.formatError("'Ni' with no fields")
- }
- val, err := strconv.ParseFloat(fields[0], 32)
- if err != nil {
- return dec.formatError("'d' parse float error")
- }
- dec.matCurrent.Refraction = float32(val)
- return nil
- }
- // Parses specular exponent
- // Ns <specular_exponent>
- func (dec *Decoder) parseNs(fields []string) error {
- if len(fields) < 1 {
- return dec.formatError("'Ns' with no fields")
- }
- val, err := strconv.ParseFloat(fields[0], 32)
- if err != nil {
- return dec.formatError("'d' parse float error")
- }
- dec.matCurrent.Shininess = float32(val)
- return nil
- }
- // Parses illumination model (0 to 10)
- // illum <ilum_#>
- func (dec *Decoder) parseIllum(fields []string) error {
- if len(fields) < 1 {
- return dec.formatError("'illum' with no fields")
- }
- val, err := strconv.ParseUint(fields[0], 10, 32)
- if err != nil {
- return dec.formatError("'d' parse int error")
- }
- dec.matCurrent.Illum = int(val)
- return nil
- }
- // Parses color texture linked to the diffuse reflectivity of the material
- // map_Kd [-options] <filename>
- func (dec *Decoder) parseMapKd(fields []string) error {
- if len(fields) < 1 {
- return dec.formatError("No fields")
- }
- dec.matCurrent.MapKd = fields[0]
- return nil
- }
- func (dec *Decoder) formatError(msg string) error {
- return fmt.Errorf("%s in line:%d", msg, dec.line)
- }
- func (dec *Decoder) appendWarn(ftype string, msg string) {
- wline := fmt.Sprintf("%s(%d): %s", ftype, dec.line, msg)
- dec.Warnings = append(dec.Warnings, wline)
- }
|