package main // This reads obj files? import ( "bufio" "fmt" "io" "log" "math" "strings" ) type Vec3f struct { X, Y, Z float32 } type Vec2i struct { X, Y int } type Vec2f struct { X, Y float32 } // A ROW MAJOR matrix type Mat44f [16]float32 func (m *Mat44f) Set(x int, y int, val float32) { m[x+y*4] = val } func (m *Mat44f) Get(x int, y int) float32 { return m[x+y*4] } func (m *Mat44f) ZeroFill() { for i := range m { m[i] = 0 } } func (m *Mat44f) SetIdentity() { m.ZeroFill() for i := range 4 { m.Set(i, i, 1) } } // Compute the projection matrix, filling the given matrix. FOV is in degrees func (m *Mat44f) SetProjection(fov float32, near float32, far float32) { // Projection matrix is (ROW MAJOR!) // S 0 0 0 // 0 S 0 0 // 0 0 -f/(f-n) -1 // 0 0 -fn/(f-n) 0 // where S (scale) is 1 / tan(fov / 2) (assuming fov is radians) m.ZeroFill() scale := float32(1 / math.Tan(float64(fov)*0.5*math.Pi/180)) m.Set(0, 0, scale) m.Set(1, 1, scale) m.Set(2, 2, -far/(far-near)) m.Set(3, 2, -1) m.Set(2, 3, -far*near/(far-near)) } func (m *Mat44f) SetTranslation(x, y, z float32) { m.SetIdentity() m.Set(0, 3, x) // Let user decide how to offset x m.Set(1, 3, y) // Let user decide how to offset x m.Set(2, 3, z) // Get farther away from the face (user) } // Multiply the given point by our vector. Remember this is row-major order func (m *Mat44f) MultiplyPoint3(p Vec3f) Vec3f { var out Vec3f // We hope very much that Go will optimize the function calls for us, // along with computing the constants. out.X = p.X*m.Get(0, 0) + p.Y*m.Get(0, 1) + p.Z*m.Get(0, 2) + m.Get(0, 3) out.Y = p.X*m.Get(1, 0) + p.Y*m.Get(1, 1) + p.Z*m.Get(1, 2) + m.Get(1, 3) out.Z = p.X*m.Get(2, 0) + p.Y*m.Get(2, 1) + p.Z*m.Get(2, 2) + m.Get(2, 3) w := p.X*m.Get(3, 0) + p.Y*m.Get(3, 1) + p.Z*m.Get(3, 2) + m.Get(3, 3) if w != 1 { out.X /= w out.Y /= w out.Z /= w } return out } // Multiply two 4x4 matrices together (not optimized) func (m *Mat44f) Multiply(m2 *Mat44f) Mat44f { var result Mat44f // This is the x and y of our resulting matrix for y := 0; y < 4; y++ { for x := 0; x < 4; x++ { for i := 0; i < 4; i++ { result[x+y*4] += m[i+y*4] * m2[x+i*4] } } } return result } // A single vertex generally has multiple items associated with it // when it's part of a face. type Vertex struct { Pos Vec3f Tex Vec3f } type Facef [3]Vertex // struct { // Vertices [3]Vec3f // TextureCoords [3]Vec2i // } type ObjModel struct { Vertices []Vec3f VTexture []Vec3f Faces []Facef } func (vi *Vec2i) ToF() Vec2f { return Vec2f{float32(vi.X), float32(vi.Y)} } func (vi *Vec3f) ToVec2i() Vec2i { return Vec2i{int(vi.X), int(vi.Y)} } func (v0 *Vec3f) Sub(v1 Vec3f) Vec3f { return Vec3f{ X: v0.X - v1.X, Y: v0.Y - v1.Y, Z: v0.Z - v1.Z, } } func (v0 *Vec3f) CrossProduct(v1 Vec3f) Vec3f { return Vec3f{ X: v0.Y*v1.Z - v0.Z*v1.Y, Y: v0.Z*v1.X - v0.X*v1.Z, Z: v0.X*v1.Y - v0.Y*v1.X, } } //func (v func (v *Vec3f) Normalize() Vec3f { l := float32(math.Sqrt(float64(v.MultSimp(v)))) return Vec3f{ X: v.X / l, Y: v.Y / l, Z: v.Z / l, } } func (v0 *Vec3f) MultSimp(v1 *Vec3f) float32 { return v0.X*v1.X + v0.Y*v1.Y + v0.Z*v1.Z } // Parse an obj file at the given reader. Only handles v and f right now func ParseObj(reader io.Reader) (*ObjModel, error) { result := ObjModel{ Vertices: make([]Vec3f, 0), Faces: make([]Facef, 0), } breader := bufio.NewReader(reader) done := false for !done { // Scan a line line, err := breader.ReadString('\n') if err != nil { if err == io.EOF { done = true } else { log.Printf("NOT EOF ERR?") return nil, err } } line = strings.Trim(line, " \t\n\r") if len(line) == 0 { continue } // Find the first "item", whatever that is. This also gets rid of comments // since we just don't use lines that start with # (no handler var t string _, err = fmt.Sscan(line, &t) if err != nil { log.Printf("SSCANF ERR") return nil, err } line = line[len(t):] if t == "v" { // Read a vertex, should be just three floats var v Vec3f _, err := fmt.Sscan(line, &v.X, &v.Y, &v.Z) if err != nil { return nil, err } result.Vertices = append(result.Vertices, v) } else if t == "vt" { // Read a vertex tex coord, should be just three floats too var vt Vec3f _, err := fmt.Sscan(line, &vt.X, &vt.Y, &vt.Z) if err != nil { return nil, err } result.VTexture = append(result.VTexture, vt) } else if t == "f" { // Read a face; in our example, it's always three sets. // For THIS example, we throw away those other values var face Facef var vi [3]int var vti [3]int var ti int _, err := fmt.Sscanf(line, "%d/%d/%d %d/%d/%d %d/%d/%d", &vi[0], &vti[0], &ti, &vi[1], &vti[1], &ti, &vi[2], &vti[2], &ti) if err != nil { return nil, err } for i := range 3 { if vi[i] > len(result.Vertices) || vi[i] < 1 { return nil, fmt.Errorf("Face vertex index out of bounds: %d", vi[i]) } face[i].Pos = result.Vertices[vi[i]-1] if vti[i] > len(result.VTexture) || vti[i] < 1 { return nil, fmt.Errorf("Face vertex texture index out of bounds: %d", vti[i]) } face[i].Tex = result.VTexture[vti[i]-1] } result.Faces = append(result.Faces, face) } } return &result, nil }