Removed shader, added 3D rendering (no demo yet, coming next), started bitmap font rendering, a few fixes and improvements.

This commit is contained in:
Marvin Blum
2016-05-07 20:26:36 +02:00
parent 1d974566de
commit 139e668233
15 changed files with 809 additions and 106 deletions

View File

@@ -5,7 +5,11 @@ Game engine written in Go using OpenGL and GLFW. Mostly for 2D rendering, but al
## Install ## Install
go-game requires OpenGL and GLFW. The following three steps install everything you need:
``` ```
go get github.com/go-gl/gl/v4.5-core/gl
go get github.com/go-gl/glfw/v3.1/glfw
go get github.com/DeKugelschieber/go-game go get github.com/DeKugelschieber/go-game
``` ```
@@ -23,3 +27,7 @@ You can find some examples within the demo folder. For full reference visit: htt
- 3.1 - 3.1
To use an older GL version, you need to replace the GL imports in package goga. It should mostly be compatible down to 3.x. To use an older GL version, you need to replace the GL imports in package goga. It should mostly be compatible down to 3.x.
## License
MIT

View File

@@ -0,0 +1,53 @@
ply
format ascii 1.0
comment Created by Blender 2.69 (sub 0) - www.blender.org, source file: ''
element vertex 26
property float x
property float y
property float z
property float nx
property float ny
property float nz
property float s
property float t
element face 12
property list uchar uint vertex_indices
end_header
1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 0.499999 0.500000
1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.499999 0.749956
-1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.250043 0.749957
1.000000 0.999999 1.000000 -0.000000 -0.000000 1.000000 0.499998 0.250043
-1.000000 1.000000 1.000000 -0.000000 -0.000000 1.000000 0.250041 0.250044
0.999999 -1.000001 1.000000 -0.000000 -0.000000 1.000000 0.499996 0.000087
1.000000 1.000000 -1.000000 1.000000 0.000000 -0.000000 0.749956 0.749956
1.000000 0.999999 1.000000 1.000000 0.000000 -0.000000 0.749956 0.999913
1.000000 -1.000000 -1.000000 1.000000 0.000000 -0.000000 0.499999 0.749956
1.000000 -1.000000 -1.000000 -0.000000 -1.000000 -0.000000 0.499999 0.749956
0.999999 -1.000001 1.000000 -0.000000 -1.000000 -0.000000 0.500000 0.999913
-1.000000 -1.000000 -1.000000 -0.000000 -1.000000 -0.000000 0.250043 0.749957
-1.000000 -1.000000 -1.000000 -1.000000 0.000000 -0.000000 0.250043 0.749957
-1.000000 -1.000000 1.000000 -1.000000 0.000000 -0.000000 0.250043 0.999913
-1.000000 1.000000 1.000000 -1.000000 0.000000 -0.000000 0.000087 0.999913
1.000000 0.999999 1.000000 0.000000 1.000000 0.000000 0.499998 0.250043
1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 0.499999 0.500000
-1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.250041 0.250044
-1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 0.250042 0.500001
1.000000 0.999999 1.000000 1.000000 -0.000001 0.000000 0.749956 0.999913
0.999999 -1.000001 1.000000 1.000000 -0.000001 0.000000 0.500000 0.999913
1.000000 -1.000000 -1.000000 1.000000 -0.000001 0.000000 0.499999 0.749956
-1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 0.250042 0.500001
-1.000000 -1.000000 1.000000 0.000000 -0.000000 1.000000 0.250040 0.000088
-1.000000 1.000000 -1.000000 -1.000000 0.000000 -0.000000 0.000087 0.749957
-1.000000 -1.000000 1.000000 -0.000000 -1.000000 0.000000 0.250043 0.999913
3 0 1 2
3 3 4 5
3 6 7 8
3 9 10 11
3 12 13 14
3 15 16 17
3 18 0 2
3 19 20 21
3 16 22 17
3 4 23 5
3 24 12 14
3 10 25 11

BIN
demo/model/assets/cube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

117
game.go
View File

@@ -22,14 +22,15 @@ const (
Default_shader_2D_model = "m" Default_shader_2D_model = "m"
Default_shader_2D_tex = "tex" Default_shader_2D_tex = "tex"
// source for 2D shader
default_shader_2d_vertex_src = `#version 130 default_shader_2d_vertex_src = `#version 130
uniform mat3 o, m; uniform mat3 o, m;
in vec2 vertex; in vec2 vertex;
in vec2 texCoord; in vec2 texCoord;
out vec2 tc; out vec2 tc;
void main(){ void main(){
tc = texCoord; tc = texCoord;
gl_Position = vec4(o*m*vec3(vertex, 1.0), 1.0); gl_Position = vec4(o*m*vec3(vertex, 1.0), 1.0);
}` }`
default_shader_2d_fragment_src = `#version 130 default_shader_2d_fragment_src = `#version 130
precision highp float; precision highp float;
@@ -37,7 +38,61 @@ const (
in vec2 tc; in vec2 tc;
out vec4 color; out vec4 color;
void main(){ void main(){
color = texture(tex, tc); color = texture(tex, tc);
}`
// constants for default 3D shader
Default_shader_3D_vertex_attrib = "vertex"
Default_shader_3D_texcoord_attrib = "texCoord"
Default_shader_3D_pv = "pv"
Default_shader_3D_model = "m"
Default_shader_3D_tex = "tex"
// source for 3D shader
default_shader_3d_vertex_src = `#version 130
uniform mat4 pv, m;
in vec3 vertex;
in vec2 texCoord;
out vec2 tc;
void main(){
tc = texCoord;
gl_Position = pv*m*vec4(vertex, 1.0);
}`
default_shader_3d_fragment_src = `#version 130
precision highp float;
uniform sampler2D tex;
in vec2 tc;
out vec4 color;
void main(){
color = texture(tex, tc);
}`
// constants for default text shader
Default_shader_text_vertex_attrib = "vertex"
Default_shader_text_texcoord_attrib = "texCoord"
Default_shader_text_ortho = "o"
Default_shader_text_model = "m"
Default_shader_text_tex = "tex"
Default_shader_text_color = "color"
// source for text shader
default_shader_text_vertex_src = `#version 130
uniform mat3 o, m;
in vec2 vertex;
in vec2 texCoord;
out vec2 tc;
void main(){
tc = texCoord;
gl_Position = vec4(o*m*vec3(vertex, 1.0), 1.0);
}`
default_shader_text_fragment_src = `#version 130
precision highp float;
uniform sampler2D tex;
uniform vec4 color;
in vec2 tc;
out vec4 c;
void main(){
c = texture(tex, tc)*color;
}` }`
) )
@@ -73,8 +128,10 @@ var (
viewportHeight int viewportHeight int
// Default resources // Default resources
DefaultCamera *Camera DefaultCamera *Camera
Default2DShader *Shader Default2DShader *Shader
Default3DShader *Shader
DefaultTextShader *Shader
) )
func init() { func init() {
@@ -211,7 +268,7 @@ func initGoga(width, height int) {
DefaultCamera.CalcRatio() DefaultCamera.CalcRatio()
DefaultCamera.CalcOrtho() DefaultCamera.CalcOrtho()
// default shader // default 2D shader
shader, err := NewShader(default_shader_2d_vertex_src, default_shader_2d_fragment_src) shader, err := NewShader(default_shader_2d_vertex_src, default_shader_2d_fragment_src)
if err != nil { if err != nil {
@@ -222,25 +279,52 @@ func initGoga(width, height int) {
Default2DShader.BindAttrib(Default_shader_2D_vertex_attrib) Default2DShader.BindAttrib(Default_shader_2D_vertex_attrib)
Default2DShader.BindAttrib(Default_shader_2D_texcoord_attrib) Default2DShader.BindAttrib(Default_shader_2D_texcoord_attrib)
// default 3D shader
shader, err = NewShader(default_shader_3d_vertex_src, default_shader_3d_fragment_src)
if err != nil {
panic(err)
}
Default3DShader = shader
Default3DShader.BindAttrib(Default_shader_3D_vertex_attrib)
Default3DShader.BindAttrib(Default_shader_3D_texcoord_attrib)
// default text shader
shader, err = NewShader(default_shader_text_vertex_src, default_shader_text_fragment_src)
if err != nil {
panic(err)
}
DefaultTextShader = shader
DefaultTextShader.BindAttrib(Default_shader_text_vertex_attrib)
DefaultTextShader.BindAttrib(Default_shader_text_texcoord_attrib)
// settings and registration // settings and registration
ClearColorBuffer(true) ClearColorBuffer(true)
EnableAlphaBlending(true) EnableAlphaBlending(true)
AddLoader(&PngLoader{gl.LINEAR, false}) AddLoader(&PngLoader{gl.LINEAR, false})
AddLoader(&PlyLoader{gl.STATIC_DRAW}) AddLoader(&PlyLoader{gl.STATIC_DRAW})
AddSystem(NewSpriteRenderer(nil, nil, false)) AddSystem(NewSpriteRenderer(nil, nil, false))
AddSystem(NewModelRenderer(nil, nil, false))
AddSystem(NewCulling2D(0, 0, width, height)) AddSystem(NewCulling2D(0, 0, width, height))
} }
func cleanup() { func cleanup() {
// cleanup resources // cleanup resources
log.Printf("Cleaning up %v resources", len(resources)) log.Printf("Trying to cleaning up %v resources", len(resources))
dropped := 0
for _, res := range resources { for _, res := range resources {
if drop, ok := res.(Dropable); ok { if drop, ok := res.(Dropable); ok {
drop.Drop() drop.Drop()
dropped++
} }
} }
log.Printf("Dropped %v resources", dropped)
// cleanup systems // cleanup systems
log.Printf("Cleaning up %v systems", len(systems)) log.Printf("Cleaning up %v systems", len(systems))
@@ -259,6 +343,7 @@ func cleanup() {
log.Print("Cleaning up default resources") log.Print("Cleaning up default resources")
Default2DShader.Drop() Default2DShader.Drop()
DefaultTextShader.Drop()
} }
// Stops the game and closes the window. // Stops the game and closes the window.
@@ -287,6 +372,15 @@ func ClearDepthBuffer(do bool) {
} }
} }
func removeClearBuffer(buffer uint32) {
for i, b := range clearBuffer {
if b == buffer {
clearBuffer = append(clearBuffer[:i], clearBuffer[i+1:]...)
return
}
}
}
// Enables/Disables alpha blending by source alpha channel. // Enables/Disables alpha blending by source alpha channel.
// BLEND = SRC_ALPHA | ONE_MINUS_SRC_ALPHA // BLEND = SRC_ALPHA | ONE_MINUS_SRC_ALPHA
func EnableAlphaBlending(enable bool) { func EnableAlphaBlending(enable bool) {
@@ -332,12 +426,3 @@ func GetWidth() int {
func GetHeight() int { func GetHeight() int {
return viewportHeight return viewportHeight
} }
func removeClearBuffer(buffer uint32) {
for i, buffer := range clearBuffer {
if buffer == buffer {
clearBuffer = append(clearBuffer[:i], clearBuffer[i+1:]...)
return
}
}
}

View File

@@ -75,6 +75,25 @@ type Ply struct {
IndexBuffer, VertexBuffer, TexCoordBuffer, NormalBuffer *VBO IndexBuffer, VertexBuffer, TexCoordBuffer, NormalBuffer *VBO
} }
// Drops contained GL buffers.
func (p *Ply) Drop() {
if p.IndexBuffer != nil {
p.IndexBuffer.Drop()
}
if p.VertexBuffer != nil {
p.VertexBuffer.Drop()
}
if p.TexCoordBuffer != nil {
p.TexCoordBuffer.Drop()
}
if p.NormalBuffer != nil {
p.NormalBuffer.Drop()
}
}
// Returns the name of this resource. // Returns the name of this resource.
func (p *Ply) GetName() string { func (p *Ply) GetName() string {
return p.name return p.name

193
model.go Normal file
View File

@@ -0,0 +1,193 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
)
const (
model_renderer_name = "modelRenderer"
)
// Component representing a 3D mesh.
type Mesh struct {
Index, Vertex, TexCoord *VBO
Vao *VAO
}
// Creates a new mesh with given GL buffers.
// The VAO must be prepared by ModelRenderer.
func NewMesh(index, vertex, texcoord *VBO) *Mesh {
mesh := &Mesh{}
mesh.Index = index
mesh.Vertex = vertex
mesh.TexCoord = texcoord
CheckGLError()
return mesh
}
// Drops the VBOs and VAO contained in mesh.
// This must not be done, if mesh was filled from outer source (like a ply file).
func (m *Mesh) Drop() {
m.Index.Drop()
m.Vertex.Drop()
m.TexCoord.Drop()
m.Vao.Drop()
}
// Model is an actor having a 3D position, a texture and a 3D mesh.
type Model struct {
*Actor
*Pos3D
*Tex
*Mesh
}
// Creates a new model with given mesh and texture.
func NewModel(mesh *Mesh, tex *Tex) *Model {
model := &Model{}
model.Actor = NewActor()
model.Pos3D = NewPos3D()
model.Tex = tex
model.Mesh = mesh
model.Size = Vec3{1, 1, 1}
model.Scale = Vec3{1, 1, 1}
model.Visible = true
CheckGLError()
return model
}
// The model renderer is a system rendering models.
// It has a 3D position component, to move all models at once.
type ModelRenderer struct {
Pos3D
Shader *Shader
Camera *Camera
ortho bool
models []Model
}
// Creates a new model renderer using given shader and camera.
// If shader and/or camera are nil, the default one will be used.
// Orth can be set to true, to use orthogonal projection.
func NewModelRenderer(shader *Shader, camera *Camera, ortho bool) *ModelRenderer {
if shader == nil {
shader = Default3DShader
}
if camera == nil {
camera = DefaultCamera
}
renderer := &ModelRenderer{}
renderer.Shader = shader
renderer.Camera = camera
renderer.ortho = ortho
renderer.models = make([]Model, 0)
renderer.Size = Vec3{1, 1, 1}
renderer.Scale = Vec3{1, 1, 1}
CheckGLError()
return renderer
}
func (s *ModelRenderer) Cleanup() {}
// Prepares a model to be rendered by setting up its VAO.
func (s *ModelRenderer) Prepare(model *Model) {
model.Vao = NewVAO()
model.Vao.Bind()
s.Shader.EnableVertexAttribArrays()
model.Index.Bind()
model.Vertex.Bind()
model.Vertex.AttribPointer(s.Shader.GetAttribLocation(Default_shader_3D_vertex_attrib), 3, gl.FLOAT, false, 0)
model.TexCoord.Bind()
model.TexCoord.AttribPointer(s.Shader.GetAttribLocation(Default_shader_3D_texcoord_attrib), 2, gl.FLOAT, false, 0)
model.Vao.Unbind()
}
// Adds model to the renderer.
// Perpare it first!
func (s *ModelRenderer) Add(actor *Actor, pos *Pos3D, tex *Tex, mesh *Mesh) bool {
id := actor.GetId()
for _, model := range s.models {
if id == model.Actor.GetId() {
return false
}
}
s.models = append(s.models, Model{actor, pos, tex, mesh})
return true
}
// Removes model from renderer.
func (s *ModelRenderer) Remove(actor *Actor) bool {
return s.RemoveById(actor.GetId())
}
// Removes model from renderer by ID.
func (s *ModelRenderer) RemoveById(id ActorId) bool {
for i, model := range s.models {
if model.Actor.GetId() == id {
s.models = append(s.models[:i], s.models[i+1:]...)
return true
}
}
return false
}
// Removes all sprites.
func (s *ModelRenderer) RemoveAll() {
s.models = make([]Model, 0)
}
// Returns number of sprites.
func (s *ModelRenderer) Len() int {
return len(s.models)
}
func (s *ModelRenderer) GetName() string {
return model_renderer_name
}
// Render models.
func (s *ModelRenderer) Update(delta float64) {
s.Shader.Bind()
s.Shader.SendUniform1i(Default_shader_3D_tex, 0)
if s.ortho {
s.Shader.SendMat4(Default_shader_3D_pv, *MultMat4(s.Camera.CalcOrtho3D(), s.CalcModel()))
} else {
pv := s.Camera.CalcProjection()
pv.Mult(s.Camera.CalcView())
s.Shader.SendMat4(Default_shader_3D_pv, *pv)
}
var tid uint32
for i := range s.models {
if !s.models[i].Visible {
continue
}
s.Shader.SendMat4(Default_shader_3D_model, *s.models[i].CalcModel())
s.models[i].Vao.Bind()
// prevent texture switching when not neccessary
if tid != s.models[i].Tex.GetId() {
tid = s.models[i].Tex.GetId()
s.models[i].Tex.Bind()
}
gl.DrawElements(gl.TRIANGLES, s.models[i].Index.Size(), gl.UNSIGNED_INT, nil)
}
}

View File

@@ -21,3 +21,21 @@ func GetTex(name string) (*Tex, error) {
return tex, nil return tex, nil
} }
// Finds and returns a Ply resource.
// If not found or when the resource is of wrong type, an error will be returned.
func GetPly(name string) (*Ply, error) {
res := GetResByName(name)
if res == nil {
return nil, errors.New("Resource not found")
}
ply, ok := res.(*Ply)
if !ok {
return nil, errors.New("Resource was not of type *Ply")
}
return ply, nil
}

View File

@@ -1,12 +0,0 @@
#version 130
precision highp float;
uniform sampler2D tex;
in vec2 tc;
out vec4 color;
void main(){
color = texture(tex, tc);
}

View File

@@ -1,13 +0,0 @@
#version 130
uniform mat3 o, m;
in vec2 vertex;
in vec2 texCoord;
out vec2 tc;
void main(){
tc = texCoord;
gl_Position = vec4(o*m*vec3(vertex, 1.0), 1.0);
}

View File

@@ -1,12 +0,0 @@
#version 130
precision highp float;
uniform sampler2D tex;
in vec2 tc;
out vec4 color;
void main(){
color = texture(tex, tc);
}

View File

@@ -1,13 +0,0 @@
#version 130
uniform mat4 pv, m;
in vec3 vertex;
in vec2 texCoord;
out vec2 tc;
void main(){
tc = texCoord;
gl_Position = pv*m*vec4(vertex, 1.0);
}

View File

@@ -1,13 +0,0 @@
#version 130
precision highp float;
uniform sampler2D tex;
uniform vec4 color;
in vec2 tc;
out vec4 c;
void main(){
c = texture(tex, tc)*color;
}

View File

@@ -1,13 +0,0 @@
#version 130
uniform mat3 o, m;
in vec2 vertex;
in vec2 texCoord;
out vec2 tc;
void main(){
tc = texCoord;
gl_Position = vec4(o*m*vec3(vertex, 1.0), 1.0);
}

View File

@@ -15,19 +15,6 @@ type Sprite struct {
*Tex *Tex
} }
// The sprite renderer is a system rendering sprites.
// It has a 2D position componente, to move all sprites at once.
type SpriteRenderer struct {
Pos2D
Shader *Shader
Camera *Camera
sprites []Sprite
index, vertex, texCoord *VBO
vao *VAO
}
// Creates a new sprite with given texture. // Creates a new sprite with given texture.
func NewSprite(tex *Tex) *Sprite { func NewSprite(tex *Tex) *Sprite {
sprite := &Sprite{} sprite := &Sprite{}
@@ -43,6 +30,19 @@ func NewSprite(tex *Tex) *Sprite {
return sprite return sprite
} }
// The sprite renderer is a system rendering sprites.
// It has a 2D position component, to move all sprites at once.
type SpriteRenderer struct {
Pos2D
Shader *Shader
Camera *Camera
sprites []Sprite
index, vertex, texCoord *VBO
vao *VAO
}
// Creates a new sprite renderer using given shader and camera. // Creates a new sprite renderer using given shader and camera.
// If shader and/or camera are nil, the default one will be used. // If shader and/or camera are nil, the default one will be used.
func NewSpriteRenderer(shader *Shader, camera *Camera, flip bool) *SpriteRenderer { func NewSpriteRenderer(shader *Shader, camera *Camera, flip bool) *SpriteRenderer {
@@ -132,7 +132,7 @@ func (s *SpriteRenderer) GetName() string {
return sprite_renderer_name return sprite_renderer_name
} }
// Renders sprites. // Render sprites.
func (s *SpriteRenderer) Update(delta float64) { func (s *SpriteRenderer) Update(delta float64) {
s.Shader.Bind() s.Shader.Bind()
s.Shader.SendMat3(Default_shader_2D_ortho, *MultMat3(s.Camera.CalcOrtho(), s.CalcModel())) s.Shader.SendMat3(Default_shader_2D_ortho, *MultMat3(s.Camera.CalcOrtho(), s.CalcModel()))
@@ -147,6 +147,7 @@ func (s *SpriteRenderer) Update(delta float64) {
s.Shader.SendMat3(Default_shader_2D_model, *s.sprites[i].CalcModel()) s.Shader.SendMat3(Default_shader_2D_model, *s.sprites[i].CalcModel())
// prevent texture switching when not neccessary
if tid != s.sprites[i].Tex.GetId() { if tid != s.sprites[i].Tex.GetId() {
tid = s.sprites[i].Tex.GetId() tid = s.sprites[i].Tex.GetId()
s.sprites[i].Tex.Bind() s.sprites[i].Tex.Bind()

402
text.go Normal file
View File

@@ -0,0 +1,402 @@
package goga
/*
import (
"core"
"dp"
"encoding/json"
"geo"
"github.com/go-gl/gl/v4.5-core/gl"
"io/ioutil"
"util"
)
const (
char_padding = 2
)
// Returns a new renderable text object.
func NewText(font *Font, text string) *Text {
t := &Text{id: core.NextId()}
t.text = text
t.index = dp.NewVBO(gl.ELEMENT_ARRAY_BUFFER)
t.vertex = dp.NewVBO(gl.ARRAY_BUFFER)
t.texCoord = dp.NewVBO(gl.ARRAY_BUFFER)
t.vao = dp.NewVAO()
t.SetText(font, text)
t.Size = geo.Vec2{1, 1}
t.Scale = geo.Vec2{1, 1}
t.Visible = true
return t
}
type Character struct {
char byte
min, max, size geo.Vec2
offset float64
}
type Font struct {
Tex *dp.Tex
tileSize float64
CharPadding geo.Vec2
Space, Tab, Line float64
chars []Character
}
type jsonChar struct {
Char string
X, Y, Offset float64
}
// Creates a new font from texture. Characters must be added afterwards.
// The characters must be placed within a grid,
// the second parameter describes the width and height of one tile in pixel.
func NewFont(tex *dp.Tex, tileSize int) *Font {
font := &Font{}
font.Tex = tex
font.tileSize = float64(tileSize)
font.CharPadding = geo.Vec2{0.05, 0.05}
font.Space = 0.3
font.Tab = 1.2
font.Line = 1
font.chars = make([]Character, 0)
return font
}
// Loads characters from JSON file.
// Format:
//
// [
// {
// "char": "a",
// "x": 0,
// "y": 0,
// "offset": 0
// },
// ...
// ]
//
// Where x and y start in the upper left corner of the texture, both of type int.
// Offset is optional and can be used to move a character up or down (relative to others).
// If cut is set to true, the characters will be true typed.
func (f *Font) LoadFromJson(path string, cut bool) error {
// load file content
content, err := ioutil.ReadFile(path)
if err != nil {
return err
}
// read json
chars := make([]jsonChar, 0)
if err = json.Unmarshal(content, &chars); err != nil {
return err
}
f.extractChars(chars, cut)
return nil
}
func (f *Font) extractChars(chars []jsonChar, cut bool) {
for _, char := range chars {
if len(char.Char) != 1 {
continue
}
var min, max, size geo.Vec2
if !cut {
min = geo.Vec2{char.X * f.tileSize, char.Y * f.tileSize}
max = geo.Vec2{min.X + f.tileSize, min.Y + f.tileSize}
size = geo.Vec2{1, 1}
} else {
min, max, size = f.cutChar(int(char.X), int(char.Y))
}
f.chars = append(f.chars, Character{char.Char[0], min, max, size, char.Offset})
}
}
func (f *Font) cutChar(x, y int) (geo.Vec2, geo.Vec2, geo.Vec2) {
minX := int(f.Tex.GetSize().X)
minY := int(f.Tex.GetSize().Y)
maxX := 0
maxY := 0
rgba := f.Tex.GetRGBA()
for ry := y * int(f.tileSize); ry < (y+1)*int(f.tileSize); ry++ {
for rx := x * int(f.tileSize); rx < (x+1)*int(f.tileSize); rx++ {
_, _, _, a := rgba.At(rx, ry).RGBA()
if a == 0 {
continue
}
if rx < minX {
minX = rx
} else if rx > maxX {
maxX = rx
}
if ry < minY {
minY = ry
} else if ry > maxY {
maxY = ry
}
}
}
minX -= char_padding
maxX += char_padding
minY -= char_padding
maxY += char_padding
texSize := f.Tex.GetSize()
min := geo.Vec2{float64(minX) / texSize.X, float64(maxY) / texSize.Y}
max := geo.Vec2{float64(maxX) / texSize.X, float64(minY) / texSize.Y}
// size
size := geo.Vec2{float64(maxX-minX) / f.tileSize, float64(maxY-minY) / f.tileSize}
return min, max, size
}
func (f *Font) getChar(char byte) *Character {
for _, character := range f.chars {
if character.char == char {
return &character
}
}
return nil
}
type Text struct {
*Actor
*Pos2D
id int
text string
bounds geo.Vec2
index, vertex, texCoord *dp.VBO
vao *dp.VAO
}
// Deletes GL buffers bound to this text.
func (t *Text) Drop() {
t.index.Drop()
t.vertex.Drop()
t.texCoord.Drop()
t.vao.Drop()
}
// Sets the given string as text and (re)creates buffers.
func (t *Text) SetText(font *Font, text string) {
t.text = text
indices := make([]uint32, len(text)*6)
vertices := make([]float32, len(text)*8)
texCoords := make([]float32, len(text)*8)
chars := 0
// create indices
var index uint32 = 0
for i := 0; i < len(text)*6; i += 6 {
indices[i] = index
indices[i+1] = index + 1
indices[i+2] = index + 2
indices[i+3] = index + 1
indices[i+4] = index + 2
indices[i+5] = index + 3
index += 4
}
// create vertices/texCoords
index = 0
offset := geo.Vec2{}
var width, height float64
for i := 0; i < len(text)*8 && int(index) < len(text); i += 8 {
c := font.getChar(text[index])
index++
// whitespace and new line
if text[index-1] == ' ' {
offset.X += font.Space
i -= 8
continue
}
if text[index-1] == '\n' {
offset.X = 0
offset.Y -= font.Line
i -= 8
continue
}
if text[index-1] == '\t' {
offset.X += font.Tab
i -= 8
continue
}
// character not found
if c == nil {
i -= 8
continue
}
// usual character
vertices[i] = float32(offset.X)
vertices[i+1] = float32(offset.Y + c.offset)
vertices[i+2] = float32(offset.X + c.size.X)
vertices[i+3] = float32(offset.Y + c.offset)
vertices[i+4] = float32(offset.X)
vertices[i+5] = float32(offset.Y + c.size.Y + c.offset)
vertices[i+6] = float32(offset.X + c.size.X)
vertices[i+7] = float32(offset.Y + c.size.Y + c.offset)
texCoords[i] = float32(c.min.X)
texCoords[i+1] = float32(c.min.Y)
texCoords[i+2] = float32(c.max.X)
texCoords[i+3] = float32(c.min.Y)
texCoords[i+4] = float32(c.min.X)
texCoords[i+5] = float32(c.max.Y)
texCoords[i+6] = float32(c.max.X)
texCoords[i+7] = float32(c.max.Y)
offset.X += c.size.X + font.CharPadding.X
chars++
if offset.X > width {
width = offset.X
}
if offset.Y*-1+font.Line > height {
height = offset.Y*-1 + font.Line
}
}
t.bounds = geo.Vec2{width, height}
// fill GL buffer
t.index.Fill(gl.Ptr(indices[:chars*6]), 4, chars*6, gl.STATIC_DRAW)
t.vertex.Fill(gl.Ptr(vertices[:chars*8]), 4, chars*8, gl.STATIC_DRAW)
t.texCoord.Fill(gl.Ptr(texCoords[:chars*8]), 4, chars*8, gl.STATIC_DRAW)
util.CheckGLError()
}
func (t *Text) GetId() int {
return t.id
}
// Returns the text as string.
func (t *Text) GetText() string {
return t.text
}
// Returns bounds of text, which is the size of characters.
func (t *Text) GetBounds() geo.Vec2 {
return geo.Vec2{t.bounds.X * t.Size.X * t.Scale.X, t.bounds.Y * t.Size.Y * t.Scale.Y}
}
type TextRenderer struct {
Pos2D
Shader *dp.Shader
Camera *Camera
Font *Font
Color geo.Vec4
texts []*Text
}
// Creates a new text renderer using given shader, camera and font.
// If shader and/or camera are nil, the default one will be used.
func NewTextRenderer(shader *dp.Shader, camera *Camera, font *Font) *TextRenderer {
renderer := &TextRenderer{}
renderer.Shader = shader
renderer.Camera = camera
renderer.Font = font
renderer.Color = geo.Vec4{1, 1, 1, 1}
renderer.texts = make([]*Text, 0)
renderer.Size = geo.Vec2{1, 1}
renderer.Scale = geo.Vec2{1, 1}
return renderer
}
// Prepares a text for rendering.
func (r *TextRenderer) Prepare(text *Text) {
text.vao = dp.NewVAO()
text.vao.Bind()
r.Shader.EnableVertexAttribArrays()
text.index.Bind()
text.vertex.Bind()
text.vertex.AttribPointer(r.Shader.GetAttribLocation(TEXTRENDERER_VERTEX_ATTRIB), 2, gl.FLOAT, false, 0)
text.texCoord.Bind()
text.texCoord.AttribPointer(r.Shader.GetAttribLocation(TEXTRENDERER_TEXCOORD_ATTRIB), 2, gl.FLOAT, false, 0)
text.vao.Unbind()
}
// Adds text to the renderer.
func (r *TextRenderer) Add(text *Text) {
r.texts = append(r.texts, text)
}
// Returns text by ID.
func (r *TextRenderer) Get(id int) *Text {
for _, text := range r.texts {
if text.GetId() == id {
return text
}
}
return nil
}
// Removes text from renderer by ID.
func (r *TextRenderer) Remove(id int) *Text {
for i, text := range r.texts {
if text.GetId() == id {
r.texts = append(r.texts[:i], r.texts[i+1:]...)
return text
}
}
return nil
}
// Removes all sprites.
func (r *TextRenderer) Clear() {
r.texts = make([]*Text, 0)
}
// Renders sprites.
func (r *TextRenderer) Render() {
r.Shader.Bind()
r.Shader.SendMat3(TEXTRENDERER_ORTHO, *geo.MultMat3(r.Camera.CalcOrtho(), r.CalcModel()))
r.Shader.SendUniform1i(TEXTRENDERER_TEX, 0)
r.Shader.SendUniform4f(TEXTRENDERER_COLOR, float32(r.Color.X), float32(r.Color.Y), float32(r.Color.Z), float32(r.Color.W))
r.Font.Tex.Bind()
for i := range r.texts {
if !r.texts[i].Visible {
continue
}
r.texts[i].vao.Bind()
r.Shader.SendMat3(TEXTRENDERER_MODEL, *r.texts[i].CalcModel())
gl.DrawElements(gl.TRIANGLES, r.texts[i].index.Size(), gl.UNSIGNED_INT, nil)
}
}
*/