5 Commits

28 changed files with 752 additions and 395 deletions

5
CHANGELOG.md Normal file
View File

@@ -0,0 +1,5 @@
# Changelog
## 0.1_beta
* beta release

View File

@@ -1,14 +1,13 @@
# go-game (package "goga")
Game engine written in Go using OpenGL and GLFW. Mostly for 2D rendering, but also capable of rendering 3D, providing everything to get you started.
**Under heavy development, do not use yet!**
## 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/gl/v3.1-core/gl
go get github.com/go-gl/glfw/v3.1/glfw
go get github.com/DeKugelschieber/go-game
```
@@ -17,18 +16,16 @@ You also need a cgo compiler (typically gcc) and GL/GLFW development libraries a
## Usage
*WIP*
Examples can be found within the demo folder. For full reference visit: https://godoc.org/github.com/DeKugelschieber/go-game
## Dependencies
* https://github.com/go-gl/gl
- 4.5-core
- 3.1-core
* https://github.com/go-gl/glfw
- 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 a different GL version, you need to replace the GL imports in package goga.
## Contribute

View File

@@ -2,6 +2,9 @@
* ~~cleanup resources~~
* more logging
* limit FPS
* fullscreen
* simple access to default resources like GetTex()
* ~~limit FPS~~
- does this really work?
* ~~fullscreen~~
* ~~simple access to default resources like GetTex()~~
* ~~text rendering + font loading~~
* more options for GLFW

View File

@@ -1,7 +1,5 @@
package goga
import ()
// An actor ID is a unique integer,
// which can be used to reference an actor.
type ActorId uint64

83
default_shader.go Normal file
View File

@@ -0,0 +1,83 @@
package goga
const (
// constants for default 2D shader
Default_shader_2D_vertex_attrib = "vertex"
Default_shader_2D_texcoord_attrib = "texCoord"
Default_shader_2D_ortho = "o"
Default_shader_2D_model = "m"
Default_shader_2D_tex = "tex"
// source for 2D shader
default_shader_2d_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_2d_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 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;
}`
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

87
demo/input/input.go Normal file
View File

@@ -0,0 +1,87 @@
package main
import (
"github.com/DeKugelschieber/go-game"
"github.com/go-gl/glfw/v3.2/glfw"
)
const (
gopher_path = "src/github.com/DeKugelschieber/go-game/demo/input/assets/gopher.png"
)
type Game struct {
mouseX, mouseY float64
sprite *goga.Sprite
}
func (g *Game) Setup() {
// load texture
_, err := goga.LoadRes(gopher_path)
if err != nil {
panic(err)
}
// create sprite
tex, err := goga.GetTex("gopher.png")
if err != nil {
panic(err)
}
sprite := goga.NewSprite(tex)
sprite.Size.X = sprite.Size.X / 4
sprite.Size.Y = sprite.Size.Y / 4
g.sprite = sprite
renderer, ok := goga.GetSystemByName("spriteRenderer").(*goga.SpriteRenderer)
if !ok {
panic("Could not find renderer")
}
renderer.Add(sprite.Actor, sprite.Pos2D, sprite.Tex)
culling, ok := goga.GetSystemByName("culling2d").(*goga.Culling2D)
if !ok {
panic("Could not find culling")
}
culling.Add(sprite.Actor, sprite.Pos2D)
// register input listeners
goga.AddKeyboardListener(g)
goga.AddMouseListener(g)
}
func (g *Game) Update(delta float64) {}
func (g *Game) OnKeyEvent(key glfw.Key, code int, action glfw.Action, mod glfw.ModifierKey) {
// ESC
if key == 256 {
goga.Stop()
}
}
func (g *Game) OnMouseButton(button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
if button == 0 {
g.sprite.Pos.X = g.mouseX
g.sprite.Pos.Y = g.mouseY
}
}
func (g *Game) OnMouseMove(x float64, y float64) {
g.mouseX = x
g.mouseY = y
}
func (g *Game) OnMouseScroll(x float64, y float64) {}
func main() {
game := Game{}
options := goga.RunOptions{ClearColor: goga.Vec4{1, 1, 1, 0},
Resizable: true,
SetViewportOnResize: true,
ExitOnClose: true}
goga.Run(&game, &options)
}

View File

@@ -55,6 +55,7 @@ func main() {
options := goga.RunOptions{ClearColor: goga.Vec4{0, 0, 0, 0},
Resizable: true,
SetViewportOnResize: true,
ExitOnClose: true}
ExitOnClose: true,
Fullscreen: true}
goga.Run(&game, &options)
}

View File

@@ -1,28 +0,0 @@
[
{
"char": "A",
"x": 0,
"y": 0
},
{
"char": "b",
"x": 1,
"y": 0
},
{
"char": "e",
"x": 2,
"y": 0
},
{
"char": "r",
"x": 0,
"y": 1
},
{
"char": "'",
"x": 3,
"y": 0,
"offset": 0.5
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,113 @@
[
{"char":"a", "x": 0, "y": 0, "offset": 0},
{"char":"b", "x": 1, "y": 0, "offset": 0},
{"char":"c", "x": 2, "y": 0, "offset": 0},
{"char":"d", "x": 3, "y": 0, "offset": 0},
{"char":"e", "x": 4, "y": 0, "offset": 0},
{"char":"f", "x": 5, "y": 0, "offset": 0},
{"char":"g", "x": 6, "y": 0, "offset": 0},
{"char":"h", "x": 7, "y": 0, "offset": 0},
{"char":"i", "x": 8, "y": 0, "offset": 0},
{"char":"j", "x": 9, "y": 0, "offset": 0},
{"char":"k", "x": 10, "y": 0, "offset": 0},
{"char":"l", "x": 11, "y": 0, "offset": 0},
{"char":"m", "x": 12, "y": 0, "offset": 0},
{"char":"n", "x": 13, "y": 0, "offset": 0},
{"char":"o", "x": 14, "y": 0, "offset": 0},
{"char":"p", "x": 15, "y": 0, "offset": 0},
{"char":"q", "x": 16, "y": 0, "offset": 0},
{"char":"r", "x": 17, "y": 0, "offset": 0},
{"char":"s", "x": 18, "y": 0, "offset": 0},
{"char":"t", "x": 19, "y": 0, "offset": 0},
{"char":"u", "x": 20, "y": 0, "offset": 0},
{"char":"v", "x": 21, "y": 0, "offset": 0},
{"char":"w", "x": 22, "y": 0, "offset": 0},
{"char":"x", "x": 23, "y": 0, "offset": 0},
{"char":"y", "x": 24, "y": 0, "offset": 0},
{"char":"z", "x": 25, "y": 0, "offset": 0},
{"char":"A", "x": 0, "y": 0, "offset": 0},
{"char":"B", "x": 1, "y": 0, "offset": 0},
{"char":"C", "x": 2, "y": 0, "offset": 0},
{"char":"D", "x": 3, "y": 0, "offset": 0},
{"char":"E", "x": 4, "y": 0, "offset": 0},
{"char":"F", "x": 5, "y": 0, "offset": 0},
{"char":"G", "x": 6, "y": 0, "offset": 0},
{"char":"H", "x": 7, "y": 0, "offset": 0},
{"char":"I", "x": 8, "y": 0, "offset": 0},
{"char":"J", "x": 9, "y": 0, "offset": 0},
{"char":"K", "x": 10, "y": 0, "offset": 0},
{"char":"L", "x": 11, "y": 0, "offset": 0},
{"char":"M", "x": 12, "y": 0, "offset": 0},
{"char":"N", "x": 13, "y": 0, "offset": 0},
{"char":"O", "x": 14, "y": 0, "offset": 0},
{"char":"P", "x": 15, "y": 0, "offset": 0},
{"char":"Q", "x": 16, "y": 0, "offset": 0},
{"char":"R", "x": 17, "y": 0, "offset": 0},
{"char":"S", "x": 18, "y": 0, "offset": 0},
{"char":"T", "x": 19, "y": 0, "offset": 0},
{"char":"U", "x": 20, "y": 0, "offset": 0},
{"char":"V", "x": 21, "y": 0, "offset": 0},
{"char":"W", "x": 22, "y": 0, "offset": 0},
{"char":"X", "x": 23, "y": 0, "offset": 0},
{"char":"Y", "x": 24, "y": 0, "offset": 0},
{"char":"Z", "x": 25, "y": 0, "offset": 0},
{"char":"0", "x": 26, "y": 0, "offset": 0},
{"char":"1", "x": 27, "y": 0, "offset": 0},
{"char":"2", "x": 28, "y": 0, "offset": 0},
{"char":"3", "x": 29, "y": 0, "offset": 0},
{"char":"4", "x": 30, "y": 0, "offset": 0},
{"char":"5", "x": 31, "y": 0, "offset": 0},
{"char":"6", "x": 32, "y": 0, "offset": 0},
{"char":"7", "x": 33, "y": 0, "offset": 0},
{"char":"8", "x": 34, "y": 0, "offset": 0},
{"char":"9", "x": 35, "y": 0, "offset": 0},
{"char":"ö", "x": 36, "y": 0, "offset": 0},
{"char":"ä", "x": 37, "y": 0, "offset": 0},
{"char":"ü", "x": 38, "y": 0, "offset": 0},
{"char":"ß", "x": 39, "y": 0, "offset": 0},
{"char":"Ö", "x": 36, "y": 0, "offset": 0},
{"char":"Ä", "x": 37, "y": 0, "offset": 0},
{"char":"Ü", "x": 38, "y": 0, "offset": 0},
{"char":",", "x": 68, "y": 0, "offset": -0.25},
{"char":";", "x": 69, "y": 0, "offset": 0},
{"char":".", "x": 70, "y": 0, "offset": 0},
{"char":":", "x": 71, "y": 0, "offset": 0},
{"char":"-", "x": 72, "y": 0, "offset": 0},
{"char":"_", "x": 73, "y": 0, "offset": 0},
{"char":"^", "x": 40, "y": 0, "offset": 0},
{"char":"°", "x": 41, "y": 0, "offset": 0},
{"char":"!", "x": 42, "y": 0, "offset": 0},
{"char":"\"", "x": 43, "y": 0, "offset": 0},
{"char":"§", "x": 44, "y": 0, "offset": 0},
{"char":"$", "x": 45, "y": 0, "offset": 0},
{"char":"%", "x": 46, "y": 0, "offset": 0},
{"char":"&", "x": 47, "y": 0, "offset": 0},
{"char":"/", "x": 48, "y": 0, "offset": 0},
{"char":"(", "x": 49, "y": 0, "offset": 0},
{"char":")", "x": 50, "y": 0, "offset": 0},
{"char":"=", "x": 51, "y": 0, "offset": 0},
{"char":"?", "x": 52, "y": 0, "offset": 0},
{"char":"`", "x": 53, "y": 0, "offset": 0},
{"char":"²", "x": 54, "y": 0, "offset": 0},
{"char":"³", "x": 55, "y": 0, "offset": 0},
{"char":"{", "x": 56, "y": 0, "offset": 0},
{"char":"[", "x": 57, "y": 0, "offset": 0},
{"char":"]", "x": 58, "y": 0, "offset": 0},
{"char":"}", "x": 59, "y": 0, "offset": 0},
{"char":"\\", "x": 60, "y": 0, "offset": 0},
{"char":"+", "x": 61, "y": 0, "offset": 0},
{"char":"*", "x": 62, "y": 0, "offset": 0},
{"char":"~", "x": 63, "y": 0, "offset": 0},
{"char":"#", "x": 64, "y": 0, "offset": 0},
{"char":"<", "x": 65, "y": 0, "offset": 0},
{"char":">", "x": 66, "y": 0, "offset": 0},
{"char":"|", "x": 67, "y": 0, "offset": 0},
{"char":"'", "x": 68, "y": 0, "offset": 0},
{"char":"'", "x": 69, "y": 0, "offset": 0}
]

BIN
demo/text/assets/victor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -2,52 +2,70 @@ package main
import (
"github.com/DeKugelschieber/go-game"
"github.com/go-gl/gl/v3.2-core/gl"
)
const (
gopher_path = "src/github.com/DeKugelschieber/go-game/demo/sprite/assets/gopher.png"
font_path = "src/github.com/DeKugelschieber/go-game/demo/text/assets/victor.png"
font_json = "src/github.com/DeKugelschieber/go-game/demo/text/assets/victor.json"
)
type Game struct{}
func (g *Game) Setup() {
// load texture
_, err := goga.LoadRes(gopher_path)
pngLoader, ok := goga.GetLoaderByExt("png").(*goga.PngLoader)
if !ok {
panic("Could not get PNG loader")
}
pngLoader.KeepData = true
pngLoader.Filter = gl.NEAREST
_, err := goga.LoadRes(font_path)
if err != nil {
panic(err)
}
// create sprite
tex, err := goga.GetTex("gopher.png")
pngLoader.KeepData = false
pngLoader.Filter = gl.LINEAR
// create font
tex, err := goga.GetTex("victor.png")
if err != nil {
panic(err)
}
sprite := goga.NewSprite(tex)
renderer, ok := goga.GetSystemByName("spriteRenderer").(*goga.SpriteRenderer)
font := goga.NewFont(tex, 16)
if err := font.FromJson(font_json, true); err != nil {
panic(err)
}
// setup renderer
renderer, ok := goga.GetSystemByName("textRenderer").(*goga.TextRenderer)
if !ok {
panic("Could not find renderer")
}
renderer.Add(sprite.Actor, sprite.Pos2D, sprite.Tex)
renderer.Font = font
culling, ok := goga.GetSystemByName("culling2d").(*goga.Culling2D)
if !ok {
panic("Could not find culling")
}
culling.Add(sprite.Actor, sprite.Pos2D)
// create and add text
text := goga.NewText(font, "Hello, World!_")
text.Size = goga.Vec2{16, 16}
text.Pos = goga.Vec2{20, 20}
renderer.Prepare(text)
renderer.Add(text.Actor, text.Pos2D, text.TextComponent)
}
func (g *Game) Update(delta float64) {}
func main() {
game := Game{}
options := goga.RunOptions{ClearColor: goga.Vec4{1, 1, 1, 0},
options := goga.RunOptions{ClearColor: goga.Vec4{0, 0, 0, 0},
Resizable: true,
SetViewportOnResize: true,
ExitOnClose: true}

View File

@@ -7,7 +7,7 @@ type Dropable interface {
}
// Drops given GL objects.
// Objects must implement the Dropable inteface.
// Objects must implement the Dropable interface.
func Drop(objects []Dropable) {
for _, obj := range objects {
obj.Drop()

2
fbo.go
View File

@@ -1,7 +1,7 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
)
// Frame Buffer Object.

131
game.go
View File

@@ -1,8 +1,8 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/glfw/v3.1/glfw"
"github.com/go-gl/gl/v3.2-core/gl"
"github.com/go-gl/glfw/v3.2/glfw"
"log"
"math"
"runtime"
@@ -14,86 +14,6 @@ const (
default_height = uint32(400)
default_title = "Game"
default_exit_on_close = true
// constants for default 2D shader
Default_shader_2D_vertex_attrib = "vertex"
Default_shader_2D_texcoord_attrib = "texCoord"
Default_shader_2D_ortho = "o"
Default_shader_2D_model = "m"
Default_shader_2D_tex = "tex"
// source for 2D shader
default_shader_2d_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_2d_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 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;
}`
)
// If set in RunOptions, the function will be called on window resize.
@@ -109,6 +29,9 @@ type RunOptions struct {
SetViewportOnResize bool
ResizeCallbackFunc ResizeCallback
ExitOnClose bool
RefreshRate int
Fullscreen bool
MonitorId uint // index
}
// Main game object.
@@ -135,7 +58,8 @@ var (
)
func init() {
// GL functions must be called from main thread.
// GL functions must be called from main thread,
// so we disable multithreading by the runtime here.
log.Print("Locking OS thread")
runtime.LockOSThread()
}
@@ -168,32 +92,52 @@ func Run(game Game, options *RunOptions) {
title := default_title
exitOnClose := default_exit_on_close
if options != nil && options.Width > 0 {
if options != nil {
if options.Width > 0 {
width = options.Width
}
if options != nil && options.Height > 0 {
if options.Height > 0 {
height = options.Height
}
if options != nil && len(options.Title) > 0 {
if len(options.Title) > 0 {
title = options.Title
}
if options != nil {
exitOnClose = options.ExitOnClose
}
if options != nil && !options.Resizable {
if !options.Resizable {
glfw.WindowHint(glfw.Resizable, glfw.False)
}
}
wnd, err := glfw.CreateWindow(int(width), int(height), title, nil, nil)
var monitor *glfw.Monitor
if options != nil && options.Fullscreen {
monitors := glfw.GetMonitors()
if int(options.MonitorId) < len(monitors) {
monitor = monitors[options.MonitorId]
}
}
if monitor != nil {
videoMode := monitor.GetVideoMode()
width = uint32(videoMode.Width)
height = uint32(videoMode.Height)
}
wnd, err := glfw.CreateWindow(int(width), int(height), title, monitor, nil)
if err != nil {
panic("Error creating GLFW window: " + err.Error())
}
if options != nil && options.RefreshRate != 0 {
glfw.WindowHint(glfw.RefreshRate, options.RefreshRate)
}
// window event handlers
wnd.SetSizeCallback(func(w *glfw.Window, width, height int) {
if options == nil {
@@ -211,6 +155,8 @@ func Run(game Game, options *RunOptions) {
}
})
initInput(wnd)
// make GL context current
wnd.MakeContextCurrent()
@@ -235,8 +181,8 @@ func Run(game Game, options *RunOptions) {
delta := time.Duration(0)
var deltaSec float64
for running {
if exitOnClose && wnd.ShouldClose() {
for {
if !running || exitOnClose && wnd.ShouldClose() {
cleanup()
return
}
@@ -310,6 +256,7 @@ func initGoga(width, height int) {
AddSystem(NewModelRenderer(nil, nil, false))
AddSystem(NewCulling2D(0, 0, width, height))
AddSystem(NewKeyframeRenderer(nil, nil))
AddSystem(NewTextRenderer(nil, nil, nil)) // font must be set outside!
}
func cleanup() {

View File

@@ -1,7 +1,7 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
"log"
)

98
input.go Normal file
View File

@@ -0,0 +1,98 @@
package goga
import (
"github.com/go-gl/glfw/v3.2/glfw"
)
var (
keyboardListener []KeyboardListener
mouseListener []MouseListener
)
// Interface for keyboard input events.
// Implement and register to receive keyboard input.
type KeyboardListener interface {
OnKeyEvent(glfw.Key, int, glfw.Action, glfw.ModifierKey)
}
// Interface for mouse input events.
// Implement and register to receive mouse input.
type MouseListener interface {
OnMouseButton(glfw.MouseButton, glfw.Action, glfw.ModifierKey)
OnMouseMove(float64, float64)
OnMouseScroll(float64, float64)
}
func initInput(wnd *glfw.Window) {
wnd.SetKeyCallback(keyboardCallback)
wnd.SetMouseButtonCallback(mouseButtonCallback)
wnd.SetCursorPosCallback(mouseMoveCallback)
wnd.SetScrollCallback(mouseScrollCallback)
keyboardListener = make([]KeyboardListener, 0)
mouseListener = make([]MouseListener, 0)
}
func keyboardCallback(wnd *glfw.Window, key glfw.Key, code int, action glfw.Action, mod glfw.ModifierKey) {
for _, listener := range keyboardListener {
listener.OnKeyEvent(key, code, action, mod)
}
}
func mouseButtonCallback(wnd *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
for _, listener := range mouseListener {
listener.OnMouseButton(button, action, mod)
}
}
func mouseMoveCallback(wnd *glfw.Window, x float64, y float64) {
for _, listener := range mouseListener {
listener.OnMouseMove(x, float64(viewportHeight)-y)
}
}
func mouseScrollCallback(wnd *glfw.Window, x float64, y float64) {
for _, listener := range mouseListener {
listener.OnMouseScroll(x, y)
}
}
// Adds a new keyboard listener.
func AddKeyboardListener(listener KeyboardListener) {
keyboardListener = append(keyboardListener, listener)
}
// Removes given keyboard listener if found.
func RemoveKeyboardListener(listener KeyboardListener) {
for i, l := range keyboardListener {
if l == listener {
keyboardListener = append(keyboardListener[:i], keyboardListener[i+1:]...)
return
}
}
}
// Removes all registered keyboard listeners.
func RemoveAllKeyboardListener() {
keyboardListener = make([]KeyboardListener, 0)
}
// Adds a new mouse listener.
func AddMouseListener(listener MouseListener) {
mouseListener = append(mouseListener, listener)
}
// Removes given mouse listener if found.
func RemoveMouseListener(listener MouseListener) {
for i, l := range mouseListener {
if l == listener {
mouseListener = append(mouseListener[:i], mouseListener[i+1:]...)
return
}
}
}
// Removes all registered mouse listeners.
func RemoveAllMouseListener() {
mouseListener = make([]MouseListener, 0)
}

View File

@@ -1,7 +1,7 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
)
const (
@@ -14,6 +14,44 @@ type Keyframe struct {
Min, Max Vec2
}
// A set of keyframes making up an animation.
type KeyframeSet struct {
Keyframes []Keyframe
}
// Keyframe animation component.
// It has a start and an end frame, a play speed and option to loop.
type KeyframeAnimation struct {
Start, End int
Loop bool
Speed float64
Current int
Interpolation float64
}
// An animated sprite is a sprite with keyframe animation information.
// It will be updated and rendered by the KeyframeRenderer.
type AnimatedSprite struct {
*Actor
*Pos2D
*Tex
*KeyframeSet
*KeyframeAnimation
}
// The keyframe renderer renders animated sprites.
// It has a 2D position component, to move all sprites at once.
type KeyframeRenderer struct {
Pos2D
Shader *Shader
Camera *Camera
sprites []AnimatedSprite
index, vertex *VBO
vao *VAO
}
// Creates a new single keyframe with texture VBO.
func NewKeyframe(min, max Vec2) *Keyframe {
keyframe := &Keyframe{Min: min, Max: max}
@@ -36,11 +74,6 @@ func NewKeyframe(min, max Vec2) *Keyframe {
return keyframe
}
// A set of keyframes making up an animation.
type KeyframeSet struct {
Keyframes []Keyframe
}
// Creates a new empty keyframe set with given size.
func NewKeyframeSet() *KeyframeSet {
set := &KeyframeSet{}
@@ -55,31 +88,11 @@ func (s *KeyframeSet) Add(frame *Keyframe) int {
return len(s.Keyframes)
}
// Keyframe animation component.
// It has a start and an end frame, a play speed and option to loop.
type KeyframeAnimation struct {
Start, End int
Loop bool
Speed float64
Current int
Interpolation float64
}
// Creates a new keyframe animation with given start, end and loop.
func NewKeyframeAnimation(start, end int, loop bool, speed float64) *KeyframeAnimation {
return &KeyframeAnimation{start, end, loop, speed, 0, 0}
}
// An animated sprite is a sprite with keyframe animation information.
// It will be updated and rendered by the KeyframeRenderer.
type AnimatedSprite struct {
*Actor
*Pos2D
*Tex
*KeyframeSet
*KeyframeAnimation
}
// Creates a new animated sprite.
func NewAnimatedSprite(tex *Tex, set *KeyframeSet, width, height int) *AnimatedSprite {
sprite := &AnimatedSprite{}
@@ -101,19 +114,6 @@ func NewAnimatedSprite(tex *Tex, set *KeyframeSet, width, height int) *AnimatedS
return sprite
}
// The keyframe renderer renders animated sprites.
// It has a 2D position component, to move all sprites at once.
type KeyframeRenderer struct {
Pos2D
Shader *Shader
Camera *Camera
sprites []AnimatedSprite
index, vertex *VBO
vao *VAO
}
// Creates a new keyframe renderer using given shader and camera.
// If shader and/or camera are nil, the default one will be used.
func NewKeyframeRenderer(shader *Shader, camera *Camera) *KeyframeRenderer {

View File

@@ -3,7 +3,7 @@ package goga
import (
"bufio"
"errors"
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
"image"
"image/draw"
"image/png"
@@ -21,6 +21,27 @@ type PngLoader struct {
KeepData bool
}
// 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
}
// 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 *PngLoader) Load(file string) (Res, error) {
// load texture
imgFile, err := os.Open(file)
@@ -61,20 +82,6 @@ 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
}
// Drops contained GL buffers.
func (p *Ply) Drop() {
if p.IndexBuffer != nil {
@@ -124,13 +131,6 @@ 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()

View File

@@ -1,7 +1,7 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
)
const (
@@ -14,6 +14,26 @@ type Mesh struct {
Vao *VAO
}
// Model is an actor having a 3D position, a texture and a 3D mesh.
type Model struct {
*Actor
*Pos3D
*Tex
*Mesh
}
// 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 mesh with given GL buffers.
// The VAO must be prepared by ModelRenderer.
func NewMesh(index, vertex, texcoord *VBO) *Mesh {
@@ -36,14 +56,6 @@ func (m *Mesh) 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{}
@@ -60,18 +72,6 @@ func NewModel(mesh *Mesh, tex *Tex) *Model {
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.

14
pos.go
View File

@@ -8,6 +8,13 @@ type Pos2D struct {
M Mat3
}
// Position component for 3D objects
type Pos3D struct {
Pos, Size, Scale, RotPoint, Rot Vec3
Visible bool
M Mat4
}
// Creates a default initialized Pos2D.
func NewPos2D() *Pos2D {
m := Mat3{}
@@ -38,13 +45,6 @@ func (p *Pos2D) PointInRect(point Vec2) bool {
return point.X > p.Pos.X && point.X < p.Pos.X+p.Size.X*p.Scale.X && point.Y > p.Pos.Y && point.Y < p.Pos.Y+p.Size.Y*p.Scale.Y
}
// Position component for 3D objects
type Pos3D struct {
Pos, Size, Scale, RotPoint, Rot Vec3
Visible bool
M Mat4
}
// Creates a default initialized Pos3D.
func NewPos3D() *Pos3D {
m := Mat4{}

View File

@@ -2,7 +2,7 @@ package goga
import (
"errors"
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
"log"
"strings"
)

View File

@@ -1,7 +1,7 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
)
const (
@@ -15,6 +15,19 @@ type Sprite struct {
*Tex
}
// 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 with given texture.
func NewSprite(tex *Tex) *Sprite {
sprite := &Sprite{}
@@ -30,19 +43,6 @@ func NewSprite(tex *Tex) *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.
// If shader and/or camera are nil, the default one will be used.
func NewSpriteRenderer(shader *Shader, camera *Camera, flip bool) *SpriteRenderer {

2
tex.go
View File

@@ -1,7 +1,7 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
"image"
)

293
text.go
View File

@@ -1,69 +1,122 @@
package goga
/*
import (
"core"
"dp"
"encoding/json"
"geo"
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
"io/ioutil"
"util"
)
const (
char_padding = 2
text_renderer_name = "textRenderer"
)
// 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 {
type character struct {
char byte
min, max, size geo.Vec2
min, max, size 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 represents a texture mapped font.
// It can be loaded from JSON together with a texture.
type Font struct {
Tex *Tex
tileSize float64
CharPadding Vec2
Space, Tab, Line float64
chars []character
}
// Renderable text component.
// Use together with Text and create using NewText().
type TextComponent struct {
Color Vec4
text string
bounds Vec2
index, vertex, texCoord *VBO
vao *VAO
}
// Text is an actor representing text rendered as texture mapped font.
// Each Text has a position and its own buffers.
type Text struct {
*Actor
*Pos2D
*TextComponent
}
// The text renderer is a system rendering 2D texture mapped font.
// It has a 2D position component, to move all texts at once.
type TextRenderer struct {
Pos2D
Shader *Shader
Camera *Camera
Font *Font
texts []Text
}
// Creates a new font for given texture.
// The tile size specifies the size of one character tile on texture.
// Characters must be added afterwards.
func NewFont(tex *Tex, tileSize float64) *Font {
font := Font{}
font.Tex = tex
font.tileSize = float64(tileSize)
font.CharPadding = geo.Vec2{0.05, 0.05}
font.tileSize = tileSize
font.CharPadding = Vec2{0.05, 0.05}
font.Space = 0.3
font.Tab = 1.2
font.Line = 1
font.chars = make([]Character, 0)
font.chars = make([]character, 0)
return font
return &font
}
// Returns a new renderable text object.
func NewText(font *Font, textStr string) *Text {
text := Text{}
text.Actor = NewActor()
text.Pos2D = NewPos2D()
text.TextComponent = &TextComponent{}
text.index = NewVBO(gl.ELEMENT_ARRAY_BUFFER)
text.vertex = NewVBO(gl.ARRAY_BUFFER)
text.texCoord = NewVBO(gl.ARRAY_BUFFER)
text.vao = NewVAO()
text.SetText(font, textStr)
text.Color = Vec4{1, 1, 1, 1}
text.Size = Vec2{1, 1}
text.Scale = Vec2{1, 1}
text.Visible = true
return &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 *Shader, camera *Camera, font *Font) *TextRenderer {
if shader == nil {
shader = DefaultTextShader
}
if camera == nil {
camera = DefaultCamera
}
renderer := &TextRenderer{}
renderer.Shader = shader
renderer.Camera = camera
renderer.Font = font
renderer.texts = make([]Text, 0)
renderer.Size = Vec2{1, 1}
renderer.Scale = Vec2{1, 1}
return renderer
}
// Loads characters from JSON file.
@@ -82,7 +135,7 @@ func NewFont(tex *dp.Tex, tileSize int) *Font {
// 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 {
func (f *Font) FromJson(path string, cut bool) error {
// load file content
content, err := ioutil.ReadFile(path)
@@ -108,21 +161,22 @@ func (f *Font) extractChars(chars []jsonChar, cut bool) {
continue
}
var min, max, size geo.Vec2
var min, max, size 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}
min = Vec2{char.X * f.tileSize, char.Y * f.tileSize}
max = Vec2{min.X + f.tileSize, min.Y + f.tileSize}
size = 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})
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) {
func (f *Font) cutChar(x, y int) (Vec2, Vec2, Vec2) {
// find min/max corners of character on texture
minX := int(f.Tex.GetSize().X)
minY := int(f.Tex.GetSize().Y)
maxX := 0
@@ -151,22 +205,21 @@ func (f *Font) cutChar(x, y int) (geo.Vec2, geo.Vec2, geo.Vec2) {
}
}
// add padding
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}
min := Vec2{float64(minX) / texSize.X, float64(maxY) / texSize.Y}
max := Vec2{float64(maxX) / texSize.X, float64(minY) / texSize.Y}
size := Vec2{float64(maxX-minX) / f.tileSize, float64(maxY-minY) / f.tileSize}
return min, max, size
}
func (f *Font) getChar(char byte) *Character {
func (f *Font) getChar(char byte) *character {
for _, character := range f.chars {
if character.char == char {
return &character
@@ -176,20 +229,8 @@ func (f *Font) getChar(char byte) *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() {
// Deletes GL buffers bound to this text component.
func (t *TextComponent) Drop() {
t.index.Drop()
t.vertex.Drop()
t.texCoord.Drop()
@@ -199,7 +240,6 @@ func (t *Text) 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)
@@ -221,7 +261,7 @@ func (t *Text) SetText(font *Font, text string) {
// create vertices/texCoords
index = 0
offset := geo.Vec2{}
offset := Vec2{}
var width, height float64
for i := 0; i < len(text)*8 && int(index) < len(text); i += 8 {
@@ -285,18 +325,14 @@ func (t *Text) SetText(font *Font, text string) {
}
}
t.bounds = geo.Vec2{width, height}
t.bounds = 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
CheckGLError()
}
// Returns the text as string.
@@ -305,87 +341,86 @@ func (t *Text) GetText() string {
}
// 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}
func (t *Text) GetBounds() Vec2 {
return 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.
// Prepares given text for rendering.
func (r *TextRenderer) Prepare(text *Text) {
text.vao = dp.NewVAO()
text.vao = 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.vertex.AttribPointer(r.Shader.GetAttribLocation(Default_shader_text_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.texCoord.AttribPointer(r.Shader.GetAttribLocation(Default_shader_text_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)
// Frees recources created by text component.
// This is called automatically when system gets removed.
func (r *TextRenderer) Cleanup() {
for _, text := range r.texts {
text.Drop()
}
}
// Returns text by ID.
func (r *TextRenderer) Get(id int) *Text {
// Adds text to the renderer.
func (r *TextRenderer) Add(actor *Actor, pos *Pos2D, text *TextComponent) bool {
id := actor.GetId()
for _, text := range r.texts {
if text.GetId() == id {
return text
if id == text.Actor.GetId() {
return false
}
}
return nil
r.texts = append(r.texts, Text{actor, pos, text})
return true
}
// Removes text from renderer.
func (r *TextRenderer) Remove(actor *Actor) bool {
return r.RemoveById(actor.GetId())
}
// Removes text from renderer by ID.
func (r *TextRenderer) Remove(id int) *Text {
func (r *TextRenderer) RemoveById(id ActorId) bool {
for i, text := range r.texts {
if text.GetId() == id {
if text.Actor.GetId() == id {
r.texts = append(r.texts[:i], r.texts[i+1:]...)
return text
return true
}
}
return nil
return false
}
// Removes all sprites.
func (r *TextRenderer) Clear() {
r.texts = make([]*Text, 0)
// Removes all texts.
func (r *TextRenderer) RemoveAll() {
r.texts = make([]Text, 0)
}
// Renders sprites.
func (r *TextRenderer) Render() {
// Returns number of texts.
func (r *TextRenderer) Len() int {
return len(r.texts)
}
func (r *TextRenderer) GetName() string {
return text_renderer_name
}
// Renders texts.
func (r *TextRenderer) Update(delta float64) {
if r.Font == nil {
return
}
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.Shader.SendMat3(Default_shader_text_ortho, *MultMat3(r.Camera.CalcOrtho(), r.CalcModel()))
r.Shader.SendUniform1i(Default_shader_text_tex, 0)
r.Font.Tex.Bind()
for i := range r.texts {
@@ -394,9 +429,9 @@ func (r *TextRenderer) Render() {
}
r.texts[i].vao.Bind()
r.Shader.SendMat3(TEXTRENDERER_MODEL, *r.texts[i].CalcModel())
r.Shader.SendUniform4f(Default_shader_text_color, float32(r.texts[i].Color.X), float32(r.texts[i].Color.Y), float32(r.texts[i].Color.Z), float32(r.texts[i].Color.W))
r.Shader.SendMat3(Default_shader_text_model, *r.texts[i].CalcModel())
gl.DrawElements(gl.TRIANGLES, r.texts[i].index.Size(), gl.UNSIGNED_INT, nil)
}
}
*/

2
vao.go
View File

@@ -1,7 +1,7 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
)
// Vertex Array Object.

2
vbo.go
View File

@@ -1,7 +1,7 @@
package goga
import (
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/gl/v3.2-core/gl"
"unsafe"
)