diff --git a/camera.go b/camera.go new file mode 100644 index 0000000..18891a5 --- /dev/null +++ b/camera.go @@ -0,0 +1,78 @@ +package goga + +const ( + default_camera_pos_x = 5 + default_camera_pos_y = 5 + default_camera_pos_z = 5 + default_camera_up_x = 0 + default_camera_up_y = 0 + default_camera_up_z = 1 + default_camera_fov = 60 + default_camera_znear = 1 + default_camera_zfar = 20 +) + +type Camera struct { + Viewport Vec4 + Position, LookAt, Up Vec3 + Fov, Ratio, Znear, Zfar float64 + Projection, Ortho3D, View Mat4 + Ortho Mat3 +} + +// Creates a new 2D/3D camera. +// Pass the viewport as arguments. +func NewCamera(x, y, width, height int) *Camera { + camera := &Camera{} + camera.Position = Vec3{default_camera_pos_x, default_camera_pos_y, default_camera_pos_z} + camera.Up = Vec3{default_camera_up_x, default_camera_up_y, default_camera_up_z} + camera.Fov = default_camera_fov + camera.Znear = default_camera_znear + camera.Zfar = default_camera_zfar + camera.SetViewport(x, y, width, height) + camera.CalcRatio() + + return camera +} + +// Updates viewport. +func (c *Camera) SetViewport(x, y, width, height int) { + c.Viewport = Vec4{float64(x), float64(y), float64(width), float64(height)} +} + +// Calculates viewport ratio (width/height). +func (c *Camera) CalcRatio() { + c.Ratio = (c.Viewport.Z - c.Viewport.X) / (c.Viewport.W - c.Viewport.Y) +} + +// Calculates projection matrix and returns it. +func (c *Camera) CalcProjection() *Mat4 { + c.Projection.Identity() + c.Projection.Perspective(c.Fov, c.Ratio, c.Znear, c.Zfar) + + return &c.Projection +} + +// Calculates orthogonal projection matrix and returns it. +func (c *Camera) CalcOrtho() *Mat3 { + c.Ortho.Identity() + c.Ortho.Ortho(c.Viewport) + + return &c.Ortho +} + +// Calculates 3D orthogonal projection matrix and returns it. +func (c *Camera) CalcOrtho3D() *Mat4 { + c.Ortho3D.Identity() + c.Ortho3D.Ortho(c.Viewport, c.Znear, c.Zfar) + + return &c.Ortho3D +} + +// Calculates view matrix and returns it. +func (c *Camera) CalcView() *Mat4 { + c.View.Identity() + c.View.LookAt(c.Position, c.LookAt, c.Up) + + return &c.View +} diff --git a/loader.go b/loader.go index b1ba7ce..ebd602e 100644 --- a/loader.go +++ b/loader.go @@ -1,11 +1,15 @@ package goga import ( + "bufio" + "errors" "github.com/go-gl/gl/v4.5-core/gl" "image" "image/draw" "image/png" "os" + "strconv" + "strings" ) // Loads textures from png files. @@ -56,3 +60,232 @@ func (p *PngLoader) Load(file string) (Res, error) { func (p *PngLoader) Ext() string { return "png" } + +// Standford ply file resource. +type Ply struct { + name string + path string + ext string + + firstLine, data, hasVertex, hasTexCoord, hasNormal bool + elements, faces int + indices []uint32 + vertices, texCoords, normals []float32 + + IndexBuffer, VertexBuffer, TexCoordBuffer, NormalBuffer *VBO +} + +// Returns the name of this resource. +func (p *Ply) GetName() string { + return p.name +} + +// Sets the name of this resource. +func (p *Ply) SetName(name string) { + p.name = name +} + +// Returns the path of this resource. +func (p *Ply) GetPath() string { + return p.path +} + +// Sets the path of this resource. +func (p *Ply) SetPath(path string) { + p.path = path +} + +// Returns the file extension of this resource. +func (p *Ply) GetExt() string { + return p.ext +} + +// Sets the file extension of this resource. +func (p *Ply) SetExt(ext string) { + p.ext = ext +} + +// Loads ply files and creates VBOs within the Ply resource. +// The indices must be present as triangles. +// Expected type is float32. If it fails to parse, it will panic. +type PlyLoader struct { + VboUsage uint32 +} + +func (p *PlyLoader) Load(file string) (Res, error) { + handle, err := os.Open(file) + defer handle.Close() + + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(handle) + ply := Ply{} + ply.indices = make([]uint32, 0) + ply.vertices = make([]float32, 0) + ply.texCoords = make([]float32, 0) + ply.normals = make([]float32, 0) + + for scanner.Scan() { + line := strings.ToLower(scanner.Text()) + + if ply.data && ply.elements == 0 && ply.faces > 0 { + ply.faces-- + + if err := ply.parseIndices(line); err != nil { + return nil, err + } + } + + if ply.data && ply.elements > 0 { + ply.elements-- + + if err := ply.parseData(line); err != nil { + return nil, err + } + } + + if err := ply.parseHeader(line); err != nil { + return nil, err + } + + ply.firstLine = false + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + index, vertex, texCoord, normal := ply.createVBOs(p.VboUsage) + ply.IndexBuffer = index + ply.VertexBuffer = vertex + ply.TexCoordBuffer = texCoord + ply.NormalBuffer = normal + + return &ply, nil +} + +func (p *Ply) createVBOs(vboUsage uint32) (*VBO, *VBO, *VBO, *VBO) { + index := NewVBO(gl.ELEMENT_ARRAY_BUFFER) + index.Fill(gl.Ptr(p.indices), 4, len(p.indices), vboUsage) + + vertex := NewVBO(gl.ARRAY_BUFFER) + vertex.Fill(gl.Ptr(p.vertices), 4, len(p.vertices), vboUsage) + + var texCoord, normal *VBO + + if p.hasTexCoord { + texCoord = NewVBO(gl.ARRAY_BUFFER) + texCoord.Fill(gl.Ptr(p.texCoords), 4, len(p.texCoords), vboUsage) + } + + if p.hasNormal { + normal = NewVBO(gl.ARRAY_BUFFER) + normal.Fill(gl.Ptr(p.normals), 4, len(p.normals), vboUsage) + } + + return index, vertex, texCoord, normal +} + +func (p *Ply) parseHeader(line string) error { + if p.firstLine && line != "ply" { // make sure it's a ply file + return errors.New("File is not of type ply") + } else if strings.Contains(line, "element vertex") { // number of elements + elements, err := strconv.Atoi(line[15:]) + + if err != nil { + return errors.New("Elements could not be parsed") + } + + p.elements = elements + } else if strings.Contains(line, "property float") { + line = line[15:] + + if line == "x" || line == "y" || line == "z" { + p.hasVertex = true + } else if line == "nx" || line == "ny" || line == "nz" { + p.hasNormal = true + } else if line == "s" || line == "t" { + p.hasTexCoord = true + } + } else if strings.Contains(line, "element face") { // number of faces + faces, err := strconv.Atoi(line[13:]) + + if err != nil { + return errors.New("Faces could not be parsed") + } + + p.faces = faces + } else if strings.Contains(line, "end_header") { + p.data = true + } + + return nil +} + +func (p *Ply) parseData(line string) error { + if !p.hasVertex { + return errors.New("ply must have vertex data") + } + + parts := strings.Split(line, " ") + i := 0 + + p.vertices = append(p.vertices, parseFloat32(parts[0])) + p.vertices = append(p.vertices, parseFloat32(parts[1])) + p.vertices = append(p.vertices, parseFloat32(parts[2])) + + if p.hasNormal { + i += 3 + + p.normals = append(p.normals, parseFloat32(parts[3])) + p.normals = append(p.normals, parseFloat32(parts[4])) + p.normals = append(p.normals, parseFloat32(parts[5])) + } + + if p.hasTexCoord { + p.texCoords = append(p.texCoords, parseFloat32(parts[3+i])) + p.texCoords = append(p.texCoords, parseFloat32(parts[4+i])) + } + + return nil +} + +func parseFloat32(str string) float32 { + float, err := strconv.ParseFloat(str, 32) + + if err != nil { + panic(err) + } + + return float32(float) +} + +func (p *Ply) parseIndices(line string) error { + parts := strings.Split(line, " ") + + if len(parts) != 4 || parts[0] != "3" { + return errors.New("Expected triangles for indices") + } + + p.indices = append(p.indices, parseUint32(parts[1])) + p.indices = append(p.indices, parseUint32(parts[2])) + p.indices = append(p.indices, parseUint32(parts[3])) + + return nil +} + +func parseUint32(str string) uint32 { + i, err := strconv.Atoi(str) + + if err != nil { + panic(err) + } + + return uint32(i) +} + +func (p *PlyLoader) Ext() string { + return "ply" +}