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 }