package main // This reads obj files? import ( "bufio" "fmt" "io" "log" "math" "strings" ) // These should probably go somewhere else but they're here because they're tied // with the object format specification and I don't want a lot of granularity right now type Vec3f struct { X, Y, Z 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 // 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)) } // 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 } type Facef [3]Vec3f type ObjModel struct { Vertices []Vec3f Faces []Facef } // 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 vertex Vec3f _, err := fmt.Sscan(line, &vertex.X, &vertex.Y, &vertex.Z) if err != nil { return nil, err } result.Vertices = append(result.Vertices, vertex) } 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 ti int _, err := fmt.Sscanf(line, "%d/%d/%d %d/%d/%d %d/%d/%d", &vi[0], &ti, &ti, &vi[1], &ti, &ti, &vi[2], &ti, &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] = result.Vertices[vi[i]-1] } result.Faces = append(result.Faces, face) } } return &result, nil }