tofu

Making something with OpenGL in Go
Log | Files | Refs

object.go (7803B)


      1 package tofu
      2 
      3 import (
      4 	"bufio"
      5 	"errors"
      6 	"fmt"
      7 	"log"
      8 	"math"
      9 	"os"
     10 	"path/filepath"
     11 	"strconv"
     12 	"strings"
     13 
     14 	"github.com/go-gl/gl/v3.3-core/gl"
     15 )
     16 
     17 type ErrUnsupported string
     18 
     19 func (e ErrUnsupported) Error() string {
     20 	return string(e) + " unsupported, ignoring"
     21 }
     22 
     23 var NoIndex = math.MinInt
     24 
     25 type VertexIndex struct {
     26 	V, N, T int
     27 }
     28 
     29 type Face [3]VertexIndex
     30 
     31 type Object struct {
     32 	Vertices  []Vec3
     33 	TexCoords []Vec2
     34 	Normals   []Vec3
     35 	Tangents  []Vec3
     36 	Faces     []Face
     37 	Materials map[string]*Material
     38 	vao       *VAO
     39 }
     40 
     41 func DecodeObject(filename string) (*Object, error) {
     42 	f, err := os.Open(filename)
     43 	if err != nil {
     44 		return nil, fmt.Errorf("open file %s: %v", filename, err)
     45 	}
     46 	defer f.Close()
     47 	obj := new(Object)
     48 	s := bufio.NewScanner(f)
     49 	nl := 1
     50 	for s.Scan() {
     51 		if err := decodeObjectLine(obj, s.Text(), filename); err != nil {
     52 			var perr ErrUnsupported
     53 			if errors.As(err, &perr) {
     54 				//log.Printf("%s:%d %v", filename, nl, perr)
     55 			} else {
     56 				return nil, fmt.Errorf("%s:%d %s", filename, nl, err)
     57 			}
     58 		}
     59 		nl++
     60 	}
     61 	if err := s.Err(); err != nil {
     62 		return nil, err
     63 	}
     64 	return obj, nil
     65 }
     66 
     67 func decodeObjectLine(obj *Object, line string, filename string) error {
     68 	if len(line) == 0 || line[0] == '#' {
     69 		return nil
     70 	}
     71 	sf := strings.Fields(line)
     72 	if len(sf) == 0 {
     73 		return nil
     74 	}
     75 	switch t := sf[0]; t {
     76 	case "v":
     77 		if err := obj.addVertex(sf[1:]); err != nil {
     78 			return fmt.Errorf("addVertex: %v", err)
     79 		}
     80 	case "vt":
     81 		if err := obj.addTexCoord(sf[1:]); err != nil {
     82 			return fmt.Errorf("addTexCoords: %v", err)
     83 		}
     84 	case "vn":
     85 		if err := obj.addNormal(sf[1:]); err != nil {
     86 			return fmt.Errorf("addNormal: %v", err)
     87 		}
     88 	case "f":
     89 		if err := obj.addFace(sf[1:]); err != nil {
     90 			return fmt.Errorf("addFace: %v", err)
     91 		}
     92 	case "mtllib":
     93 		if len(sf) < 2 {
     94 			return fmt.Errorf("no mtllib file specified")
     95 		}
     96 		m, err := decodeMaterial(filepath.Join(filepath.Dir(filename), sf[1]))
     97 		if err != nil {
     98 			return fmt.Errorf("decode mtl file: %v", err)
     99 		}
    100 		if obj.Materials == nil {
    101 			obj.Materials = make(map[string]*Material)
    102 		}
    103 		obj.Materials[m.Name] = m
    104 		log.Printf("mtl: %+v", m)
    105 	default:
    106 		return ErrUnsupported(t)
    107 	}
    108 	return nil
    109 }
    110 
    111 func (obj *Object) addVertex(s []string) error {
    112 	if len(s) != 3 {
    113 		return fmt.Errorf("want 3 coordinates, got %d", len(s))
    114 	}
    115 	var v Vec3
    116 	for i := 0; i < 3; i++ {
    117 		f, err := strconv.ParseFloat(s[i], 32)
    118 		if err != nil {
    119 			return err
    120 		}
    121 		v[i] = float32(f)
    122 	}
    123 	obj.Vertices = append(obj.Vertices, v)
    124 	return nil
    125 }
    126 
    127 func (obj *Object) addTexCoord(s []string) error {
    128 	if len(s) != 2 {
    129 		log.Println("only 2D texture is supported, ignoring the third element")
    130 	}
    131 	var t Vec2
    132 	for i := 0; i < 2; i++ {
    133 		f, err := strconv.ParseFloat(s[i], 32)
    134 		if err != nil {
    135 			return err
    136 		}
    137 		t[i] = float32(f)
    138 	}
    139 	obj.TexCoords = append(obj.TexCoords, t)
    140 	return nil
    141 }
    142 
    143 func (obj *Object) addNormal(s []string) error {
    144 	if len(s) != 3 {
    145 		return fmt.Errorf("want 3 coordinates, got %d", len(s))
    146 	}
    147 	var n Vec3
    148 	for i := 0; i < 3; i++ {
    149 		f, err := strconv.ParseFloat(s[i], 32)
    150 		if err != nil {
    151 			return err
    152 		}
    153 		n[i] = float32(f)
    154 	}
    155 	obj.Normals = append(obj.Normals, n)
    156 	return nil
    157 }
    158 
    159 func (obj *Object) addFace(s []string) error {
    160 	if len(s) != 3 {
    161 		return fmt.Errorf("want 3 pairs of indices, got %d", len(s))
    162 	}
    163 	var f Face
    164 	for i := 0; i < 3; i++ {
    165 		tuple := strings.Split(s[i], "/")
    166 		if len(tuple) == 0 || len(tuple) > 3 {
    167 			return fmt.Errorf("invalid face: %q", s[i])
    168 		}
    169 		v, err := strconv.Atoi(tuple[0])
    170 		if err != nil {
    171 			return fmt.Errorf("invalid index: %s", tuple[0])
    172 		}
    173 		f[i].V = v - 1
    174 		if len(tuple) > 1 && tuple[1] != "" {
    175 			t, err := strconv.Atoi(tuple[1])
    176 			if err != nil {
    177 				return fmt.Errorf("invalid index: %s", tuple[1])
    178 			}
    179 			f[i].T = t - 1
    180 		} else {
    181 			f[i].T = NoIndex
    182 		}
    183 		if len(tuple) == 3 {
    184 			n, err := strconv.Atoi(tuple[2])
    185 			if err != nil {
    186 				return fmt.Errorf("invalid index: %s", tuple[2])
    187 			}
    188 			f[i].N = n - 1
    189 		} else {
    190 			f[i].N = NoIndex
    191 		}
    192 	}
    193 	obj.Faces = append(obj.Faces, f)
    194 	return nil
    195 }
    196 
    197 func (obj *Object) hasNormals() bool {
    198 	return obj.Faces[0][0].N != NoIndex
    199 }
    200 
    201 func (obj *Object) hasTexCoords() bool {
    202 	return obj.Faces[0][0].T != NoIndex
    203 }
    204 
    205 func (obj *Object) hasTangents() bool {
    206 	return obj.Tangents != nil
    207 }
    208 
    209 const stride = 3 + 2 + 3 + 3
    210 
    211 func (obj *Object) data() []float32 {
    212 	data := make([]float32, stride*3*len(obj.Faces))
    213 	for i, f := range obj.Faces {
    214 		for j := 0; j < 3; j++ {
    215 			data[stride*(3*i+j)] = obj.Vertices[f[j].V][0]
    216 			data[stride*(3*i+j)+1] = obj.Vertices[f[j].V][1]
    217 			data[stride*(3*i+j)+2] = obj.Vertices[f[j].V][2]
    218 			if obj.hasTexCoords() {
    219 				data[stride*(3*i+j)+3] = obj.TexCoords[f[j].T][0]
    220 				data[stride*(3*i+j)+4] = obj.TexCoords[f[j].T][1]
    221 			}
    222 			if obj.hasNormals() {
    223 				data[stride*(3*i+j)+5] = obj.Normals[f[j].N][0]
    224 				data[stride*(3*i+j)+6] = obj.Normals[f[j].N][1]
    225 				data[stride*(3*i+j)+7] = obj.Normals[f[j].N][2]
    226 			}
    227 			if obj.hasTangents() {
    228 				data[stride*(3*i+j)+8] = obj.Tangents[f[j].N][0]
    229 				data[stride*(3*i+j)+9] = obj.Tangents[f[j].N][1]
    230 				data[stride*(3*i+j)+10] = obj.Tangents[f[j].N][2]
    231 			}
    232 		}
    233 	}
    234 	return data
    235 }
    236 
    237 func (obj *Object) faceData() []uint32 {
    238 	fdata := make([]uint32, 3*len(obj.Faces))
    239 	for i := 0; i < 3*len(obj.Faces); i++ {
    240 		fdata[i] = uint32(i)
    241 	}
    242 	return fdata
    243 }
    244 
    245 func (obj *Object) SetNormals() {
    246 	obj.Normals = make([]Vec3, len(obj.Faces))
    247 	for i, f := range obj.Faces {
    248 		v := obj.Vertices[f[0].V].Sub(obj.Vertices[f[1].V])
    249 		w := obj.Vertices[f[0].V].Sub(obj.Vertices[f[2].V])
    250 		n := v.Cross(w).Normalize()
    251 		obj.Normals[i] = n
    252 		obj.Faces[i][0].N = i
    253 		obj.Faces[i][1].N = i
    254 		obj.Faces[i][2].N = i
    255 	}
    256 }
    257 
    258 func (obj *Object) SetTangents() {
    259 	if !obj.hasNormals() {
    260 		panic(fmt.Errorf("object does not have normals"))
    261 	}
    262 	if !obj.hasTexCoords() {
    263 		panic(fmt.Errorf("object does not have texCoords"))
    264 	}
    265 	obj.Tangents = make([]Vec3, len(obj.Normals))
    266 	for i, f := range obj.Normals {
    267 // WIP
    268 	}
    269 }
    270 
    271 // Load copies object data into the GPU.
    272 func (obj *Object) Load() {
    273 	obj.vao = newVAO(obj)
    274 }
    275 
    276 func (obj *Object) Draw(prog *Program) {
    277 	// TODO: performance?
    278 	obj.vao.bind()
    279 	for _, m := range obj.Materials {
    280 		prog.SetBool("Teapot.useTexture", true)
    281 		prog.SetTexture("Teapot.map_Kd", m.map_Kd)
    282 		prog.SetTexture("Teapot.map_Bump", m.map_Bump)
    283 		prog.SetTexture("Teapot.map_Ks", m.map_Ks)
    284 	}
    285 	gl.DrawElements(gl.TRIANGLES,
    286 		int32(stride*3*len(obj.Faces)),
    287 		gl.UNSIGNED_INT, nil)
    288 }
    289 
    290 type VAO struct {
    291 	id  uint32
    292 	vbo *Buffer
    293 	ebo *Buffer
    294 }
    295 
    296 const (
    297 	UniformVertex    = 0
    298 	UniformColor     = 1
    299 	UniformTexCoords = 2
    300 	UniformNormal    = 3
    301 	UniformTangent   = 4
    302 )
    303 
    304 func newVAO(obj *Object) *VAO {
    305 	var id uint32
    306 	gl.GenVertexArrays(1, &id)
    307 	vao := &VAO{id: id, vbo: newBuffer(), ebo: newBuffer()}
    308 	vao.bind()
    309 	data := obj.data()
    310 	vao.setData(data)
    311 	vao.setAttribute(UniformVertex, 3, stride, 0)
    312 	vao.setAttribute(UniformTexCoords, 2, stride, 3)
    313 	vao.setAttribute(UniformNormal, 3, stride, 5)
    314 	vao.setAttribute(UniformTangent, 3, stride, 8)
    315 	fdata := obj.faceData()
    316 	vao.setFaces(fdata)
    317 	return vao
    318 }
    319 
    320 func (vao *VAO) bind() {
    321 	gl.BindVertexArray(vao.id)
    322 }
    323 
    324 func (vao *VAO) setAttribute(uniformNum uint32, size int, stride int, offset int) {
    325 	gl.VertexAttribPointerWithOffset(uniformNum, int32(size), gl.FLOAT,
    326 		false, int32(stride)*4, uintptr(offset)*4)
    327 	gl.EnableVertexAttribArray(uniformNum)
    328 }
    329 
    330 func (vao *VAO) setData(data []float32) {
    331 	gl.BindBuffer(gl.ARRAY_BUFFER, vao.vbo.id)
    332 	gl.BufferData(gl.ARRAY_BUFFER, len(data)*4,
    333 		gl.Ptr(data), gl.STATIC_DRAW)
    334 }
    335 
    336 func (vao *VAO) setFaces(data []uint32) {
    337 	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, vao.ebo.id)
    338 	gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(data)*4,
    339 		gl.Ptr(data), gl.STATIC_DRAW)
    340 }
    341 
    342 type Buffer struct {
    343 	id uint32
    344 }
    345 
    346 func newBuffer() *Buffer {
    347 	var id uint32
    348 	gl.GenBuffers(1, &id)
    349 	return &Buffer{id: id}
    350 }