├── .gitignore
├── test
├── icon.png
├── index.html
└── main.lua
├── gfx
├── font
│ ├── doc.go
│ ├── char_sets.go
│ ├── ttf_face.go
│ └── bitmap_face.go
├── wrap
│ ├── shader.go
│ ├── font.go
│ ├── quad.go
│ ├── texture.go
│ ├── sprite_batch.go
│ ├── text.go
│ ├── wrap.go
│ ├── utils.go
│ └── graphics.go
├── image.go
├── shader_templates.go
├── volatile.go
├── quad.go
├── const.go
├── uniform.go
├── display_state.go
├── quad_indicies.go
├── vertex_buffer.go
├── font_rasterizers.go
├── text_line.go
├── text.go
├── font.go
├── canvas.go
├── polyline.go
├── sprite_batch.go
├── texture.go
├── graphics.go
└── shader.go
├── cmd
├── moony
│ └── main.go
└── app.go
├── runtime
├── web
│ └── main.go
├── time.go
├── window.go
└── runtime.go
├── go.mod
├── README.md
├── LICENSE
├── audio
├── openal
│ ├── al
│ │ ├── LICENSE
│ │ ├── alc_pc.go
│ │ ├── alc.go
│ │ ├── alc_android.go
│ │ ├── const.go
│ │ ├── al_pc.go
│ │ └── al.go
│ ├── decoding
│ │ ├── mp3.go
│ │ ├── flac.go
│ │ ├── vorbis.go
│ │ ├── wav.go
│ │ └── decoder.go
│ ├── pool.go
│ └── source.go
├── audio.go
└── wrap.go
├── file
└── file.go
├── input
├── input.go
└── input_const.go
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | vendor
3 |
--------------------------------------------------------------------------------
/test/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tanema/amore/HEAD/test/icon.png
--------------------------------------------------------------------------------
/gfx/font/doc.go:
--------------------------------------------------------------------------------
1 | // Package font provides an easy interface for creating font faces to provide to
2 | // the gfx package. It may include more in the future.
3 | package font
4 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/cmd/moony/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/tanema/amore/cmd"
8 | )
9 |
10 | func main() {
11 | exitStatus, err := cmd.App.Run()
12 | if err != nil {
13 | log.Println(err)
14 | }
15 | os.Exit(exitStatus)
16 | }
17 |
--------------------------------------------------------------------------------
/runtime/web/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | // These are lua wrapped code that will be made accessible to lua
7 | _ "github.com/tanema/amore/gfx/wrap"
8 | _ "github.com/tanema/amore/input"
9 |
10 | "github.com/tanema/amore/runtime"
11 | )
12 |
13 | func main() {
14 | if err := runtime.Run("main.lua"); err != nil {
15 | fmt.Println(err)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/runtime/time.go:
--------------------------------------------------------------------------------
1 | package runtime
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | var (
8 | fps int // frames per second
9 | frames int // frames since last update freq
10 | previousTime time.Time // last frame time
11 | previousFPSUpdate time.Time // last time fps was updated
12 | )
13 |
14 | func step() float32 {
15 | frames++
16 | dt := float32(time.Since(previousTime).Seconds())
17 | previousTime = time.Now()
18 | timeSinceLast := float32(time.Since(previousFPSUpdate).Seconds())
19 | if timeSinceLast > 1 { //update 1 per second
20 | fps = int((float32(frames) / timeSinceLast) + 0.5)
21 | previousFPSUpdate = previousTime
22 | frames = 0
23 | }
24 | return dt
25 | }
26 |
--------------------------------------------------------------------------------
/gfx/font/char_sets.go:
--------------------------------------------------------------------------------
1 | package font
2 |
3 | import "unicode"
4 |
5 | var (
6 | // ASCII is a set of all ASCII runes. These runes are codepoints from 32 to 127 inclusive.
7 | ASCII []rune
8 | // Latin is a set of all the Latin runes
9 | Latin []rune
10 | )
11 |
12 | func init() {
13 | ASCII = make([]rune, unicode.MaxASCII-32)
14 | for i := range ASCII {
15 | ASCII[i] = rune(32 + i)
16 | }
17 | Latin = rangeTable(unicode.Latin)
18 | }
19 |
20 | func rangeTable(table *unicode.RangeTable) []rune {
21 | var runes []rune
22 | for _, rng := range table.R16 {
23 | for r := rng.Lo; r <= rng.Hi; r += rng.Stride {
24 | runes = append(runes, rune(r))
25 | }
26 | }
27 | for _, rng := range table.R32 {
28 | for r := rng.Lo; r <= rng.Hi; r += rng.Stride {
29 | runes = append(runes, rune(r))
30 | }
31 | }
32 | return runes
33 | }
34 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/tanema/amore
2 |
3 | require (
4 | github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d // indirect
5 | github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1
6 | github.com/go-gl/gl v0.0.0-20180407155706-68e253793080 // indirect
7 | github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f // indirect
8 | github.com/go-gl/mathgl v0.0.0-20180319210751-5ab0e04e1f55
9 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
10 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
11 | github.com/goxjs/gl v0.0.0-20171128034433-dc8f4a9a3c9c
12 | github.com/goxjs/glfw v0.0.0-20171018044755-7dec05603e06
13 | github.com/hajimehoshi/go-mp3 v0.1.0
14 | github.com/jfreymuth/oggvorbis v1.0.0
15 | github.com/jfreymuth/vorbis v1.0.0 // indirect
16 | golang.org/x/image v0.0.0-20180403161127-f315e4403028
17 | golang.org/x/mobile v0.0.0-20180501173530-c909788f9903
18 | honnef.co/go/js/dom v0.0.0-20181202134054-9dbdcd412bde // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## REDO
2 | IDEAS FOR BETTER WORLD
3 | - Make more portable with goxjs toolkit. Get games on web
4 | - Simplify and get rid of extra items like particle generator that isn't needed
5 | - scripting interface
6 | - combine keyboard, joystick and touch into a single input, mapping package
7 | - Somehow enable cross platform deploy easily
8 |
9 | TODO:
10 | - [x] minimize everything, this is for myself I don't need all this extra
11 | - [x] remove sdl and use [goxjs glfw](https://github.com/goxjs/glfw)
12 | - [x] reimlement input with mapping key to event
13 | - [x] goxjs file loading glfw.Open()
14 | - [ ] use [beep](https://github.com/faiface/beep) to have cross platform audio
15 | - [x] use [goxjs](https://github.com/goxjs/gl) for better cross platform
16 | - [ ] default antialiasing?
17 | - [ ] fix font rendering/sprite batching in web runtime
18 | - [x] use [glua](https://github.com/yuin/gopher-lua) for scripting
19 | - [ ] [cross compile](https://github.com/karalabe/xgo) on a singple platform
20 |
21 | These changes will reduce functionality but make it more portable.
22 |
--------------------------------------------------------------------------------
/gfx/wrap/shader.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/yuin/gopher-lua"
7 |
8 | "github.com/tanema/amore/gfx"
9 | )
10 |
11 | func toShader(ls *lua.LState, offset int) *gfx.Shader {
12 | img := ls.CheckUserData(offset)
13 | if v, ok := img.Value.(*gfx.Shader); ok {
14 | return v
15 | }
16 | ls.ArgError(offset, "shader expected")
17 | return nil
18 | }
19 |
20 | func gfxNewShader(ls *lua.LState) int {
21 | return returnUD(ls, "Shader", gfx.NewShader(toString(ls, 1), toStringD(ls, 2, "")))
22 | }
23 |
24 | func gfxShaderSend(ls *lua.LState) int {
25 | program := toShader(ls, 1)
26 | name := toString(ls, 2)
27 | uniformType, found := program.GetUniformType(name)
28 | if !found {
29 | ls.ArgError(2, fmt.Sprintf("unknown uniform with name [%s]", name))
30 | }
31 | switch uniformType {
32 | case gfx.UniformFloat:
33 | program.SendFloat(name, extractFloatArray(ls, 3)...)
34 | case gfx.UniformInt:
35 | program.SendInt(name, extractIntArray(ls, 3)...)
36 | case gfx.UniformSampler:
37 | program.SendTexture(name, toTexture(ls, 3))
38 | }
39 | return 0
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Tim Anema
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/runtime/window.go:
--------------------------------------------------------------------------------
1 | package runtime
2 |
3 | import (
4 | "github.com/goxjs/glfw"
5 | )
6 |
7 | type config struct {
8 | Title string
9 | Width int
10 | Height int
11 | Resizable bool
12 | Fullscreen bool
13 | MouseShown bool
14 | }
15 |
16 | var (
17 | conf = config{
18 | Width: 800,
19 | Height: 600,
20 | }
21 | )
22 |
23 | type window struct {
24 | *glfw.Window
25 | active bool
26 | }
27 |
28 | func createWindow(conf config) (window, error) {
29 | newWin := window{active: true}
30 |
31 | var err error
32 | newWin.Window, err = glfw.CreateWindow(conf.Width, conf.Height, conf.Title, nil, nil)
33 | if err != nil {
34 | return window{}, err
35 | }
36 | newWin.MakeContextCurrent()
37 | if conf.Resizable {
38 | glfw.WindowHint(glfw.Resizable, 1)
39 | } else {
40 | glfw.WindowHint(glfw.Resizable, 0)
41 | }
42 | glfw.WindowHint(glfw.Samples, 4.0)
43 | newWin.SetFocusCallback(newWin.focus)
44 | newWin.SetIconifyCallback(newWin.iconify)
45 | return newWin, nil
46 | }
47 |
48 | func (win *window) focus(w *glfw.Window, focused bool) { win.active = focused }
49 | func (win *window) iconify(w *glfw.Window, iconified bool) { win.active = !iconified }
50 |
--------------------------------------------------------------------------------
/gfx/image.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "image"
5 | // All image types have been imported for loading them
6 | _ "image/gif"
7 | _ "image/jpeg"
8 | _ "image/png"
9 |
10 | "github.com/tanema/amore/file"
11 | )
12 |
13 | // Image is an image that is drawable to the screen
14 | type Image struct {
15 | *Texture
16 | filePath string
17 | mipmaps bool
18 | }
19 |
20 | // NewImage will create a new texture for this image and return the *Image. If the
21 | // file does not exist or cannot be decoded it will return an error.
22 | func NewImage(path string, mipmapped bool) *Image {
23 | newImage := &Image{filePath: path, mipmaps: mipmapped}
24 | registerVolatile(newImage)
25 | return newImage
26 | }
27 |
28 | // loadVolatile will create the volatile objects
29 | func (img *Image) loadVolatile() bool {
30 | if img.filePath == "" {
31 | return false
32 | }
33 |
34 | imgFile, err := file.Open(img.filePath)
35 | if err != nil {
36 | return false
37 | }
38 | defer imgFile.Close()
39 |
40 | decodedImg, _, err := image.Decode(imgFile)
41 | if err != nil || decodedImg == nil {
42 | return false
43 | }
44 |
45 | img.Texture = newImageTexture(decodedImg, img.mipmaps)
46 | return true
47 | }
48 |
--------------------------------------------------------------------------------
/test/main.lua:
--------------------------------------------------------------------------------
1 | local img, txt
2 |
3 | function onload()
4 | img = gfx.newimage("icon.png")
5 | txt = gfx.newtext(gfx.getfont(), "This is text")
6 | end
7 |
8 | function oninput(device, button, action, modifiers)
9 | if device == "keyboard" and button == "escape" and action == "release" then
10 | quit()
11 | end
12 | end
13 |
14 | function update(dt)
15 | end
16 |
17 | function draw()
18 | gfx.setcolor(1, 1, 1, 1)
19 | gfx.print({{"fps: ", getfps()}, {{1, 1, 0}, {0, 1, 1}}}, 0, 0)
20 | gfx.rectangle("fill", 300, 300, 480, 440)
21 | img:draw(300, 300)
22 | txt:draw(100, 100)
23 | gfx.setlinewidth(2)
24 | gfx.setlinejoin("bevel")
25 | gfx.ellipse("line", 300, 300, 100, 200)
26 | gfx.setcolor(1, 0, 0, 1)
27 | gfx.rectangle("line", 50, 50, 100, 100)
28 | gfx.setcolor(1, 1, 1, 1)
29 | gfx.line(0, 0, 100, 100, 200, 100)
30 |
31 | gfx.stencil(function() gfx.rectangle("fill", 225, 200, 350, 300) end, "replace", 1)
32 | gfx.setstenciltest("greater", 0)
33 | gfx.setcolor(1, 0, 0, 0.45)
34 | gfx.circle("fill", 300, 300, 150, 50)
35 | gfx.setcolor(0, 1, 0, 0.45)
36 | gfx.circle("fill", 500, 300, 150, 50)
37 | gfx.setcolor(0, 0, 1, 0.45)
38 | gfx.circle("fill", 400, 400, 150, 50)
39 | gfx.setstenciltest()
40 | end
41 |
--------------------------------------------------------------------------------
/cmd/app.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 | "strings"
6 |
7 | "github.com/mitchellh/cli"
8 |
9 | // These are lua wrapped code that will be made accessible to lua
10 | _ "github.com/tanema/amore/audio"
11 | _ "github.com/tanema/amore/gfx/wrap"
12 | _ "github.com/tanema/amore/input"
13 |
14 | "github.com/tanema/amore/runtime"
15 | )
16 |
17 | var App = &cli.CLI{
18 | Name: "moony",
19 | Args: os.Args[1:],
20 | Commands: commands,
21 | Autocomplete: true,
22 | AutocompleteNoDefaultFlags: true,
23 | }
24 |
25 | var ui = &cli.BasicUi{
26 | Reader: os.Stdin,
27 | Writer: os.Stdout,
28 | ErrorWriter: os.Stderr,
29 | }
30 |
31 | var commands = map[string]cli.CommandFactory{
32 | "": func() (cli.Command, error) { return &runCommand{ui: ui}, nil },
33 | "run": func() (cli.Command, error) { return &runCommand{ui: ui}, nil },
34 | }
35 |
36 | type runCommand struct {
37 | ui cli.Ui
38 | }
39 |
40 | func (run *runCommand) Run(args []string) int {
41 | if err := runtime.Run("main.lua"); err != nil {
42 | run.ui.Error(err.Error())
43 | return 1
44 | }
45 | return 0
46 | }
47 |
48 | func (run *runCommand) Synopsis() string {
49 | return ""
50 | }
51 |
52 | func (run *runCommand) Help() string {
53 | helpText := `
54 | Usage: moony
55 | Run your program yo
56 | Options:
57 | -h, --help show this help
58 | `
59 | return strings.TrimSpace(helpText)
60 | }
61 |
--------------------------------------------------------------------------------
/gfx/shader_templates.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "text/template"
5 | )
6 |
7 | var (
8 | shaderTemplate, _ = template.New("shader").Parse(`
9 | #ifdef GL_ES
10 | precision highp float;
11 | #endif
12 | uniform mat4 TransformMat;
13 | uniform vec4 ScreenSize;
14 | {{.Header}}
15 | #line 1
16 | {{.Code}}
17 | {{.Footer}}
18 | `)
19 | )
20 |
21 | const (
22 | vertexHeader = `
23 | attribute vec4 VertexPosition;
24 | attribute vec4 VertexTexCoord;
25 | attribute vec4 VertexColor;
26 | attribute vec4 ConstantColor;
27 | varying vec4 VaryingTexCoord;
28 | varying vec4 VaryingColor;
29 | uniform float PointSize;
30 | `
31 |
32 | defaultVertexShaderCode = `
33 | vec4 position(mat4 transformMatrix, vec4 vertexPosition) {
34 | return transformMatrix * vertexPosition;
35 | }`
36 |
37 | vertexFooter = `
38 | void main() {
39 | VaryingTexCoord = VertexTexCoord;
40 | VaryingColor = VertexColor * ConstantColor;
41 | gl_PointSize = PointSize;
42 | gl_Position = position(TransformMat, VertexPosition);
43 | }`
44 |
45 | fragmentHeader = `
46 | varying vec4 VaryingTexCoord;
47 | varying vec4 VaryingColor;
48 | uniform sampler2D Texture0;
49 | `
50 |
51 | defaultFragmentShaderCode = `
52 | vec4 effect(vec4 color, sampler2D texture, vec2 textureCoordinate, vec2 pixcoord) {
53 | return texture2D(texture, textureCoordinate) * color;
54 | }`
55 |
56 | fragmentFooter = `
57 | void main() {
58 | vec2 pixelcoord = vec2(gl_FragCoord.x, (gl_FragCoord.y * ScreenSize.z) + ScreenSize.w);
59 | gl_FragColor = effect(VaryingColor, Texture0, VaryingTexCoord.st, pixelcoord);
60 | }`
61 | )
62 |
--------------------------------------------------------------------------------
/audio/openal/al/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/gfx/volatile.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import "runtime"
4 |
5 | // volatile is an interface for all items that are loaded into the gl context.
6 | // The volatile wrapper allows you to instantiate any gl items before the context
7 | // exist, and they will be completed after the context exists.
8 | type volatile interface {
9 | loadVolatile() bool
10 | unloadVolatile()
11 | }
12 |
13 | var (
14 | allVolatile = []volatile{}
15 | unloadcallQueue = make(chan func(), 20)
16 | )
17 |
18 | // registerVolatile will put the volatile in the current object group and call
19 | // loadVolatile if the gl context is initialized.
20 | func registerVolatile(newVolatile volatile) {
21 | if !glState.initialized {
22 | allVolatile = append(allVolatile, newVolatile)
23 | return
24 | }
25 | loadVolatile(newVolatile)
26 | }
27 |
28 | func loadAllVolatile() {
29 | for _, vol := range allVolatile {
30 | loadVolatile(vol)
31 | }
32 | allVolatile = []volatile{}
33 | }
34 |
35 | func loadVolatile(newVolatile volatile) {
36 | newVolatile.loadVolatile()
37 | runtime.SetFinalizer(newVolatile, func(vol volatile) {
38 | unloadcallQueue <- vol.unloadVolatile
39 | })
40 | }
41 |
42 | // used to make sure that the unload functions are called on the main thread
43 | // and are called after each game loop. This is a bit more overhead but it
44 | // make sure that the users don't need to release resources explicitly like
45 | // some old ass technology
46 | func cleanupVolatile() {
47 | for {
48 | select {
49 | case f := <-unloadcallQueue:
50 | f()
51 | default:
52 | return
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/gfx/font/ttf_face.go:
--------------------------------------------------------------------------------
1 | package font
2 |
3 | import (
4 | "github.com/golang/freetype/truetype"
5 | "golang.org/x/image/font"
6 | "golang.org/x/image/font/gofont/gobold"
7 | "golang.org/x/image/font/gofont/goitalic"
8 | "golang.org/x/image/font/gofont/goregular"
9 |
10 | "github.com/tanema/amore/file"
11 | )
12 |
13 | // Face just an alias so you don't ahve to import multople packages named font
14 | type Face font.Face
15 |
16 | // NewTTFFace will load up a ttf font face for creating a font in graphics
17 | func NewTTFFace(filepath string, size float32) (font.Face, error) {
18 | fontBytes, err := file.Read(filepath)
19 | if err != nil {
20 | return nil, err
21 | }
22 |
23 | return ttfFromBytes(fontBytes, size)
24 | }
25 |
26 | // Bold will return an bold font face with the request font size
27 | func Bold(fontSize float32) (font.Face, error) {
28 | return ttfFromBytes(gobold.TTF, fontSize)
29 | }
30 |
31 | // Default will return an regular font face with the request font size
32 | func Default(fontSize float32) (font.Face, error) {
33 | return ttfFromBytes(goregular.TTF, fontSize)
34 | }
35 |
36 | // Italic will return an italic font face with the request font size
37 | func Italic(fontSize float32) (font.Face, error) {
38 | return ttfFromBytes(goitalic.TTF, fontSize)
39 | }
40 |
41 | func ttfFromBytes(fontBytes []byte, size float32) (font.Face, error) {
42 | ttf, err := truetype.Parse(fontBytes)
43 | if err != nil {
44 | return nil, err
45 | }
46 | return truetype.NewFace(ttf, &truetype.Options{
47 | Size: float64(size),
48 | GlyphCacheEntries: 1,
49 | }), nil
50 | }
51 |
--------------------------------------------------------------------------------
/gfx/wrap/font.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import (
4 | "github.com/yuin/gopher-lua"
5 |
6 | "github.com/tanema/amore/gfx"
7 | )
8 |
9 | func toFont(ls *lua.LState, offset int) *gfx.Font {
10 | img := ls.CheckUserData(offset)
11 | if v, ok := img.Value.(*gfx.Font); ok {
12 | return v
13 | }
14 | ls.ArgError(offset, "font expected")
15 | return nil
16 | }
17 |
18 | func gfxNewFont(ls *lua.LState) int {
19 | newFont, err := gfx.NewFont(toString(ls, 1), toFloat(ls, 2))
20 | if err == nil {
21 | return returnUD(ls, "Font", newFont)
22 | }
23 | ls.Push(lua.LNil)
24 | return 1
25 | }
26 |
27 | func gfxFontGetWidth(ls *lua.LState) int {
28 | font := toFont(ls, 1)
29 | ls.Push(lua.LNumber(font.GetWidth(toString(ls, 2))))
30 | return 1
31 | }
32 |
33 | func gfxFontGetHeight(ls *lua.LState) int {
34 | font := toFont(ls, 1)
35 | ls.Push(lua.LNumber(font.GetHeight()))
36 | return 1
37 | }
38 |
39 | func gfxFontSetFallback(ls *lua.LState) int {
40 | font := toFont(ls, 1)
41 | start := 2
42 | fallbacks := []*gfx.Font{}
43 | for x := ls.Get(start); x != nil; start++ {
44 | img := ls.CheckUserData(start)
45 | if v, ok := img.Value.(*gfx.Font); ok {
46 | fallbacks = append(fallbacks, v)
47 | }
48 | ls.ArgError(start, "font expected")
49 | }
50 | font.SetFallbacks(fallbacks...)
51 | return 0
52 | }
53 |
54 | func gfxFontGetWrap(ls *lua.LState) int {
55 | font := toFont(ls, 1)
56 | wrap, strs := font.GetWrap(toString(ls, 1), toFloat(ls, 2))
57 | ls.Push(lua.LNumber(wrap))
58 | table := ls.NewTable()
59 | for _, str := range strs {
60 | table.Append(lua.LString(str))
61 | }
62 | ls.Push(table)
63 | return 2
64 | }
65 |
--------------------------------------------------------------------------------
/gfx/wrap/quad.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import (
4 | "github.com/yuin/gopher-lua"
5 |
6 | "github.com/tanema/amore/gfx"
7 | )
8 |
9 | func toQuad(ls *lua.LState, offset int) *gfx.Quad {
10 | img := ls.CheckUserData(offset)
11 | if v, ok := img.Value.(*gfx.Quad); ok {
12 | return v
13 | }
14 | ls.ArgError(offset, "quad expected")
15 | return nil
16 | }
17 |
18 | func gfxNewQuad(ls *lua.LState) int {
19 | offset := 1
20 | args := []int32{}
21 | for x := ls.Get(offset); x != nil; offset++ {
22 | val := ls.Get(offset)
23 | if lv, ok := val.(lua.LNumber); ok {
24 | args = append(args, int32(lv))
25 | } else if val.Type() == lua.LTNil {
26 | break
27 | } else {
28 | ls.ArgError(offset, "argument wrong type, should be number")
29 | }
30 | }
31 | if offset < 6 {
32 | ls.ArgError(len(args)-1, "not enough arguments")
33 | }
34 | return returnUD(ls, "Quad", gfx.NewQuad(args[0], args[1], args[2], args[3], args[4], args[5]))
35 | }
36 |
37 | func gfxQuadGetWidth(ls *lua.LState) int {
38 | ls.Push(lua.LNumber(toQuad(ls, 1).GetWidth()))
39 | return 1
40 | }
41 |
42 | func gfxQuadGetHeight(ls *lua.LState) int {
43 | ls.Push(lua.LNumber(toQuad(ls, 1).GetHeight()))
44 | return 1
45 | }
46 |
47 | func gfxQuadGetViewport(ls *lua.LState) int {
48 | x, y, w, h := toQuad(ls, 1).GetViewport()
49 | ls.Push(lua.LNumber(x))
50 | ls.Push(lua.LNumber(y))
51 | ls.Push(lua.LNumber(w))
52 | ls.Push(lua.LNumber(h))
53 | return 4
54 | }
55 |
56 | func gfxQuadSetViewport(ls *lua.LState) int {
57 | toQuad(ls, 1).SetViewport(int32(toInt(ls, 2)), int32(toInt(ls, 3)), int32(toInt(ls, 4)), int32(toInt(ls, 5)))
58 | return 0
59 | }
60 |
--------------------------------------------------------------------------------
/audio/openal/al/alc_pc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin linux,!android
6 |
7 | package al
8 |
9 | /*
10 | #cgo darwin CFLAGS: -DGOOS_darwin
11 | #cgo linux CFLAGS: -DGOOS_linux
12 | #cgo darwin LDFLAGS: -framework OpenAL
13 | #cgo linux LDFLAGS: -lopenal
14 |
15 | #ifdef GOOS_darwin
16 | #include
17 | #include
18 | #endif
19 |
20 | #ifdef GOOS_linux
21 | #include
22 | #include
23 | #endif
24 | */
25 | import "C"
26 | import "unsafe"
27 |
28 | /*
29 | On Ubuntu 14.04 'Trusty', you may have to install these libraries:
30 | sudo apt-get install libopenal-dev
31 | */
32 |
33 | func alcGetError(d unsafe.Pointer) int32 {
34 | dev := (*C.ALCdevice)(d)
35 | return int32(C.alcGetError(dev))
36 | }
37 |
38 | func alcOpenDevice(name string) unsafe.Pointer {
39 | if name == "" {
40 | return (unsafe.Pointer)(C.alcOpenDevice((*C.ALCchar)(nil)))
41 | }
42 | n := C.CString(name)
43 | defer C.free(unsafe.Pointer(n))
44 | return (unsafe.Pointer)(C.alcOpenDevice((*C.ALCchar)(unsafe.Pointer(n))))
45 | }
46 |
47 | func alcCloseDevice(d unsafe.Pointer) bool {
48 | dev := (*C.ALCdevice)(d)
49 | return C.alcCloseDevice(dev) == C.ALC_TRUE
50 | }
51 |
52 | func alcCreateContext(d unsafe.Pointer, attrs []int32) unsafe.Pointer {
53 | dev := (*C.ALCdevice)(d)
54 | return (unsafe.Pointer)(C.alcCreateContext(dev, nil))
55 | }
56 |
57 | func alcMakeContextCurrent(c unsafe.Pointer) bool {
58 | ctx := (*C.ALCcontext)(c)
59 | return C.alcMakeContextCurrent(ctx) == C.ALC_TRUE
60 | }
61 |
62 | func alcDestroyContext(c unsafe.Pointer) {
63 | C.alcDestroyContext((*C.ALCcontext)(c))
64 | }
65 |
--------------------------------------------------------------------------------
/audio/openal/al/alc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin linux
6 |
7 | package al
8 |
9 | import (
10 | "errors"
11 | "sync"
12 | "unsafe"
13 | )
14 |
15 | var (
16 | mu sync.Mutex
17 | device unsafe.Pointer
18 | context unsafe.Pointer
19 | )
20 |
21 | // DeviceError returns the last known error from the current device.
22 | func DeviceError() int32 {
23 | return alcGetError(device)
24 | }
25 |
26 | // TODO(jbd): Investigate the cases where multiple audio output
27 | // devices might be needed.
28 |
29 | // OpenDevice opens the default audio device.
30 | // Calls to OpenDevice are safe for concurrent use.
31 | func OpenDevice() error {
32 | mu.Lock()
33 | defer mu.Unlock()
34 |
35 | // already opened
36 | if device != nil {
37 | return nil
38 | }
39 |
40 | dev := alcOpenDevice("")
41 | if dev == nil {
42 | return errors.New("al: cannot open the default audio device")
43 | }
44 | ctx := alcCreateContext(dev, nil)
45 | if ctx == nil {
46 | alcCloseDevice(dev)
47 | return errors.New("al: cannot create a new context")
48 | }
49 | if !alcMakeContextCurrent(ctx) {
50 | alcCloseDevice(dev)
51 | return errors.New("al: cannot make context current")
52 | }
53 | device = dev
54 | context = ctx
55 | return nil
56 | }
57 |
58 | // CloseDevice closes the device and frees related resources.
59 | // Calls to CloseDevice are safe for concurrent use.
60 | func CloseDevice() {
61 | mu.Lock()
62 | defer mu.Unlock()
63 |
64 | if device == nil {
65 | return
66 | }
67 |
68 | alcCloseDevice(device)
69 | if context != nil {
70 | alcDestroyContext(context)
71 | }
72 | device = nil
73 | context = nil
74 | }
75 |
--------------------------------------------------------------------------------
/audio/openal/decoding/mp3.go:
--------------------------------------------------------------------------------
1 | package decoding
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/hajimehoshi/go-mp3"
7 | )
8 |
9 | func newMp3Decoder(src io.ReadCloser) (*Decoder, error) {
10 | r, err := mp3.NewDecoder(src)
11 | if err != nil {
12 | return nil, err
13 | }
14 |
15 | d := &mp3Reader{
16 | data: []byte{},
17 | source: src,
18 | decoder: r,
19 | }
20 |
21 | return newDecoder(
22 | src,
23 | d,
24 | 2,
25 | int32(r.SampleRate()),
26 | 16,
27 | int32(r.Length()),
28 | ), nil
29 | }
30 |
31 | type mp3Reader struct {
32 | data []byte
33 | readBytes int
34 | pos int
35 | source io.Closer
36 | decoder *mp3.Decoder
37 | }
38 |
39 | func (d *mp3Reader) readUntil(pos int) error {
40 | for len(d.data) <= pos {
41 | buf := make([]uint8, 8192)
42 | n, err := d.decoder.Read(buf)
43 | d.data = append(d.data, buf[:n]...)
44 | if err != nil {
45 | if err == io.EOF {
46 | return io.EOF
47 | }
48 | return err
49 | }
50 | }
51 | return nil
52 | }
53 |
54 | func (d *mp3Reader) Read(b []byte) (int, error) {
55 | left := int(d.decoder.Length()) - d.pos
56 | if left > len(b) {
57 | left = len(b)
58 | }
59 | if left <= 0 {
60 | return 0, io.EOF
61 | }
62 | if err := d.readUntil(d.pos + left); err != nil {
63 | return 0, err
64 | }
65 | copy(b, d.data[d.pos:d.pos+left-1])
66 | d.pos += left
67 | if d.pos == int(d.decoder.Length()) {
68 | return left, io.EOF
69 | }
70 | return left, nil
71 | }
72 |
73 | func (d *mp3Reader) Seek(offset int64, whence int) (int64, error) {
74 | next := int64(0)
75 | switch whence {
76 | case io.SeekStart:
77 | next = offset
78 | case io.SeekCurrent:
79 | next = int64(d.pos) + offset
80 | case io.SeekEnd:
81 | next = int64(d.decoder.Length()) + offset
82 | }
83 | d.pos = int(next)
84 | if err := d.readUntil(d.pos); err != nil {
85 | return 0, err
86 | }
87 | return next, nil
88 | }
89 |
--------------------------------------------------------------------------------
/audio/audio.go:
--------------------------------------------------------------------------------
1 | // Package audio is use for creating audio sources, managing/pooling resources,
2 | // and playback of those audio sources.
3 | package audio
4 |
5 | import (
6 | "time"
7 |
8 | "github.com/tanema/amore/audio/openal"
9 | )
10 |
11 | // Source is an playable audio source
12 | type Source interface {
13 | IsFinished() bool
14 | GetDuration() time.Duration
15 | GetPitch() float32
16 | GetVolume() float32
17 | GetState() string
18 | IsLooping() bool
19 | IsPaused() bool
20 | IsPlaying() bool
21 | IsStatic() bool
22 | IsStopped() bool
23 | SetLooping(loop bool)
24 | SetPitch(p float32)
25 | SetVolume(v float32)
26 | Play() bool
27 | Pause()
28 | Resume()
29 | Rewind()
30 | Seek(time.Duration)
31 | Stop()
32 | Tell() time.Duration
33 | }
34 |
35 | // NewSource creates a new Source from a file at the path provided. If you
36 | // specify a static source it will all be buffered into a single buffer. If
37 | // false then it will create many buffers a cycle through them with data chunks.
38 | // This allows a smaller memory footprint while playing bigger music files. You
39 | // may want a static file if the sound is less than 2 seconds. It allows for faster
40 | // cleaning playing of shorter sounds like footsteps.
41 | func NewSource(filepath string, static bool) (Source, error) {
42 | return openal.NewSource(filepath, static)
43 | }
44 |
45 | // GetVolume returns the master volume.
46 | func GetVolume() float32 { return openal.GetVolume() }
47 |
48 | // SetVolume sets the master volume
49 | func SetVolume(gain float32) { openal.SetVolume(gain) }
50 |
51 | // PauseAll will pause all sources
52 | func PauseAll() { openal.PauseAll() }
53 |
54 | // PlayAll will play all sources
55 | func PlayAll() { openal.PlayAll() }
56 |
57 | // RewindAll will rewind all sources
58 | func RewindAll() { openal.RewindAll() }
59 |
60 | // StopAll stop all sources
61 | func StopAll() { openal.StopAll() }
62 |
--------------------------------------------------------------------------------
/gfx/quad.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | type (
4 | // Quad is essentially a crop of an image/texture
5 | Quad struct {
6 | vertices []float32
7 | x float32
8 | y float32
9 | w float32
10 | h float32
11 | sw float32
12 | sh float32
13 | }
14 | )
15 |
16 | // NewQuad will generate a new *Quad with the dimensions given
17 | // x, y are position on the texture
18 | // w, h are the size of the quad
19 | // sw, sh are references on how large the texture is. image.GetWidth(), image.GetHeight()
20 | func NewQuad(x, y, w, h, sw, sh int32) *Quad {
21 | newQuad := &Quad{
22 | x: float32(x),
23 | y: float32(y),
24 | w: float32(w),
25 | h: float32(h),
26 | sw: float32(sw),
27 | sh: float32(sh),
28 | }
29 | newQuad.generateVertices()
30 | return newQuad
31 | }
32 |
33 | // generateVertices generates an array of data for drawing the quad.
34 | func (quad *Quad) generateVertices() {
35 | quad.vertices = []float32{
36 | 0, 0, quad.x / quad.sw, quad.y / quad.sh,
37 | 0, quad.h, quad.x / quad.sw, (quad.y + quad.h) / quad.sh,
38 | quad.w, 0, (quad.x + quad.w) / quad.sw, quad.y / quad.sh,
39 | quad.w, quad.h, (quad.x + quad.w) / quad.sw, (quad.y + quad.h) / quad.sh,
40 | }
41 | }
42 |
43 | // getVertices will return the generated verticies
44 | func (quad *Quad) getVertices() []float32 {
45 | return quad.vertices
46 | }
47 |
48 | // SetViewport sets the texture coordinates according to a viewport.
49 | func (quad *Quad) SetViewport(x, y, w, h int32) {
50 | quad.x = float32(x)
51 | quad.y = float32(y)
52 | quad.w = float32(w)
53 | quad.h = float32(h)
54 | quad.generateVertices()
55 | }
56 |
57 | // GetWidth gets the width of the quad
58 | func (quad *Quad) GetWidth() float32 {
59 | return quad.w
60 | }
61 |
62 | // GetHeight gets the height of the quad
63 | func (quad *Quad) GetHeight() float32 {
64 | return quad.h
65 | }
66 |
67 | // GetViewport gets the current viewport of this Quad.
68 | func (quad *Quad) GetViewport() (x, y, w, h int32) {
69 | return int32(quad.x), int32(quad.y), int32(quad.w), int32(quad.h)
70 | }
71 |
--------------------------------------------------------------------------------
/audio/openal/al/alc_android.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package al
6 |
7 | /*
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | ALCint call_alcGetError(LPALCGETERROR fn, ALCdevice* d) {
14 | return fn(d);
15 | }
16 |
17 | ALCdevice* call_alcOpenDevice(LPALCOPENDEVICE fn, const ALCchar* name) {
18 | return fn(name);
19 | }
20 |
21 | ALCboolean call_alcCloseDevice(LPALCCLOSEDEVICE fn, ALCdevice* d) {
22 | return fn(d);
23 | }
24 |
25 | ALCcontext* call_alcCreateContext(LPALCCREATECONTEXT fn, ALCdevice* d, const ALCint* attrs) {
26 | return fn(d, attrs);
27 | }
28 |
29 | ALCboolean call_alcMakeContextCurrent(LPALCMAKECONTEXTCURRENT fn, ALCcontext* c) {
30 | return fn(c);
31 | }
32 |
33 | void call_alcDestroyContext(LPALCDESTROYCONTEXT fn, ALCcontext* c) {
34 | return fn(c);
35 | }
36 | */
37 | import "C"
38 | import (
39 | "sync"
40 | "unsafe"
41 | )
42 |
43 | var once sync.Once
44 |
45 | func alcGetError(d unsafe.Pointer) int32 {
46 | dev := (*C.ALCdevice)(d)
47 | return int32(C.call_alcGetError(alcGetErrorFunc, dev))
48 | }
49 |
50 | func alcOpenDevice(name string) unsafe.Pointer {
51 | once.Do(initAL)
52 | n := C.CString(name)
53 | defer C.free(unsafe.Pointer(n))
54 |
55 | return (unsafe.Pointer)(C.call_alcOpenDevice(alcOpenDeviceFunc, (*C.ALCchar)(unsafe.Pointer(n))))
56 | }
57 |
58 | func alcCloseDevice(d unsafe.Pointer) bool {
59 | dev := (*C.ALCdevice)(d)
60 | return C.call_alcCloseDevice(alcCloseDeviceFunc, dev) == C.AL_TRUE
61 | }
62 |
63 | func alcCreateContext(d unsafe.Pointer, attrs []int32) unsafe.Pointer {
64 | dev := (*C.ALCdevice)(d)
65 | // TODO(jbd): Handle attrs.
66 | return (unsafe.Pointer)(C.call_alcCreateContext(alcCreateContextFunc, dev, nil))
67 | }
68 |
69 | func alcMakeContextCurrent(c unsafe.Pointer) bool {
70 | ctx := (*C.ALCcontext)(c)
71 | return C.call_alcMakeContextCurrent(alcMakeContextCurrentFunc, ctx) == C.AL_TRUE
72 | }
73 |
74 | func alcDestroyContext(c unsafe.Pointer) {
75 | C.call_alcDestroyContext(alcDestroyContextFunc, (*C.ALCcontext)(c))
76 | }
77 |
--------------------------------------------------------------------------------
/gfx/const.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import "github.com/goxjs/gl"
4 |
5 | type (
6 | // WrapMode is used for setting texture/image/canvas wrap
7 | WrapMode int
8 | // FilterMode is used for setting texture/image/canvas filters
9 | FilterMode int
10 | // StencilAction is how a stencil function modifies the stencil values of pixels it touches.
11 | StencilAction uint32
12 | // CompareMode defines different types of per-pixel stencil test comparisons.
13 | // The pixels of an object will be drawn if the comparison succeeds, for each
14 | // pixel that the object touches.
15 | CompareMode uint32
16 | // Usage is used for sprite batch usage, and specifies if it is static, dynamic, or stream
17 | Usage uint32
18 | )
19 |
20 | // ColorMask contains an rgba color mask
21 | type ColorMask struct {
22 | r, g, b, a bool
23 | }
24 |
25 | var ( //opengl attribute variables
26 | shaderPos = gl.Attrib{Value: 0}
27 | shaderTexCoord = gl.Attrib{Value: 1}
28 | shaderColor = gl.Attrib{Value: 2}
29 | shaderConstantColor = gl.Attrib{Value: 3}
30 | )
31 |
32 | //texture wrap
33 | const (
34 | WrapClamp WrapMode = 0x812F
35 | WrapRepeat WrapMode = 0x2901
36 | WrapMirroredRepeat WrapMode = 0x8370
37 | )
38 |
39 | //texture filter
40 | const (
41 | FilterNone FilterMode = 0
42 | FilterNearest FilterMode = 0x2600
43 | FilterLinear FilterMode = 0x2601
44 | )
45 |
46 | //stencil actions
47 | const (
48 | StencilReplace StencilAction = 0x1E01
49 | StencilIncrement StencilAction = 0x1E02
50 | StencilDecrement StencilAction = 0x1E03
51 | StencilIncrementWrap StencilAction = 0x8507
52 | StencilDecrementWrap StencilAction = 0x8508
53 | StencilInvert StencilAction = 0x150A
54 | )
55 |
56 | // stenicl test modes
57 | const (
58 | CompareGreater CompareMode = 0x0201
59 | CompareEqual CompareMode = 0x0202
60 | CompareGequal CompareMode = 0x0203
61 | CompareLess CompareMode = 0x0204
62 | CompareNotequal CompareMode = 0x0205
63 | CompareLequal CompareMode = 0x0206
64 | CompareAlways CompareMode = 0x0207
65 | )
66 |
67 | // spritebatch usage
68 | const (
69 | UsageStream Usage = 0x88E0
70 | UsageStatic Usage = 0x88E4
71 | UsageDynamic Usage = 0x88E8
72 | )
73 |
--------------------------------------------------------------------------------
/audio/openal/decoding/flac.go:
--------------------------------------------------------------------------------
1 | package decoding
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/eaburns/flac"
7 | )
8 |
9 | func newFlacDecoder(src io.ReadCloser) (*Decoder, error) {
10 | r, err := flac.NewDecoder(src)
11 | if err != nil {
12 | return nil, err
13 | }
14 |
15 | totalBytes := int32(r.TotalSamples * int64(r.NChannels) * int64(r.BitsPerSample/8))
16 | d := &flacReader{
17 | data: make([]byte, totalBytes),
18 | totalBytes: int(totalBytes),
19 | source: src,
20 | decoder: r,
21 | }
22 |
23 | return newDecoder(
24 | src,
25 | d,
26 | int16(r.NChannels),
27 | int32(r.SampleRate),
28 | int16(r.BitsPerSample),
29 | totalBytes,
30 | ), nil
31 | }
32 |
33 | type flacReader struct {
34 | data []byte
35 | totalBytes int
36 | readBytes int
37 | pos int
38 | source io.Closer
39 | decoder *flac.Decoder
40 | }
41 |
42 | func (d *flacReader) readUntil(pos int) error {
43 | for d.readBytes < pos {
44 | data, err := d.decoder.Next()
45 | if len(data) > 0 {
46 | p := d.readBytes
47 | for i := 0; i < len(data); i++ {
48 | d.data[p+i] = data[i]
49 | }
50 | d.readBytes += len(data)
51 | }
52 | if err == io.EOF {
53 | if err := d.source.Close(); err != nil {
54 | return err
55 | }
56 | break
57 | }
58 | if err != nil {
59 | return err
60 | }
61 | }
62 | return nil
63 | }
64 |
65 | func (d *flacReader) Read(b []byte) (int, error) {
66 | left := d.totalBytes - d.pos
67 | if left > len(b) {
68 | left = len(b)
69 | }
70 | if left <= 0 {
71 | return 0, io.EOF
72 | }
73 | if err := d.readUntil(d.pos + left); err != nil {
74 | return 0, err
75 | }
76 | copy(b, d.data[d.pos:d.pos+left])
77 | d.pos += left
78 | if d.pos == d.totalBytes {
79 | return left, io.EOF
80 | }
81 | return left, nil
82 | }
83 |
84 | func (d *flacReader) Seek(offset int64, whence int) (int64, error) {
85 | next := int64(0)
86 | switch whence {
87 | case io.SeekStart:
88 | next = offset
89 | case io.SeekCurrent:
90 | next = int64(d.pos) + offset
91 | case io.SeekEnd:
92 | next = int64(d.totalBytes) + offset
93 | }
94 | d.pos = int(next)
95 | if err := d.readUntil(d.pos); err != nil {
96 | return 0, err
97 | }
98 | return next, nil
99 | }
100 |
--------------------------------------------------------------------------------
/gfx/uniform.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import "github.com/goxjs/gl"
4 |
5 | // UniformType is the data type of a uniform
6 | type UniformType int
7 |
8 | //uniform types for shaders
9 | const (
10 | UniformFloat UniformType = iota
11 | UniformInt
12 | UniformBool
13 | UniformSampler
14 | UniformUnknown
15 | UniformBase UniformType = iota
16 | UniformVec
17 | UniformMat
18 | )
19 |
20 | // uniform represents a uniform in the shaders
21 | type uniform struct {
22 | Location gl.Uniform
23 | Type gl.Enum
24 | BaseType UniformType
25 | SecondType UniformType
26 | Count int
27 | TypeSize int
28 | Name string
29 | }
30 |
31 | func (u *uniform) CalculateTypeInfo() {
32 | u.BaseType = u.getBaseType()
33 | u.SecondType = u.getSecondType()
34 | u.TypeSize = u.getTypeSize()
35 | }
36 |
37 | func (u *uniform) getTypeSize() int {
38 | switch u.Type {
39 | case gl.INT, gl.FLOAT, gl.BOOL, gl.SAMPLER_2D, gl.SAMPLER_CUBE:
40 | return 1
41 | case gl.INT_VEC2, gl.FLOAT_VEC2, gl.FLOAT_MAT2, gl.BOOL_VEC2:
42 | return 2
43 | case gl.INT_VEC3, gl.FLOAT_VEC3, gl.FLOAT_MAT3, gl.BOOL_VEC3:
44 | return 3
45 | case gl.INT_VEC4, gl.FLOAT_VEC4, gl.FLOAT_MAT4, gl.BOOL_VEC4:
46 | return 4
47 | }
48 | return 1
49 | }
50 |
51 | func (u *uniform) getBaseType() UniformType {
52 | switch u.Type {
53 | case gl.INT, gl.INT_VEC2, gl.INT_VEC3, gl.INT_VEC4:
54 | return UniformInt
55 | case gl.FLOAT, gl.FLOAT_VEC2, gl.FLOAT_VEC3,
56 | gl.FLOAT_VEC4, gl.FLOAT_MAT2, gl.FLOAT_MAT3, gl.FLOAT_MAT4:
57 | return UniformFloat
58 | case gl.BOOL, gl.BOOL_VEC2, gl.BOOL_VEC3, gl.BOOL_VEC4:
59 | return UniformBool
60 | case gl.SAMPLER_2D, gl.SAMPLER_CUBE:
61 | return UniformSampler
62 | }
63 | return UniformUnknown
64 | }
65 |
66 | func (u uniform) getSecondType() UniformType {
67 | switch u.Type {
68 | case gl.INT_VEC2, gl.INT_VEC3, gl.INT_VEC4, gl.FLOAT_VEC2,
69 | gl.FLOAT_VEC3, gl.FLOAT_VEC4, gl.BOOL_VEC2, gl.BOOL_VEC3, gl.BOOL_VEC4:
70 | return UniformVec
71 | case gl.FLOAT_MAT2, gl.FLOAT_MAT3, gl.FLOAT_MAT4:
72 | return UniformMat
73 | }
74 | return UniformBase
75 | }
76 |
77 | func translateUniformBaseType(t UniformType) string {
78 | switch t {
79 | case UniformFloat:
80 | return "float"
81 | case UniformInt:
82 | return "int"
83 | case UniformBool:
84 | return "bool"
85 | case UniformSampler:
86 | return "sampler"
87 | }
88 | return "unknown"
89 | }
90 |
--------------------------------------------------------------------------------
/gfx/display_state.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "github.com/go-gl/mathgl/mgl32/matstack"
5 |
6 | "github.com/goxjs/gl"
7 | )
8 |
9 | // displayState track a certain point in transformations
10 | type displayState struct {
11 | color []float32
12 | backgroundColor []float32
13 | blendMode string
14 | lineWidth float32
15 | lineJoin string
16 | pointSize float32
17 | scissor bool
18 | scissorBox []int32
19 | stencilCompare CompareMode
20 | stencilTestValue int32
21 | font *Font
22 | shader *Shader
23 | colorMask ColorMask
24 | canvas *Canvas
25 | defaultFilter Filter
26 | }
27 |
28 | // glState keeps track of the context attributes
29 | type openglState struct {
30 | initialized bool
31 | boundTextures []gl.Texture
32 | curTextureUnit int
33 | viewport []int32
34 | framebufferSRGBEnabled bool
35 | defaultTexture gl.Texture
36 | defaultFBO gl.Framebuffer
37 | projectionStack *matstack.MatStack
38 | viewStack *matstack.MatStack
39 | currentCanvas *Canvas
40 | currentShader *Shader
41 | textureCounters []int
42 | writingToStencil bool
43 | }
44 |
45 | // newDisplayState initializes a display states default values
46 | func newDisplayState() displayState {
47 | return displayState{
48 | blendMode: "alpha",
49 | pointSize: 5,
50 | stencilCompare: CompareAlways,
51 | lineWidth: 1,
52 | lineJoin: "miter",
53 | shader: defaultShader,
54 | font: defaultFont,
55 | defaultFilter: newFilter(),
56 | color: []float32{1, 1, 1, 1},
57 | colorMask: ColorMask{r: true, g: true, b: true, a: true},
58 | scissorBox: make([]int32, 4),
59 | }
60 | }
61 |
62 | // displayStateStack is a simple stack for keeping track of display state.
63 | type displayStateStack struct {
64 | stack []displayState
65 | }
66 |
67 | // push a new element onto the top of the stack
68 | func (s *displayStateStack) push(state displayState) {
69 | s.stack = append(s.stack, state)
70 | }
71 |
72 | // take the top element off the stack
73 | func (s *displayStateStack) pop() displayState {
74 | var state displayState
75 | state, s.stack = s.stack[len(s.stack)-1], s.stack[:len(s.stack)-1]
76 | return state
77 | }
78 |
79 | // get the top element in the stack
80 | func (s *displayStateStack) back() *displayState {
81 | return &s.stack[len(s.stack)-1]
82 | }
83 |
--------------------------------------------------------------------------------
/file/file.go:
--------------------------------------------------------------------------------
1 | // Package file is meant to take care of all asset file opening so that file
2 | // access is safe if trying to access a bundled file or a file from the disk
3 | package file
4 |
5 | import (
6 | "archive/zip"
7 | "io"
8 | "io/ioutil"
9 | "path/filepath"
10 | "strings"
11 |
12 | "github.com/goxjs/glfw"
13 | )
14 |
15 | var (
16 | // map of zip file data related to thier real file path for consistent access
17 | zipFiles = make(map[string]*zip.File)
18 | )
19 |
20 | // Register will be called by bundled assets to register the bundled files into the
21 | // zip file data to be used by the program.
22 | func Register(data string) {
23 | zipReader, err := zip.NewReader(strings.NewReader(data), int64(len(data)))
24 | if err != nil {
25 | panic(err)
26 | }
27 | for _, file := range zipReader.File {
28 | zipFiles[file.Name] = file
29 | }
30 | }
31 |
32 | // Read will read a file at the path specified in total and return a byte
33 | // array of the file contents
34 | func Read(path string) ([]byte, error) {
35 | path = normalizePath(path)
36 | var file io.ReadCloser
37 | var err error
38 | if zipfile, ok := zipFiles[path]; ok {
39 | file, err = zipfile.Open()
40 | } else {
41 | file, err = Open(path)
42 | }
43 | if err != nil {
44 | return nil, err
45 | }
46 | defer file.Close()
47 | return ioutil.ReadAll(file)
48 | }
49 |
50 | // ReadString acts like Read but instead return a string. This is useful in certain
51 | // circumstances.
52 | func ReadString(filename string) string {
53 | s, err := Read(filename)
54 | if err != nil {
55 | return ""
56 | }
57 | return string(s[:])
58 | }
59 |
60 | // Open will return the file if its bundled or on disk and return a File interface
61 | // for the file and an error if it does not exist. The File interface allows for
62 | // consitent access to disk files and zip files.
63 | func Open(path string) (io.ReadCloser, error) {
64 | path = normalizePath(path)
65 | zipFile, ok := zipFiles[path]
66 | if !ok {
67 | return glfw.Open(path)
68 | }
69 | return zipFile.Open()
70 | }
71 |
72 | // Ext will return the extention of the file
73 | func Ext(filename string) string {
74 | return filepath.Ext(normalizePath(filename))
75 | }
76 |
77 | // normalizePath will prefix a path with assets/ if it comes from that directory
78 | // or if it is bundled in such a way.
79 | func normalizePath(filename string) string {
80 | p := strings.Replace(filename, "//", "/", -1)
81 | //bundle assets from asset folder
82 | if _, ok := zipFiles["assets/"+p]; ok {
83 | return "assets/" + p
84 | }
85 | return p
86 | }
87 |
--------------------------------------------------------------------------------
/audio/openal/decoding/vorbis.go:
--------------------------------------------------------------------------------
1 | package decoding
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/jfreymuth/oggvorbis"
7 | )
8 |
9 | func newVorbisDecoder(src io.ReadCloser) (*Decoder, error) {
10 | r, err := oggvorbis.NewReader(src)
11 | if err != nil {
12 | return nil, err
13 | }
14 |
15 | d := &vorbisReader{
16 | data: make([]float32, r.Length()*2),
17 | totalBytes: int(r.Length()) * 4,
18 | source: src,
19 | decoder: r,
20 | }
21 |
22 | return newDecoder(
23 | src,
24 | d,
25 | int16(r.Channels()),
26 | int32(r.SampleRate()),
27 | 16,
28 | int32(d.totalBytes),
29 | ), nil
30 | }
31 |
32 | type vorbisReader struct {
33 | data []float32
34 | totalBytes int
35 | readBytes int
36 | pos int
37 | source io.Closer
38 | decoder *oggvorbis.Reader
39 | }
40 |
41 | func (d *vorbisReader) readUntil(pos int) error {
42 | buffer := make([]float32, 8192)
43 | for d.readBytes < pos {
44 | n, err := d.decoder.Read(buffer)
45 | if n > 0 {
46 | if d.readBytes+n*2 > d.totalBytes {
47 | n = (d.totalBytes - d.readBytes) / 2
48 | }
49 | p := d.readBytes / 2
50 | for i := 0; i < n; i++ {
51 | d.data[p+i] = buffer[i]
52 | }
53 | d.readBytes += n * 2
54 | }
55 | if err == io.EOF {
56 | if err := d.source.Close(); err != nil {
57 | return err
58 | }
59 | break
60 | }
61 | if err != nil {
62 | return err
63 | }
64 | }
65 | return nil
66 | }
67 |
68 | func (d *vorbisReader) Read(b []byte) (int, error) {
69 | left := d.totalBytes - d.pos
70 | if left > len(b) {
71 | left = len(b)
72 | }
73 | if left <= 0 {
74 | return 0, io.EOF
75 | }
76 | left = left / 2 * 2
77 | if err := d.readUntil(d.pos + left); err != nil {
78 | return 0, err
79 | }
80 | for i := 0; i < left/2; i++ {
81 | f := d.data[d.pos/2+i]
82 | s := int16(f * (1<<15 - 1))
83 | b[2*i] = uint8(s)
84 | b[2*i+1] = uint8(s >> 8)
85 | }
86 | d.pos += left
87 | if d.pos == d.totalBytes {
88 | return left, io.EOF
89 | }
90 | return left, nil
91 | }
92 |
93 | func (d *vorbisReader) Seek(offset int64, whence int) (int64, error) {
94 | next := int64(0)
95 | switch whence {
96 | case io.SeekStart:
97 | next = offset
98 | case io.SeekCurrent:
99 | next = int64(d.pos) + offset
100 | case io.SeekEnd:
101 | next = int64(d.totalBytes) + offset
102 | }
103 | // pos should be always even
104 | next = next / 2 * 2
105 | d.pos = int(next)
106 | if err := d.readUntil(d.pos); err != nil {
107 | return 0, err
108 | }
109 | return next, nil
110 | }
111 |
--------------------------------------------------------------------------------
/audio/openal/al/const.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin linux
6 |
7 | package al
8 |
9 | // Error returns one of these error codes.
10 | const (
11 | NoError = 0x0000
12 | InvalidName = 0xA001
13 | InvalidEnum = 0xA002
14 | InvalidValue = 0xA003
15 | InvalidOperation = 0xA004
16 | OutOfMemory = 0xA005
17 | )
18 |
19 | // Distance models.
20 | const (
21 | InverseDistance = 0xD001
22 | InverseDistanceClamped = 0xD002
23 | LinearDistance = 0xD003
24 | LinearDistanceClamped = 0xD004
25 | ExponentDistance = 0xD005
26 | ExponentDistanceClamped = 0xD006
27 | )
28 |
29 | // Global parameters.
30 | const (
31 | paramDistanceModel = 0xD000
32 | paramDopplerFactor = 0xC000
33 | paramDopplerVelocity = 0xC001
34 | paramSpeedOfSound = 0xC003
35 | paramVendor = 0xB001
36 | paramVersion = 0xB002
37 | paramRenderer = 0xB003
38 | paramExtensions = 0xB004
39 | )
40 |
41 | // Source and listener parameters.
42 | const (
43 | paramPosition = 0x1004
44 | paramVelocity = 0x1006
45 | paramGain = 0x100A
46 |
47 | paramOrientation = 0x100F
48 |
49 | paramSourceRelative = 0x0202
50 | paramSourceType = 0x1027
51 | paramLooping = 0x1007
52 | paramBuffer = 0x1009
53 | paramBuffersQueued = 0x1015
54 | paramBuffersProcessed = 0x1016
55 | paramMinGain = 0x100D
56 | paramMaxGain = 0x100E
57 | paramReferenceDistance = 0x1020
58 | paramRolloffFactor = 0x1021
59 | paramMaxDistance = 0x1023
60 | paramPitch = 0x1003
61 | paramDirection = 0x1005
62 | paramConeInnerAngle = 0x1001
63 | paramConeOuterAngle = 0x1002
64 | paramConeOuterGain = 0x1022
65 | paramSecOffset = 0x1024
66 | paramSampleOffset = 0x1025
67 | paramByteOffset = 0x1026
68 | paramSourceState = 0x1010
69 | )
70 |
71 | // A source could be in the state of initial, playing, paused or stopped.
72 | const (
73 | Initial = 0x1011
74 | Playing = 0x1012
75 | Paused = 0x1013
76 | Stopped = 0x1014
77 | )
78 |
79 | // Buffer parameters.
80 | const (
81 | paramFreq = 0x2001
82 | paramBits = 0x2002
83 | paramChannels = 0x2003
84 | paramSize = 0x2004
85 | )
86 |
87 | // Audio formats. Buffer.BufferData accepts one of these formats as the data format.
88 | const (
89 | FormatMono8 = 0x1100
90 | FormatMono16 = 0x1101
91 | FormatStereo8 = 0x1102
92 | FormatStereo16 = 0x1103
93 | )
94 |
95 | // CapabilityDistanceModel represents the capability of specifying a different distance
96 | // model for each source.
97 | const CapabilityDistanceModel = Capability(0x200)
98 |
--------------------------------------------------------------------------------
/gfx/wrap/texture.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import (
4 | "github.com/yuin/gopher-lua"
5 |
6 | "github.com/tanema/amore/gfx"
7 | )
8 |
9 | const luaImageType = "Image"
10 |
11 | func toImage(ls *lua.LState, offset int) *gfx.Image {
12 | img := ls.CheckUserData(offset)
13 | if v, ok := img.Value.(*gfx.Image); ok {
14 | if v.Texture == nil {
15 | ls.ArgError(offset, "image not loaded")
16 | }
17 | return v
18 | }
19 | ls.ArgError(offset, "image expected")
20 | return nil
21 | }
22 |
23 | func toCanvas(ls *lua.LState, offset int) *gfx.Canvas {
24 | canvas := ls.CheckUserData(offset)
25 | if v, ok := canvas.Value.(*gfx.Canvas); ok {
26 | return v
27 | }
28 | ls.ArgError(offset, "canvas expected")
29 | return nil
30 | }
31 |
32 | func toTexture(ls *lua.LState, offset int) *gfx.Texture {
33 | text := ls.CheckUserData(offset)
34 | if v, ok := text.Value.(*gfx.Canvas); ok {
35 | return v.Texture
36 | } else if v, ok := text.Value.(*gfx.Image); ok {
37 | return v.Texture
38 | }
39 | ls.ArgError(offset, "texture expected")
40 | return nil
41 | }
42 |
43 | func gfxNewImage(ls *lua.LState) int {
44 | return returnUD(ls, "Image", gfx.NewImage(toString(ls, 1), ls.ToBool(2)))
45 | }
46 |
47 | func gfxNewCanvas(ls *lua.LState) int {
48 | w, h := gfx.GetDimensions()
49 | cw, ch := toIntD(ls, 1, int(w)), toIntD(ls, 2, int(h))
50 | return returnUD(ls, "Canvas", gfx.NewCanvas(int32(cw), int32(ch)))
51 | }
52 |
53 | func gfxCanvasNewImage(ls *lua.LState) int {
54 | canvas := toCanvas(ls, 1)
55 | cw, ch := canvas.GetDimensions()
56 | x, y, w, h := toIntD(ls, 2, 0), toIntD(ls, 3, 0), toIntD(ls, 4, int(cw)), toIntD(ls, 5, int(ch))
57 | img, err := canvas.NewImageData(int32(x), int32(y), int32(w), int32(h))
58 | if err == nil {
59 | return returnUD(ls, "Image", img)
60 | }
61 | ls.Push(lua.LNil)
62 | return 1
63 | }
64 |
65 | func gfxTextureDraw(ls *lua.LState) int {
66 | toTexture(ls, 1).Draw(extractFloatArray(ls, 2)...)
67 | return 0
68 | }
69 |
70 | func gfxTextureDrawq(ls *lua.LState) int {
71 | toTexture(ls, 1).Drawq(toQuad(ls, 2), extractFloatArray(ls, 3)...)
72 | return 0
73 | }
74 |
75 | func gfxTextureSetWrap(ls *lua.LState) int {
76 | toTexture(ls, 1).SetWrap(toWrap(ls, 2), toWrap(ls, 3))
77 | return 0
78 | }
79 |
80 | func gfxTextureSetFilter(ls *lua.LState) int {
81 | toTexture(ls, 1).SetFilter(toFilter(ls, 2), toFilter(ls, 3))
82 | return 0
83 | }
84 |
85 | func gfxTextureGetWidth(ls *lua.LState) int {
86 | ls.Push(lua.LNumber(int(toTexture(ls, 1).Width)))
87 | return 1
88 | }
89 |
90 | func gfxTextureGetHeight(ls *lua.LState) int {
91 | ls.Push(lua.LNumber(int(toTexture(ls, 1).Height)))
92 | return 1
93 | }
94 |
95 | func gfxTextureGetDimensions(ls *lua.LState) int {
96 | text := toTexture(ls, 1)
97 | ls.Push(lua.LNumber(int(text.Width)))
98 | ls.Push(lua.LNumber(int(text.Height)))
99 | return 2
100 | }
101 |
--------------------------------------------------------------------------------
/gfx/wrap/sprite_batch.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import (
4 | "github.com/yuin/gopher-lua"
5 |
6 | "github.com/tanema/amore/gfx"
7 | )
8 |
9 | func toSpriteBatch(ls *lua.LState, offset int) *gfx.SpriteBatch {
10 | img := ls.CheckUserData(offset)
11 | if v, ok := img.Value.(*gfx.SpriteBatch); ok {
12 | return v
13 | }
14 | ls.ArgError(offset, "sprite batch expected")
15 | return nil
16 | }
17 |
18 | func gfxNewSpriteBatch(ls *lua.LState) int {
19 | return returnUD(
20 | ls,
21 | "SpriteBatch",
22 | gfx.NewSpriteBatch(toTexture(ls, 1), toIntD(ls, 2, 1000), toUsage(ls, 3)),
23 | )
24 | }
25 |
26 | func gfxSpriteBatchAdd(ls *lua.LState) int {
27 | toSpriteBatch(ls, 1).Add(extractFloatArray(ls, 2)...)
28 | return 0
29 | }
30 |
31 | func gfxSpriteBatchAddq(ls *lua.LState) int {
32 | toSpriteBatch(ls, 1).Addq(toQuad(ls, 2), extractFloatArray(ls, 3)...)
33 | return 0
34 | }
35 |
36 | func gfxSpriteBatchSet(ls *lua.LState) int {
37 | toSpriteBatch(ls, 1).Set(toInt(ls, 2), extractFloatArray(ls, 3)...)
38 | return 0
39 | }
40 |
41 | func gfxSpriteBatchSetq(ls *lua.LState) int {
42 | toSpriteBatch(ls, 1).Setq(toInt(ls, 2), toQuad(ls, 3), extractFloatArray(ls, 4)...)
43 | return 0
44 | }
45 |
46 | func gfxSpriteBatchClear(ls *lua.LState) int {
47 | toSpriteBatch(ls, 1).Clear()
48 | return 0
49 | }
50 |
51 | func gfxSpriteBatchSetTexture(ls *lua.LState) int {
52 | toSpriteBatch(ls, 1).SetTexture(toTexture(ls, 2))
53 | return 0
54 | }
55 |
56 | func gfxSpriteBatchGetTexture(ls *lua.LState) int {
57 | return returnUD(ls, "Image", toSpriteBatch(ls, 1).GetTexture())
58 | }
59 |
60 | func gfxSpriteBatchSetColor(ls *lua.LState) int {
61 | batch := toSpriteBatch(ls, 1)
62 | if len(extractFloatArray(ls, 2)) == 0 {
63 | batch.ClearColor()
64 | } else {
65 | r, g, b, a := extractColor(ls, 2)
66 | batch.SetColor(r, g, b, a)
67 | }
68 | return 0
69 | }
70 |
71 | func gfxSpriteBatchGetColor(ls *lua.LState) int {
72 | batch := toSpriteBatch(ls, 1)
73 | for _, x := range batch.GetColor() {
74 | ls.Push(lua.LNumber(x))
75 | }
76 | return 4
77 | }
78 |
79 | func gfxSpriteBatchGetCount(ls *lua.LState) int {
80 | ls.Push(lua.LNumber(toSpriteBatch(ls, 1).GetCount()))
81 | return 1
82 | }
83 |
84 | func gfxSpriteBatchSetBufferSize(ls *lua.LState) int {
85 | toSpriteBatch(ls, 1).SetBufferSize(toInt(ls, 2))
86 | return 0
87 | }
88 |
89 | func gfxSpriteBatchGetBufferSize(ls *lua.LState) int {
90 | ls.Push(lua.LNumber(toSpriteBatch(ls, 1).GetBufferSize()))
91 | return 1
92 | }
93 |
94 | func gfxSpriteBatchSetDrawRange(ls *lua.LState) int {
95 | toSpriteBatch(ls, 1).SetDrawRange(toIntD(ls, 2, -1), toIntD(ls, 3, -1))
96 | return 0
97 | }
98 |
99 | func gfxSpriteBatchGetDrawRange(ls *lua.LState) int {
100 | min, max := toSpriteBatch(ls, 1).GetDrawRange()
101 | ls.Push(lua.LNumber(min))
102 | ls.Push(lua.LNumber(max))
103 | return 2
104 | }
105 |
106 | func gfxSpriteBatchDraw(ls *lua.LState) int {
107 | toSpriteBatch(ls, 1).Draw(extractFloatArray(ls, 2)...)
108 | return 0
109 | }
110 |
--------------------------------------------------------------------------------
/input/input.go:
--------------------------------------------------------------------------------
1 | package input
2 |
3 | import (
4 | "github.com/goxjs/glfw"
5 | "github.com/yuin/gopher-lua"
6 |
7 | "github.com/tanema/amore/runtime"
8 | )
9 |
10 | type inputCapture struct {
11 | ls *lua.LState
12 | isInside bool
13 | mousex, mousey float64
14 | scrollx float64
15 | scrolly float64
16 | mouseButtons map[string]bool
17 | keys map[string]bool
18 | }
19 |
20 | var currentCapture inputCapture
21 |
22 | func init() {
23 | runtime.RegisterHook(func(ls *lua.LState, window *glfw.Window) {
24 | currentCapture = inputCapture{
25 | ls: ls,
26 | mouseButtons: map[string]bool{},
27 | keys: map[string]bool{},
28 | }
29 | window.SetCursorEnterCallback(currentCapture.mouseEnter)
30 | window.SetMouseMovementCallback(currentCapture.mouseMove)
31 | window.SetScrollCallback(currentCapture.mouseScroll)
32 | window.SetMouseButtonCallback(currentCapture.mouseButton)
33 | window.SetKeyCallback(currentCapture.key)
34 | })
35 | }
36 |
37 | func (input *inputCapture) dispatch(device, button, action string, modifiers []string) {
38 | callback := input.ls.GetGlobal("oninput")
39 | if callback == nil {
40 | return
41 | }
42 |
43 | luaModifiers := input.ls.NewTable()
44 | for _, mod := range modifiers {
45 | luaModifiers.Append(lua.LString(mod))
46 | }
47 |
48 | input.ls.CallByParam(
49 | lua.P{Fn: callback, Protect: true},
50 | lua.LString(device),
51 | lua.LString(button),
52 | lua.LString(action),
53 | luaModifiers,
54 | )
55 | }
56 |
57 | func (input *inputCapture) mouseEnter(w *glfw.Window, entered bool) { input.isInside = entered }
58 |
59 | func (input *inputCapture) mouseScroll(w *glfw.Window, xoff, yoff float64) {
60 | input.scrollx, input.scrolly = xoff, yoff
61 | }
62 |
63 | func (input *inputCapture) mouseMove(w *glfw.Window, xpos, ypos, xdelta, ydelta float64) {
64 | input.mousex, input.mousey = xpos, ypos
65 | }
66 |
67 | func (input *inputCapture) mouseButton(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
68 | buttonName := mouseButtons[button]
69 | if action == glfw.Press {
70 | input.mouseButtons[buttonName] = true
71 | } else if action == glfw.Release {
72 | input.mouseButtons[buttonName] = false
73 | }
74 | input.dispatch("mouse", buttonName, actions[action], expandModifiers(mods))
75 | }
76 |
77 | func (input *inputCapture) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
78 | buttonName := keyboardMap[key]
79 | if action == glfw.Press {
80 | input.keys[buttonName] = true
81 | } else if action == glfw.Release {
82 | input.keys[buttonName] = false
83 | }
84 | input.dispatch("keyboard", buttonName, actions[action], expandModifiers(mods))
85 | }
86 |
87 | func expandModifiers(keys glfw.ModifierKey) []string {
88 | mods := []string{}
89 | if keys&glfw.ModShift == glfw.ModShift {
90 | mods = append(mods, "shift")
91 | }
92 | if keys&glfw.ModControl == glfw.ModControl {
93 | mods = append(mods, "control")
94 | }
95 | if keys&glfw.ModAlt == glfw.ModAlt {
96 | mods = append(mods, "alt")
97 | }
98 | if keys&glfw.ModSuper == glfw.ModSuper {
99 | mods = append(mods, "super")
100 | }
101 | return mods
102 | }
103 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
2 | github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d h1:HB5J9+f1xpkYLgWQ/RqEcbp3SEufyOIMYLoyKNKiG7E=
4 | github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d/go.mod h1:CHkHWWZ4kbGY6jEy1+qlitDaCtRgNvCOQdakj/1Yl/Q=
5 | github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1 h1:wl/ggSfTHqAy46hyzw1IlrUYwjqmXYvbJyPdH3rT7YE=
6 | github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1/go.mod h1:frG94byMNy+1CgGrQ25dZ+17tf98EN+OYBQL4Zh612M=
7 | github.com/go-gl/gl v0.0.0-20180407155706-68e253793080 h1:pNxZva3052YM+z2p1aP08FgaTE2NzrRJZ5BHJCmKLzE=
8 | github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
9 | github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM=
10 | github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
11 | github.com/go-gl/mathgl v0.0.0-20180319210751-5ab0e04e1f55 h1:XK0Hs1nb11ih2vOSaLRnsjeqDJsd0QZAy5aVE2RUGZk=
12 | github.com/go-gl/mathgl v0.0.0-20180319210751-5ab0e04e1f55/go.mod h1:dvrdneKbyWbK2skTda0nM4B9zSlS2GZSnjX7itr/skQ=
13 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
14 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
15 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
16 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
17 | github.com/goxjs/gl v0.0.0-20171128034433-dc8f4a9a3c9c h1:naEGFd9BJuTM/1m2lP07Bl9O7O6Jqe+bUplBfAwdygg=
18 | github.com/goxjs/gl v0.0.0-20171128034433-dc8f4a9a3c9c/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
19 | github.com/goxjs/glfw v0.0.0-20171018044755-7dec05603e06 h1:6jKIS84Kj8pXaqte+73xLvaT+OGt0+fs+TbFczltZP0=
20 | github.com/goxjs/glfw v0.0.0-20171018044755-7dec05603e06/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
21 | github.com/hajimehoshi/go-mp3 v0.1.0 h1:nYqjO6rub69tJFI1JJSItb5xFi1z0jBED3INDwyKKlc=
22 | github.com/hajimehoshi/go-mp3 v0.1.0/go.mod h1:SUuVIxBoRGgFS+RYeGIJoiD2wUWy7EajN5tmFlh2ncc=
23 | github.com/jfreymuth/oggvorbis v1.0.0 h1:aOpiihGrFLXpsh2osOlEvTcg5/aluzGQeC7m3uYWOZ0=
24 | github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
25 | github.com/jfreymuth/vorbis v1.0.0 h1:SmDf783s82lIjGZi8EGUUaS7YxPHgRj4ZXW/h7rUi7U=
26 | github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
27 | golang.org/x/image v0.0.0-20180403161127-f315e4403028 h1:jFFHllCVd24xe3zq5lxsTaMRYoTWSrdWOIA5PEltZ8I=
28 | golang.org/x/image v0.0.0-20180403161127-f315e4403028/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
29 | golang.org/x/mobile v0.0.0-20180501173530-c909788f9903 h1:GxVK7c/X4uagszrFQaHrcQ1DPlEnODQ0Zh/yFmDMgDU=
30 | golang.org/x/mobile v0.0.0-20180501173530-c909788f9903/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
31 | honnef.co/go/js/dom v0.0.0-20181202134054-9dbdcd412bde h1:a/zxkB+dOtFR2DO19YIQtrpHfuZVprzJsA19Og0p53A=
32 | honnef.co/go/js/dom v0.0.0-20181202134054-9dbdcd412bde/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
33 |
--------------------------------------------------------------------------------
/gfx/wrap/text.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import (
4 | "github.com/yuin/gopher-lua"
5 |
6 | "github.com/tanema/amore/gfx"
7 | )
8 |
9 | func extractPrintable(ls *lua.LState, offset int) ([]string, [][]float32) {
10 | strs := []string{}
11 | colors := [][]float32{}
12 |
13 | val1 := ls.Get(offset)
14 | if val1.Type() == lua.LTTable {
15 | table := val1.(*lua.LTable)
16 | rawstrs, strok := (table.RawGetInt(1)).(*lua.LTable)
17 | rawclrs, clrok := table.RawGetInt(2).(*lua.LTable)
18 | if !strok || !clrok {
19 | ls.ArgError(offset, "unexpected argument type")
20 | }
21 |
22 | rawstrs.ForEach(func(index lua.LValue, str lua.LValue) {
23 | strs = append(strs, str.String())
24 | })
25 |
26 | rawclrs.ForEach(func(index lua.LValue, color lua.LValue) {
27 | setColor := []float32{}
28 | (color.(*lua.LTable)).ForEach(func(index lua.LValue, color lua.LValue) {
29 | setColor = append(setColor, float32(color.(lua.LNumber)))
30 | })
31 | if len(setColor) == 3 {
32 | setColor = append(setColor, 1)
33 | } else if len(setColor) < 4 {
34 | ls.ArgError(offset, "not enough values for a color")
35 | }
36 | colors = append(colors, setColor)
37 | })
38 | } else if val1.Type() == lua.LTString {
39 | return []string{val1.String()}, [][]float32{gfx.GetColor()}
40 | } else {
41 | ls.ArgError(offset, "unexpected argument type")
42 | }
43 |
44 | return strs, colors
45 | }
46 |
47 | func toText(ls *lua.LState, offset int) *gfx.Text {
48 | img := ls.CheckUserData(offset)
49 | if v, ok := img.Value.(*gfx.Text); ok {
50 | return v
51 | }
52 | ls.ArgError(offset, "text expected")
53 | return nil
54 | }
55 |
56 | func gfxPrint(ls *lua.LState) int {
57 | str, clrs := extractPrintable(ls, 1)
58 | args := extractFloatArray(ls, 2)
59 | gfx.Print(str, clrs, args...)
60 | return 0
61 | }
62 |
63 | func gfxPrintf(ls *lua.LState) int {
64 | str, clrs := extractPrintable(ls, 1)
65 | wrap := toFloat(ls, 2)
66 | align := toString(ls, 3)
67 | args := extractFloatArray(ls, 4)
68 | gfx.Printf(str, clrs, wrap, align, args...)
69 | return 0
70 | }
71 |
72 | func gfxNewText(ls *lua.LState) int {
73 | str, clrs := extractPrintable(ls, 2)
74 | text := gfx.NewText(toFont(ls, 1), str, clrs, toFloatD(ls, 3, -1), toStringD(ls, 4, "left"))
75 | return returnUD(ls, "Text", text)
76 | }
77 |
78 | func gfxTextDraw(ls *lua.LState) int {
79 | txt := toText(ls, 1)
80 | txt.Draw(extractFloatArray(ls, 2)...)
81 | return 0
82 | }
83 |
84 | func gfxTextSet(ls *lua.LState) int {
85 | txt := toText(ls, 1)
86 | str, clrs := extractPrintable(ls, 2)
87 | txt.Set(str, clrs)
88 | return 0
89 | }
90 |
91 | func gfxTextGetWidth(ls *lua.LState) int {
92 | txt := toText(ls, 1)
93 | ls.Push(lua.LNumber(txt.GetWidth()))
94 | return 1
95 | }
96 |
97 | func gfxTextGetHeight(ls *lua.LState) int {
98 | txt := toText(ls, 1)
99 | ls.Push(lua.LNumber(txt.GetHeight()))
100 | return 1
101 | }
102 |
103 | func gfxTextGetDimensions(ls *lua.LState) int {
104 | txt := toText(ls, 1)
105 | w, h := txt.GetDimensions()
106 | ls.Push(lua.LNumber(w))
107 | ls.Push(lua.LNumber(h))
108 | return 2
109 | }
110 |
111 | func gfxTextGetFont(ls *lua.LState) int {
112 | return returnUD(ls, "Font", toText(ls, 1).GetFont())
113 | }
114 |
115 | func gfxTextSetFont(ls *lua.LState) int {
116 | txt := toText(ls, 1)
117 | txt.SetFont(toFont(ls, 2))
118 | return 0
119 | }
120 |
--------------------------------------------------------------------------------
/gfx/quad_indicies.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "github.com/goxjs/gl"
5 | )
6 |
7 | type indexBuffer struct {
8 | isBound bool // Whether the buffer is currently bound.
9 | ibo gl.Buffer // The IBO identifier. Assigned by OpenGL.
10 | data []uint32 // A pointer to mapped memory.
11 | }
12 |
13 | func newIndexBuffer(data []uint32) *indexBuffer {
14 | newBuffer := &indexBuffer{data: data}
15 | registerVolatile(newBuffer)
16 | return newBuffer
17 | }
18 |
19 | func (buffer *indexBuffer) bind() {
20 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.ibo)
21 | buffer.isBound = true
22 | }
23 |
24 | func (buffer *indexBuffer) unbind() {
25 | if buffer.isBound {
26 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.Buffer{})
27 | }
28 | buffer.isBound = false
29 | }
30 |
31 | func (buffer *indexBuffer) drawElements(mode uint32, offset, size int) {
32 | buffer.bind()
33 | defer buffer.unbind()
34 | gl.DrawElements(gl.Enum(mode), size, gl.UNSIGNED_INT, offset*4)
35 | }
36 |
37 | func (buffer *indexBuffer) loadVolatile() bool {
38 | buffer.ibo = gl.CreateBuffer()
39 | buffer.bind()
40 | defer buffer.unbind()
41 | gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, ui32Bytes(buffer.data), gl.STATIC_DRAW)
42 | return true
43 | }
44 |
45 | func (buffer *indexBuffer) unloadVolatile() {
46 | gl.DeleteBuffer(buffer.ibo)
47 | }
48 |
49 | /**
50 | * QuadIndices manages one shared buffer that stores the indices for an
51 | * element array. Vertex arrays using the vertex structure (or anything else
52 | * that can use the pattern below) can request a size and use it for the
53 | * drawElements call.
54 | *
55 | * 0----2
56 | * | / |
57 | * | / |
58 | * 1----3
59 | *
60 | * indices[i*6 + 0] = i*4 + 0;
61 | * indices[i*6 + 1] = i*4 + 1;
62 | * indices[i*6 + 2] = i*4 + 2;
63 | *
64 | * indices[i*6 + 3] = i*4 + 2;
65 | * indices[i*6 + 4] = i*4 + 1;
66 | * indices[i*6 + 5] = i*4 + 3;
67 | *
68 | * There will always be a large enough buffer around until all
69 | * QuadIndices instances have been deleted.
70 | *
71 | * Q: Why have something like QuadIndices?
72 | * A: The indices for the SpriteBatch do not change, only the array size
73 | * varies. Using one buffer for all element arrays removes this
74 | * duplicated data and saves some memory.
75 | */
76 | type quadIndices struct {
77 | *indexBuffer
78 | }
79 |
80 | func newQuadIndices(size int) *quadIndices {
81 | indices := make([]uint32, size*6)
82 | for i := 0; i < size; i++ {
83 | indices[i*6+0] = uint32(i*4 + 0)
84 | indices[i*6+1] = uint32(i*4 + 1)
85 | indices[i*6+2] = uint32(i*4 + 2)
86 |
87 | indices[i*6+3] = uint32(i*4 + 2)
88 | indices[i*6+4] = uint32(i*4 + 1)
89 | indices[i*6+5] = uint32(i*4 + 3)
90 | }
91 |
92 | return &quadIndices{
93 | indexBuffer: newIndexBuffer(indices),
94 | }
95 | }
96 |
97 | func newAltQuadIndices(size int) *quadIndices {
98 | indices := make([]uint32, size*6)
99 | for i := 0; i < size; i++ {
100 | indices[i*6+0] = uint32(i*4 + 0)
101 | indices[i*6+1] = uint32(i*4 + 1)
102 | indices[i*6+2] = uint32(i*4 + 2)
103 |
104 | indices[i*6+3] = uint32(i*4 + 2)
105 | indices[i*6+4] = uint32(i*4 + 3)
106 | indices[i*6+5] = uint32(i*4 + 1)
107 | }
108 |
109 | return &quadIndices{
110 | indexBuffer: newIndexBuffer(indices),
111 | }
112 | }
113 |
114 | func (qi *quadIndices) drawElements(mode uint32, offset, size int) {
115 | qi.indexBuffer.drawElements(mode, offset*6, size*6)
116 | }
117 |
--------------------------------------------------------------------------------
/audio/openal/pool.go:
--------------------------------------------------------------------------------
1 | package openal
2 |
3 | import (
4 | "sync"
5 | "time"
6 |
7 | "github.com/tanema/amore/audio/openal/al"
8 | )
9 |
10 | const maxSources = 64
11 |
12 | var pool *audioPool
13 |
14 | // audioPool manages all openAL generated sources.
15 | type audioPool struct {
16 | mutex sync.Mutex
17 | totalSources int
18 | sources [maxSources]al.Source
19 | available []al.Source
20 | playing map[al.Source]*Source
21 | }
22 |
23 | // init will open the audio interface.
24 | func init() {
25 | if err := al.OpenDevice(); err != nil {
26 | panic(err)
27 | }
28 | }
29 |
30 | // GetVolume returns the master volume.
31 | func GetVolume() float32 { return al.ListenerGain() }
32 |
33 | // SetVolume sets the master volume
34 | func SetVolume(gain float32) {
35 | al.SetListenerGain(gain)
36 | }
37 |
38 | // PauseAll will pause all sources
39 | func PauseAll() {
40 | for _, source := range pool.playing {
41 | source.Pause()
42 | }
43 | }
44 |
45 | // PlayAll will play all sources
46 | func PlayAll() {
47 | for _, source := range pool.playing {
48 | source.Play()
49 | }
50 | }
51 |
52 | // RewindAll will rewind all sources
53 | func RewindAll() {
54 | for _, source := range pool.playing {
55 | source.Rewind()
56 | }
57 | }
58 |
59 | // StopAll stop all sources
60 | func StopAll() {
61 | for _, source := range pool.playing {
62 | source.Stop()
63 | }
64 | pool.playing = make(map[al.Source]*Source)
65 | }
66 |
67 | // createPool generates a new pool and gets the max sources.
68 | func createPool() {
69 | pool = &audioPool{
70 | sources: [maxSources]al.Source{},
71 | available: []al.Source{},
72 | playing: make(map[al.Source]*Source),
73 | }
74 |
75 | // Generate sources.
76 | for i := 0; i < maxSources; i++ {
77 | pool.sources[i] = al.GenSources(1)[0]
78 |
79 | // We might hit an implementation-dependent limit on the total number
80 | // of sources before reaching maxSources.
81 | if al.Error() != 0 {
82 | break
83 | }
84 |
85 | pool.totalSources++
86 | }
87 |
88 | if pool.totalSources < 4 {
89 | panic("Could not generate audio sources.")
90 | }
91 |
92 | // Make all sources available initially.
93 | for i := 0; i < pool.totalSources; i++ {
94 | pool.available = append(pool.available, pool.sources[i])
95 | }
96 |
97 | go func() {
98 | ticker := time.NewTicker(1 * time.Second)
99 | go func() {
100 | for {
101 | select {
102 | case <-ticker.C:
103 | pool.update()
104 | }
105 | }
106 | }()
107 | }()
108 | }
109 |
110 | // update will cycle through al the playing sources and updates the buffers
111 | func (p *audioPool) update() {
112 | for _, source := range p.playing {
113 | if !source.update() {
114 | source.Stop()
115 | }
116 | }
117 | }
118 |
119 | // getSourceCount will get the count of playing sources
120 | func (p *audioPool) getSourceCount() int {
121 | return len(p.playing)
122 | }
123 |
124 | // claim will take an openAL source for playing with an amore source
125 | func (p *audioPool) claim(source *Source) bool {
126 | if len(p.available) > 0 {
127 | source.source, p.available = p.available[len(p.available)-1], p.available[:len(p.available)-1]
128 | p.playing[source.source] = source
129 | return true
130 | }
131 | return false
132 | }
133 |
134 | // release will put an openAL source back in to the available queue
135 | func (p *audioPool) release(source *Source) {
136 | p.available = append(p.available, source.source)
137 | delete(p.playing, source.source)
138 | source.source = 0
139 | }
140 |
--------------------------------------------------------------------------------
/gfx/vertex_buffer.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "math"
5 |
6 | "github.com/goxjs/gl"
7 | )
8 |
9 | type vertexBuffer struct {
10 | isBound bool // Whether the buffer is currently bound.
11 | usage Usage // Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW.
12 | vbo gl.Buffer // The VBO identifier. Assigned by OpenGL.
13 | data []float32 // A pointer to mapped memory.
14 | modifiedOffset int
15 | modifiedSize int
16 | }
17 |
18 | func newVertexBuffer(size int, data []float32, usage Usage) *vertexBuffer {
19 | newBuffer := &vertexBuffer{
20 | usage: usage,
21 | data: make([]float32, size),
22 | }
23 | if len(data) > 0 {
24 | copy(newBuffer.data, data[:size])
25 | }
26 | registerVolatile(newBuffer)
27 | return newBuffer
28 | }
29 |
30 | func (buffer *vertexBuffer) bufferStatic() {
31 | if buffer.modifiedSize == 0 {
32 | return
33 | }
34 | // Upload the mapped data to the buffer.
35 | gl.BufferSubData(gl.ARRAY_BUFFER, buffer.modifiedOffset*4, f32Bytes(buffer.data))
36 | }
37 |
38 | func (buffer *vertexBuffer) bufferStream() {
39 | gl.BufferData(gl.ARRAY_BUFFER, f32Bytes(buffer.data), gl.Enum(buffer.usage))
40 | }
41 |
42 | func (buffer *vertexBuffer) bufferData() {
43 | if buffer.modifiedSize != 0 { //if there is no modified size might as well do the whole buffer
44 | buffer.modifiedOffset = int(math.Min(float64(buffer.modifiedOffset), float64(len(buffer.data)-1)))
45 | buffer.modifiedSize = int(math.Min(float64(buffer.modifiedSize), float64(len(buffer.data)-buffer.modifiedOffset)))
46 | } else {
47 | buffer.modifiedOffset = 0
48 | buffer.modifiedSize = len(buffer.data)
49 | }
50 |
51 | buffer.bind()
52 | if buffer.modifiedSize > 0 {
53 | switch buffer.usage {
54 | case UsageStatic:
55 | buffer.bufferStatic()
56 | case UsageStream:
57 | buffer.bufferStream()
58 | case UsageDynamic:
59 | // It's probably more efficient to treat it like a streaming buffer if
60 | // at least a third of its contents have been modified during the map().
61 | if buffer.modifiedSize >= len(buffer.data)/3 {
62 | buffer.bufferStream()
63 | } else {
64 | buffer.bufferStatic()
65 | }
66 | }
67 | }
68 | buffer.modifiedOffset = 0
69 | buffer.modifiedSize = 0
70 | }
71 |
72 | func (buffer *vertexBuffer) bind() {
73 | gl.BindBuffer(gl.ARRAY_BUFFER, buffer.vbo)
74 | buffer.isBound = true
75 | }
76 |
77 | func (buffer *vertexBuffer) unbind() {
78 | if buffer.isBound {
79 | gl.BindBuffer(gl.ARRAY_BUFFER, gl.Buffer{})
80 | }
81 | buffer.isBound = false
82 | }
83 |
84 | func (buffer *vertexBuffer) fill(offset int, data []float32) {
85 | copy(buffer.data[offset:], data[:])
86 | if !buffer.vbo.Valid() {
87 | return
88 | }
89 | // We're being conservative right now by internally marking the whole range
90 | // from the start of section a to the end of section b as modified if both
91 | // a and b are marked as modified.
92 | oldRangeEnd := buffer.modifiedOffset + buffer.modifiedSize
93 | buffer.modifiedOffset = int(math.Min(float64(buffer.modifiedOffset), float64(offset)))
94 | newRangeEnd := int(math.Max(float64(offset+len(data)), float64(oldRangeEnd)))
95 | buffer.modifiedSize = newRangeEnd - buffer.modifiedOffset
96 | buffer.bufferData()
97 | }
98 |
99 | func (buffer *vertexBuffer) loadVolatile() bool {
100 | buffer.vbo = gl.CreateBuffer()
101 | buffer.bind()
102 | defer buffer.unbind()
103 | gl.BufferData(gl.ARRAY_BUFFER, f32Bytes(buffer.data), gl.Enum(buffer.usage))
104 | return true
105 | }
106 |
107 | func (buffer *vertexBuffer) unloadVolatile() {
108 | gl.DeleteBuffer(buffer.vbo)
109 | }
110 |
--------------------------------------------------------------------------------
/audio/openal/decoding/wav.go:
--------------------------------------------------------------------------------
1 | package decoding
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "os"
10 | )
11 |
12 | type waveDecoder struct {
13 | io.Reader
14 | src io.ReadCloser
15 | header *riffHeader
16 | info *riffChunkFmt
17 | firstSamplePos uint32
18 | dataSize int32
19 | }
20 |
21 | type riffHeader struct {
22 | Ftype [4]byte
23 | ChunkSize uint32
24 | ChunkFormat [4]byte
25 | }
26 |
27 | type riffChunkFmt struct {
28 | LengthOfHeader uint32
29 | AudioFormat uint16 // 1 = PCM not compressed
30 | NumChannels uint16
31 | SampleRate uint32
32 | BytesPerSec uint32
33 | BytesPerBloc uint16
34 | BitsPerSample uint16
35 | }
36 |
37 | func newWaveDecoder(src io.ReadCloser) (*Decoder, error) {
38 | decoder := &waveDecoder{
39 | src: src,
40 | Reader: src,
41 | info: &riffChunkFmt{},
42 | header: &riffHeader{},
43 | }
44 |
45 | if err := decoder.decode(); err != nil {
46 | return nil, err
47 | }
48 |
49 | return newDecoder(
50 | src,
51 | decoder,
52 | int16(decoder.info.NumChannels),
53 | int32(decoder.info.SampleRate),
54 | int16(decoder.info.BitsPerSample),
55 | decoder.dataSize,
56 | ), nil
57 | }
58 |
59 | func (decoder *waveDecoder) decode() error {
60 | if err := binary.Read(decoder.src, binary.LittleEndian, decoder.header); err != nil {
61 | return err
62 | }
63 |
64 | if !bytes.Equal(decoder.header.Ftype[:], []byte("RIFF")) ||
65 | !bytes.Equal(decoder.header.ChunkFormat[:], []byte("WAVE")) {
66 | return errors.New("not a RIFF/WAVE file")
67 | }
68 |
69 | var chunk [4]byte
70 | var chunkSize uint32
71 | for {
72 | // Read next chunkID
73 | err := binary.Read(decoder.src, binary.BigEndian, &chunk)
74 | if err == io.EOF {
75 | return io.ErrUnexpectedEOF
76 | } else if err != nil {
77 | return err
78 | }
79 |
80 | // and it's size in bytes
81 | err = binary.Read(decoder.src, binary.LittleEndian, &chunkSize)
82 | if err == io.EOF {
83 | return io.ErrUnexpectedEOF
84 | } else if err != nil {
85 | return err
86 | }
87 |
88 | seeker := decoder.src.(io.Seeker)
89 | if bytes.Equal(chunk[:], []byte("fmt ")) {
90 | // seek 4 bytes back because riffChunkFmt reads the chunkSize again
91 | if _, err = seeker.Seek(-4, os.SEEK_CUR); err != nil {
92 | return err
93 | }
94 |
95 | if err = binary.Read(decoder.src, binary.LittleEndian, decoder.info); err != nil {
96 | return err
97 | }
98 | if decoder.info.LengthOfHeader > 16 { // canonical format if chunklen == 16
99 | // Skip extra params
100 | if _, err = seeker.Seek(int64(decoder.info.LengthOfHeader-16), os.SEEK_CUR); err != nil {
101 | return err
102 | }
103 | }
104 |
105 | // Is audio supported ?
106 | if decoder.info.AudioFormat != 1 {
107 | return fmt.Errorf("Audio Format not supported")
108 | }
109 | } else if bytes.Equal(chunk[:], []byte("data")) {
110 | size, _ := seeker.Seek(0, os.SEEK_CUR)
111 | decoder.firstSamplePos = uint32(size)
112 | decoder.dataSize = int32(chunkSize)
113 | break
114 | } else {
115 | if _, err = seeker.Seek(int64(chunkSize), os.SEEK_CUR); err != nil {
116 | return err
117 | }
118 | }
119 | }
120 |
121 | return nil
122 | }
123 |
124 | func (decoder *waveDecoder) Seek(offset int64, whence int) (int64, error) {
125 | seeker := decoder.src.(io.Seeker)
126 | switch whence {
127 | case io.SeekStart:
128 | offset += int64(decoder.firstSamplePos)
129 | case io.SeekCurrent:
130 | case io.SeekEnd:
131 | offset += int64(decoder.firstSamplePos) + int64(decoder.dataSize)
132 | whence = io.SeekStart
133 | }
134 | return seeker.Seek(offset, whence)
135 | }
136 |
--------------------------------------------------------------------------------
/gfx/font_rasterizers.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "image"
5 | "image/draw"
6 | "math"
7 | "unicode"
8 |
9 | "golang.org/x/image/font"
10 | "golang.org/x/image/math/fixed"
11 | )
12 |
13 | type (
14 | rasterizer struct {
15 | face font.Face
16 | atlasImg *image.RGBA
17 | texture *Texture
18 | mapping map[rune]glyphData
19 | lineHeight float32
20 | ascent float32
21 | descent float32
22 | advance float32
23 | }
24 | glyphData struct {
25 | quad *Quad
26 | advance float32
27 | descent float32
28 | lsb float32
29 | rsb float32
30 | }
31 | )
32 |
33 | const glyphPadding int = 2
34 |
35 | func newRasterizer(face font.Face, runeSets ...[]rune) *rasterizer {
36 | runes := uniqRunesForSets(runeSets...)
37 | imageWidth, imageHeight, advance, height := calcSquareMapping(face, runes)
38 | atlasImg := image.NewRGBA(image.Rect(0, 0, imageWidth, imageHeight))
39 | mapping := make(map[rune]glyphData)
40 | var gx, gy int
41 | for _, r := range runes {
42 | rect, srcImg, srcPoint, adv, ok := face.Glyph(fixed.P(gx, gy+height), r)
43 | if !ok {
44 | continue
45 | }
46 | draw.Draw(atlasImg, rect, srcImg, srcPoint, draw.Src)
47 | mapping[r] = glyphData{
48 | descent: float32(rect.Min.Y - gy),
49 | lsb: float32(rect.Min.X - gx),
50 | rsb: i2f(adv) - float32((rect.Max.X - gx)),
51 | quad: NewQuad(
52 | int32(rect.Min.X), int32(rect.Min.Y),
53 | int32(rect.Dx()), int32(rect.Dy()),
54 | int32(imageWidth), int32(imageHeight),
55 | ),
56 | advance: i2f(adv),
57 | }
58 |
59 | gx += advance + glyphPadding
60 | if gx+advance >= imageWidth {
61 | gx = 0
62 | gy += height + glyphPadding
63 | }
64 | }
65 |
66 | newRast := &rasterizer{
67 | face: face,
68 | atlasImg: atlasImg,
69 | mapping: mapping,
70 | ascent: i2f(face.Metrics().Ascent),
71 | descent: i2f(face.Metrics().Descent),
72 | lineHeight: i2f(face.Metrics().Height) * 1.25,
73 | advance: float32(advance),
74 | }
75 |
76 | registerVolatile(newRast)
77 | return newRast
78 | }
79 |
80 | func (rast *rasterizer) loadVolatile() bool {
81 | rast.texture = newImageTexture(rast.atlasImg, false)
82 | return true
83 | }
84 |
85 | func (rast *rasterizer) unloadVolatile() {}
86 |
87 | func uniqRunesForSets(runeSets ...[]rune) []rune {
88 | seen := make(map[rune]bool)
89 | runes := []rune{unicode.ReplacementChar}
90 | for _, set := range runeSets {
91 | for _, r := range set {
92 | if !seen[r] {
93 | runes = append(runes, r)
94 | seen[r] = true
95 | }
96 | }
97 | }
98 | return runes
99 | }
100 |
101 | func calcSquareMapping(face font.Face, runes []rune) (imageWidth, imageHeight, advance, height int) {
102 | var maxAdvance fixed.Int26_6
103 | for _, r := range runes {
104 | _, adv, ok := face.GlyphBounds(r)
105 | if !ok {
106 | continue
107 | }
108 | if adv > maxAdvance {
109 | maxAdvance = adv
110 | }
111 | }
112 | a := i2f(maxAdvance)
113 | h := i2f(face.Metrics().Ascent + face.Metrics().Descent)
114 | squareWidth := float32(math.Ceil(math.Sqrt(float64(len(runes)))))
115 | imageWidth = int(pow2(uint32(a * squareWidth)))
116 | imageHeight = int(pow2(uint32(h * squareWidth)))
117 | return imageWidth, imageHeight, ceil(a), ceil(h)
118 | }
119 |
120 | func i2f(i fixed.Int26_6) float32 {
121 | return float32(i) / (1 << 6)
122 | }
123 |
124 | func f2i(f float64) fixed.Int26_6 {
125 | return fixed.Int26_6(f * (1 << 6))
126 | }
127 |
128 | func pow2(x uint32) uint32 {
129 | x--
130 | x |= x >> 1
131 | x |= x >> 2
132 | x |= x >> 4
133 | x |= x >> 8
134 | x |= x >> 16
135 | return x + 1
136 | }
137 |
138 | func ceil(x float32) int {
139 | return int(math.Ceil(float64(x)))
140 | }
141 |
--------------------------------------------------------------------------------
/gfx/text_line.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | type textLine struct {
8 | chars []rune
9 | glyphs []glyphData
10 | colors [][]float32
11 | rasts []*rasterizer
12 | kern []float32
13 | size int
14 | lastBreak int
15 | spaceCount int
16 | width float32
17 | y float32
18 | }
19 |
20 | func generateLines(font *Font, text []string, color [][]float32, wrapLimit float32) ([]*textLine, float32, float32) {
21 | var lines []*textLine
22 | var prevChar rune
23 | var width, gy float32
24 | currentLine := &textLine{}
25 |
26 | breakOff := func(y float32, immediate bool) {
27 | newLine := currentLine.breakOff(immediate)
28 | newLine.y = y
29 | width = float32(math.Max(float64(width), float64(currentLine.width)))
30 | lines = append(lines, currentLine)
31 | currentLine = newLine
32 | prevChar = 0
33 | }
34 |
35 | for i, st := range text {
36 | for _, char := range st {
37 | if char == '\n' {
38 | gy += font.GetLineHeight()
39 | breakOff(gy, true)
40 | continue
41 | } else if char == '\r' {
42 | breakOff(gy, true)
43 | continue
44 | } else if char == '\t' {
45 | if glyph, rast, ok := font.findGlyph(' '); ok {
46 | currentLine.add(' ', glyph, color[i], rast, font.Kern(prevChar, char))
47 | currentLine.add(' ', glyph, color[i], rast, font.Kern(' ', char))
48 | currentLine.add(' ', glyph, color[i], rast, font.Kern(' ', char))
49 | currentLine.add(' ', glyph, color[i], rast, font.Kern(' ', char))
50 | }
51 | continue
52 | }
53 | if glyph, rast, ok := font.findGlyph(char); ok {
54 | currentLine.add(char, glyph, color[i], rast, font.Kern(prevChar, char))
55 | }
56 | if wrapLimit > 0 && currentLine.width >= wrapLimit {
57 | gy += font.GetLineHeight()
58 | breakOff(gy, false)
59 | } else {
60 | prevChar = char
61 | }
62 | }
63 | }
64 |
65 | lines = append(lines, currentLine)
66 | width = float32(math.Max(float64(width), float64(currentLine.width)))
67 |
68 | return lines, width, gy + font.GetLineHeight()
69 | }
70 |
71 | func (l *textLine) add(char rune, g glyphData, color []float32, rast *rasterizer, kern float32) {
72 | if char == ' ' {
73 | l.lastBreak = l.size
74 | l.spaceCount++
75 | l.width += g.advance
76 | } else {
77 | l.width += g.advance + kern + g.lsb
78 | }
79 | l.chars = append(l.chars, char)
80 | l.glyphs = append(l.glyphs, g)
81 | l.colors = append(l.colors, color)
82 | l.rasts = append(l.rasts, rast)
83 | l.kern = append(l.kern, kern+g.lsb)
84 | l.size++
85 | }
86 |
87 | func (l *textLine) breakOff(immediate bool) *textLine {
88 | breakPoint := l.lastBreak
89 | if l.lastBreak == -1 || immediate {
90 | breakPoint = l.size - 1
91 | }
92 |
93 | newLine := &textLine{}
94 |
95 | for i := l.size - 1; i > breakPoint; i-- {
96 | ch, g, cl, r, k := l.trimLastChar()
97 | newLine.chars = append([]rune{ch}, newLine.chars...)
98 | newLine.glyphs = append([]glyphData{g}, newLine.glyphs...)
99 | newLine.colors = append([][]float32{cl}, newLine.colors...)
100 | newLine.rasts = append([]*rasterizer{r}, newLine.rasts...)
101 | newLine.kern = append([]float32{k}, newLine.kern...)
102 | newLine.size++
103 | newLine.width += float32(g.advance) + k
104 | }
105 |
106 | for i := l.size - 1; i >= 0; i-- {
107 | if l.chars[i] == ' ' {
108 | l.trimLastChar()
109 | continue
110 | }
111 | break
112 | }
113 |
114 | return newLine
115 | }
116 |
117 | func (l *textLine) trimLastChar() (rune, glyphData, []float32, *rasterizer, float32) {
118 | i := l.size - 1
119 | ch, g, cl, r, k := l.chars[i], l.glyphs[i], l.colors[i], l.rasts[i], l.kern[i]
120 | l.chars = l.chars[:i]
121 | l.glyphs = l.glyphs[:i]
122 | l.colors = l.colors[:i]
123 | l.rasts = l.rasts[:i]
124 | l.kern = l.kern[:i]
125 | l.size--
126 | l.width -= float32(g.advance) + k
127 | if ch == ' ' {
128 | l.spaceCount--
129 | }
130 | return ch, g, cl, r, k
131 | }
132 |
--------------------------------------------------------------------------------
/gfx/font/bitmap_face.go:
--------------------------------------------------------------------------------
1 | package font
2 |
3 | import (
4 | "image"
5 |
6 | "golang.org/x/image/font"
7 | "golang.org/x/image/math/fixed"
8 |
9 | "github.com/tanema/amore/file"
10 | )
11 |
12 | // BitmapFace holds data for a bitmap font face to satisfy the font.Face interface
13 | type BitmapFace struct {
14 | img image.Image
15 | glyphs map[rune]glyphData
16 | advance fixed.Int26_6
17 | metrics font.Metrics
18 | }
19 |
20 | type glyphData struct {
21 | rect image.Rectangle
22 | pt image.Point
23 | }
24 |
25 | // NewBitmapFace will load up an image font face for creating a font in graphics
26 | func NewBitmapFace(filepath, glyphHints string) (font.Face, error) {
27 | imgFile, err := file.Open(filepath)
28 | if err != nil {
29 | return nil, err
30 | }
31 | defer imgFile.Close()
32 |
33 | img, _, err := image.Decode(imgFile)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | glyphRuneHints := []rune(glyphHints)
39 | advance := img.Bounds().Dx() / len(glyphRuneHints)
40 | newFace := BitmapFace{
41 | img: img,
42 | glyphs: make(map[rune]glyphData),
43 | advance: fixed.I(advance),
44 | metrics: font.Metrics{
45 | Height: fixed.I(img.Bounds().Dy()),
46 | Ascent: fixed.I(img.Bounds().Dy()),
47 | Descent: 0,
48 | },
49 | }
50 |
51 | var gx int
52 | for i, r := range glyphRuneHints {
53 | newFace.glyphs[r] = glyphData{
54 | rect: image.Rect(gx, 0, gx+advance, newFace.metrics.Height.Ceil()),
55 | pt: image.Pt(i*advance, 0),
56 | }
57 | gx += advance
58 | }
59 |
60 | return newFace, err
61 | }
62 |
63 | // Glyph returns the draw.DrawMask parameters (dr, mask, maskp) to draw r's
64 | // glyph at the sub-pixel destination location dot, and that glyph's
65 | // advance width.
66 | //
67 | // It returns !ok if the face does not contain a glyph for r.
68 | //
69 | // The contents of the mask image returned by one Glyph call may change
70 | // after the next Glyph call. Callers that want to cache the mask must make
71 | // a copy.
72 | func (face BitmapFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
73 | var glyph glyphData
74 | glyph, ok = face.glyphs[r]
75 | if !ok {
76 | return
77 | }
78 | dr.Min = image.Point{
79 | X: dot.X.Floor(),
80 | Y: dot.Y.Floor() - face.metrics.Height.Floor(),
81 | }
82 | dr.Max = image.Point{
83 | X: dr.Min.X + face.advance.Floor(),
84 | Y: dr.Min.Y + face.metrics.Height.Floor(),
85 | }
86 | return dr, face.img, glyph.pt, face.advance, ok
87 | }
88 |
89 | // GlyphBounds returns the bounding box of r's glyph, drawn at a dot equal
90 | // to the origin, and that glyph's advance width.
91 | //
92 | // It returns !ok if the face does not contain a glyph for r.
93 | //
94 | // The glyph's ascent and descent equal -bounds.Min.Y and +bounds.Max.Y. A
95 | // visual depiction of what these metrics are is at
96 | // https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
97 | func (face BitmapFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
98 | _, ok = face.glyphs[r]
99 | return fixed.R(0, 0, face.advance.Ceil(), face.metrics.Height.Ceil()), face.advance, ok
100 | }
101 |
102 | // GlyphAdvance returns the advance width of r's glyph.
103 | //
104 | // It returns !ok if the face does not contain a glyph for r.
105 | func (face BitmapFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
106 | return face.advance, true
107 | }
108 |
109 | // Kern returns the horizontal adjustment for the kerning pair (r0, r1). A
110 | // positive kern means to move the glyphs further apart.
111 | func (face BitmapFace) Kern(r0, r1 rune) fixed.Int26_6 {
112 | return 0.0
113 | }
114 |
115 | // Metrics returns the metrics for this Face.
116 | func (face BitmapFace) Metrics() font.Metrics {
117 | return face.metrics
118 | }
119 |
120 | // Close to satisfy face interface
121 | func (face BitmapFace) Close() error {
122 | return nil
123 | }
124 |
--------------------------------------------------------------------------------
/audio/openal/decoding/decoder.go:
--------------------------------------------------------------------------------
1 | // Package decoding is used for converting familiar file types to data usable by
2 | // OpenAL.
3 | package decoding
4 |
5 | import (
6 | "errors"
7 | "io"
8 | "os"
9 | "time"
10 |
11 | "github.com/tanema/amore/audio/openal/al"
12 | "github.com/tanema/amore/file"
13 | )
14 |
15 | type (
16 | // Decoder is a base implementation of a few methods to keep tryings DRY
17 | Decoder struct {
18 | src io.ReadCloser
19 | codec io.ReadSeeker
20 | bitDepth int16
21 | eof bool
22 | dataSize int32
23 | currentPos int64
24 | byteRate int32
25 |
26 | Channels int16
27 | SampleRate int32
28 | Buffer []byte
29 | Format uint32
30 | }
31 | )
32 |
33 | var extHandlers = map[string]func(io.ReadCloser) (*Decoder, error){
34 | ".wav": newWaveDecoder,
35 | ".ogg": newVorbisDecoder,
36 | ".flac": newFlacDecoder,
37 | ".mp3": newMp3Decoder,
38 | }
39 |
40 | // arbitrary buffer size, could be tuned in the future
41 | const bufferSize = 128 * 1024
42 |
43 | // Decode will get the file at the path provided. It will then send it to the decoder
44 | // that will handle its file type by the extention on the path. Supported formats
45 | // are wav, ogg, and flac. If there is an error retrieving the file or decoding it,
46 | // will return that error.
47 | func Decode(filepath string) (*Decoder, error) {
48 | src, err := file.Open(filepath)
49 | if err != nil {
50 | return nil, err
51 | }
52 |
53 | handler, hasHandler := extHandlers[file.Ext(filepath)]
54 | if !hasHandler {
55 | src.Close()
56 | return nil, errors.New("unsupported audio file extention")
57 | }
58 |
59 | decoder, err := handler(src)
60 | if err != nil {
61 | src.Close()
62 | return nil, err
63 | }
64 |
65 | return decoder, nil
66 | }
67 |
68 | func newDecoder(src io.ReadCloser, codec io.ReadSeeker, channels int16, sampleRate int32, bitDepth int16, dataSize int32) *Decoder {
69 | decoder := &Decoder{
70 | src: src,
71 | codec: codec,
72 | Channels: channels,
73 | SampleRate: sampleRate,
74 | bitDepth: bitDepth,
75 | dataSize: dataSize,
76 | currentPos: 0,
77 | byteRate: (sampleRate * int32(channels) * int32(bitDepth)) / 8,
78 | }
79 |
80 | switch {
81 | case channels == 1 && bitDepth == 8:
82 | decoder.Format = al.FormatMono8
83 | case channels == 1 && bitDepth == 16:
84 | decoder.Format = al.FormatMono16
85 | case channels == 2 && bitDepth == 8:
86 | decoder.Format = al.FormatStereo8
87 | case channels == 2 && bitDepth == 16:
88 | decoder.Format = al.FormatStereo16
89 | }
90 |
91 | return decoder
92 | }
93 |
94 | // IsFinished returns is the decoder is finished decoding
95 | func (decoder *Decoder) IsFinished() bool {
96 | return decoder.eof
97 | }
98 |
99 | // Duration will return the total time duration of this clip
100 | func (decoder *Decoder) Duration() time.Duration {
101 | return decoder.ByteOffsetToDur(decoder.dataSize)
102 | }
103 |
104 | // GetData returns the complete set of data
105 | func (decoder *Decoder) GetData() []byte {
106 | data := make([]byte, decoder.dataSize)
107 | decoder.Seek(0)
108 | decoder.codec.Read(data)
109 | return data
110 | }
111 |
112 | // ByteOffsetToDur will translate byte count to time duration
113 | func (decoder *Decoder) ByteOffsetToDur(offset int32) time.Duration {
114 | return time.Duration(offset/decoder.byteRate) * time.Second
115 | }
116 |
117 | // DurToByteOffset will translate time duration to a byte count
118 | func (decoder *Decoder) DurToByteOffset(dur time.Duration) int32 {
119 | return int32(dur/time.Second) * decoder.byteRate
120 | }
121 |
122 | // Decode will read the next chunk into the buffer and return the amount of bytes read
123 | func (decoder *Decoder) Decode() int {
124 | buffer := make([]byte, bufferSize)
125 | n, err := decoder.codec.Read(buffer)
126 | decoder.Buffer = buffer[:n]
127 | decoder.eof = (err == io.EOF)
128 | return n
129 | }
130 |
131 | // Seek will seek in the source data by count of bytes
132 | func (decoder *Decoder) Seek(s int64) bool {
133 | decoder.currentPos = s
134 | _, err := decoder.codec.Seek(decoder.currentPos, os.SEEK_SET)
135 | decoder.eof = (err == io.EOF)
136 | return err == nil || decoder.eof
137 | }
138 |
--------------------------------------------------------------------------------
/gfx/wrap/wrap.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import "github.com/tanema/amore/runtime"
4 |
5 | var graphicsFunctions = runtime.LuaFuncs{
6 | "circle": gfxCirle,
7 | "arc": gfxArc,
8 | "ellipse": gfxEllipse,
9 | "points": gfxPoints,
10 | "line": gfxLine,
11 | "rectangle": gfxRectangle,
12 | "polygon": gfxPolygon,
13 | "getviewport": gfxGetViewport,
14 | "setviewport": gfxSetViewport,
15 | "getwidth": gfxGetWidth,
16 | "getheight": gfxGetHeight,
17 | "getdimensions": gfxGetDimensions,
18 | "origin": gfxOrigin,
19 | "translate": gfxTranslate,
20 | "rotate": gfxRotate,
21 | "scale": gfxScale,
22 | "shear": gfxShear,
23 | "push": gfxPush,
24 | "pop": gfxPop,
25 | "clear": gfxClear,
26 | "setscissor": gfxSetScissor,
27 | "getscissor": gfxGetScissor,
28 | "setlinewidth": gfxSetLineWidth,
29 | "setlinejoin": gfxSetLineJoin,
30 | "getlinewidth": gfxGetLineWidth,
31 | "getlinejoin": gfxGetLineJoin,
32 | "setpointsize": gfxSetPointSize,
33 | "getpointsize": gfxGetPointSize,
34 | "setcolor": gfxSetColor,
35 | "setbackgroundcolor": gfxSetBackgroundColor,
36 | "getcolor": gfxGetColor,
37 | "getbackgroundcolor": gfxGetBackgroundColor,
38 | "getcolormask": gfxGetColorMask,
39 | "setcolormask": gfxSetColorMask,
40 | "print": gfxPrint,
41 | "printf": gfxPrintf,
42 | "getfont": gfxGetFont,
43 | "setfont": gfxSetFont,
44 | "setblendmode": gfxSetBlendMode,
45 | "getstenciltest": gfxGetStencilTest,
46 | "setstenciltest": gfxSetStencilTest,
47 | "stencil": gfxStencil,
48 | "setshader": gfxSetShader,
49 |
50 | // metatable entries
51 | "newimage": gfxNewImage,
52 | "newtext": gfxNewText,
53 | "newfont": gfxNewFont,
54 | "newquad": gfxNewQuad,
55 | "newcanvas": gfxNewCanvas,
56 | "newspritebatch": gfxNewSpriteBatch,
57 | "newshader": gfxNewShader,
58 | }
59 |
60 | var graphicsMetaTables = runtime.LuaMetaTable{
61 | "Image": {
62 | "draw": gfxTextureDraw,
63 | "drawq": gfxTextureDrawq,
64 | "getwidth": gfxTextureGetWidth,
65 | "getheigth": gfxTextureGetHeight,
66 | "getDimensions": gfxTextureGetDimensions,
67 | "setwrap": gfxTextureSetWrap,
68 | "setfilter": gfxTextureSetFilter,
69 | },
70 | "Text": {
71 | "set": gfxTextSet,
72 | "draw": gfxTextDraw,
73 | "getwidth": gfxTextGetWidth,
74 | "getheight": gfxTextGetHeight,
75 | "getdimensions": gfxTextGetDimensions,
76 | "getfont": gfxTextGetFont,
77 | "setfont": gfxTextSetFont,
78 | },
79 | "Font": {
80 | "getwidth": gfxFontGetWidth,
81 | "getheight": gfxFontGetHeight,
82 | "setfallback": gfxFontSetFallback,
83 | "getwrap": gfxFontGetWrap,
84 | },
85 | "Quad": {
86 | "getwidth": gfxQuadGetWidth,
87 | "geteheight": gfxQuadGetHeight,
88 | "getviewport": gfxQuadGetViewport,
89 | "setviewport": gfxQuadSetViewport,
90 | },
91 | "Canvas": {
92 | "newimage": gfxCanvasNewImage,
93 | "draw": gfxTextureDraw,
94 | "drawq": gfxTextureDrawq,
95 | "getwidth": gfxTextureGetWidth,
96 | "getheigth": gfxTextureGetHeight,
97 | "getDimensions": gfxTextureGetDimensions,
98 | "setwrap": gfxTextureSetWrap,
99 | "setfilter": gfxTextureSetFilter,
100 | },
101 | "SpriteBatch": {
102 | "add": gfxSpriteBatchAdd,
103 | "addq": gfxSpriteBatchAddq,
104 | "set": gfxSpriteBatchSet,
105 | "setq": gfxSpriteBatchSetq,
106 | "clear": gfxSpriteBatchClear,
107 | "settexture": gfxSpriteBatchSetTexture,
108 | "gettexture": gfxSpriteBatchGetTexture,
109 | "setcolor": gfxSpriteBatchSetColor,
110 | "getcolor": gfxSpriteBatchGetColor,
111 | "getcount": gfxSpriteBatchGetCount,
112 | "setbuffersize": gfxSpriteBatchSetBufferSize,
113 | "getbuffersize": gfxSpriteBatchGetBufferSize,
114 | "setdrawrange": gfxSpriteBatchSetDrawRange,
115 | "getdrawrange": gfxSpriteBatchGetDrawRange,
116 | "draw": gfxSpriteBatchDraw,
117 | },
118 | "Shader": {
119 | "send": gfxShaderSend,
120 | },
121 | }
122 |
123 | func init() {
124 | runtime.RegisterModule("gfx", graphicsFunctions, graphicsMetaTables)
125 | }
126 |
--------------------------------------------------------------------------------
/runtime/runtime.go:
--------------------------------------------------------------------------------
1 | package runtime
2 |
3 | import (
4 | "runtime"
5 |
6 | "github.com/goxjs/gl"
7 | "github.com/goxjs/glfw"
8 | "github.com/yuin/gopher-lua"
9 |
10 | "github.com/tanema/amore/file"
11 | "github.com/tanema/amore/gfx"
12 | )
13 |
14 | // LuaFuncs is the declarations of functions within a module
15 | type LuaFuncs map[string]lua.LGFunction
16 |
17 | // LuaMetaTable is the declarations of metatables within a module
18 | type LuaMetaTable map[string]LuaFuncs
19 |
20 | // LuaLoadHook is a function that will be called before the gameloop starts with
21 | // the state. This is good for fetching global callbacks to call later.
22 | type LuaLoadHook func(*lua.LState, *glfw.Window)
23 |
24 | type luaModule struct {
25 | name string
26 | functions LuaFuncs
27 | metatables map[string]LuaFuncs
28 | }
29 |
30 | var (
31 | registeredModules = []luaModule{}
32 | registeredHooks = []LuaLoadHook{}
33 | )
34 |
35 | func init() {
36 | runtime.GOMAXPROCS(runtime.NumCPU())
37 | runtime.LockOSThread() //important OpenGl Demand it and stamp thier feet if you dont
38 | }
39 |
40 | // RegisterModule registers a lua module within the global namespace for easy access
41 | func RegisterModule(name string, funcs LuaFuncs, metatables LuaMetaTable) {
42 | registeredModules = append(registeredModules, luaModule{name: name, functions: funcs, metatables: metatables})
43 | }
44 |
45 | // RegisterHook will add a lua state load hook
46 | func RegisterHook(fn LuaLoadHook) {
47 | registeredHooks = append(registeredHooks, fn)
48 | }
49 |
50 | // Run starts the lua program
51 | func Run(entrypoint string) error {
52 | if err := glfw.Init(gl.ContextWatcher); err != nil {
53 | return err
54 | }
55 | defer glfw.Terminate()
56 | win, err := createWindow(conf)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | ls := lua.NewState()
62 | defer ls.Close()
63 |
64 | gfx.InitContext(win.Window)
65 | importGlobals(ls, win.Window)
66 | importModules(ls)
67 | runHooks(ls, win.Window)
68 |
69 | entryfile, err := file.Open(entrypoint)
70 | if err != nil {
71 | return err
72 | }
73 |
74 | if fn, err := ls.Load(entryfile, entrypoint); err != nil {
75 | return err
76 | } else {
77 | ls.Push(fn)
78 | if err := ls.PCall(0, lua.MultRet, nil); err != nil {
79 | return err
80 | }
81 | }
82 |
83 | if load := ls.GetGlobal("onload"); load != lua.LNil {
84 | if err := ls.CallByParam(lua.P{Fn: load, Protect: true}); err != nil {
85 | return err
86 | }
87 | }
88 |
89 | return gameloop(ls, win)
90 | }
91 |
92 | func importGlobals(ls *lua.LState, win *glfw.Window) {
93 | ls.SetGlobal("getfps", ls.NewFunction(func(L *lua.LState) int {
94 | L.Push(lua.LNumber(fps))
95 | return 1
96 | }))
97 | ls.SetGlobal("quit", ls.NewFunction(func(L *lua.LState) int {
98 | win.SetShouldClose(true)
99 | return 0
100 | }))
101 | }
102 |
103 | func importModules(ls *lua.LState) {
104 | for _, mod := range registeredModules {
105 | newMod := ls.NewTable()
106 | ls.SetFuncs(newMod, mod.functions)
107 | for tablename, metatable := range mod.metatables {
108 | mt := ls.NewTypeMetatable(tablename)
109 | newMod.RawSetString(tablename, mt)
110 | ls.SetField(mt, "__index", ls.SetFuncs(ls.NewTable(), metatable))
111 | }
112 | ls.SetGlobal(mod.name, newMod)
113 | }
114 | }
115 |
116 | func runHooks(ls *lua.LState, win *glfw.Window) {
117 | for _, fn := range registeredHooks {
118 | fn(ls, win)
119 | }
120 | }
121 |
122 | // Start creates a window and context for the game to run on and runs the game
123 | // loop. As such this function should be put as the last call in your main function.
124 | // update and draw will be called synchronously because calls to OpenGL that are
125 | // not on the main thread will crash your program.
126 | func gameloop(luaState *lua.LState, win window) error {
127 | for !win.ShouldClose() {
128 | if update := luaState.GetGlobal("update"); update != lua.LNil {
129 | dt := lua.LNumber(step())
130 | if err := luaState.CallByParam(lua.P{Fn: update, Protect: true}, dt); err != nil {
131 | return err
132 | }
133 | }
134 | if win.active {
135 | color := gfx.GetBackgroundColor()
136 | gfx.Clear(color[0], color[1], color[2], color[3])
137 | gfx.Origin()
138 | if draw := luaState.GetGlobal("draw"); draw != lua.LNil {
139 | if err := luaState.CallByParam(lua.P{Fn: draw, Protect: true}); err != nil {
140 | return err
141 | }
142 | }
143 | gfx.Present()
144 | win.SwapBuffers()
145 | }
146 | glfw.PollEvents()
147 | }
148 | return nil
149 | }
150 |
--------------------------------------------------------------------------------
/input/input_const.go:
--------------------------------------------------------------------------------
1 | package input
2 |
3 | import (
4 | "github.com/goxjs/glfw"
5 | )
6 |
7 | var actions = map[glfw.Action]string{
8 | glfw.Release: "release",
9 | glfw.Press: "press",
10 | glfw.Repeat: "repeat",
11 | }
12 |
13 | var mouseButtons = map[glfw.MouseButton]string{
14 | glfw.MouseButtonLeft: "left",
15 | glfw.MouseButtonRight: "right",
16 | glfw.MouseButtonMiddle: "middle",
17 | }
18 |
19 | var keyboardMap = map[glfw.Key]string{
20 | glfw.KeySpace: "space",
21 | glfw.KeyApostrophe: "apostrophe",
22 | glfw.KeyComma: "comma",
23 | glfw.KeyMinus: "minus",
24 | glfw.KeyPeriod: "period",
25 | glfw.KeySlash: "slash",
26 | glfw.Key0: "0",
27 | glfw.Key1: "1",
28 | glfw.Key2: "2",
29 | glfw.Key3: "3",
30 | glfw.Key4: "4",
31 | glfw.Key5: "5",
32 | glfw.Key6: "6",
33 | glfw.Key7: "7",
34 | glfw.Key8: "8",
35 | glfw.Key9: "9",
36 | glfw.KeySemicolon: "semicolon",
37 | glfw.KeyEqual: "equal",
38 | glfw.KeyA: "a",
39 | glfw.KeyB: "b",
40 | glfw.KeyC: "c",
41 | glfw.KeyD: "d",
42 | glfw.KeyE: "e",
43 | glfw.KeyF: "f",
44 | glfw.KeyG: "g",
45 | glfw.KeyH: "h",
46 | glfw.KeyI: "i",
47 | glfw.KeyJ: "j",
48 | glfw.KeyK: "k",
49 | glfw.KeyL: "l",
50 | glfw.KeyM: "m",
51 | glfw.KeyN: "n",
52 | glfw.KeyO: "o",
53 | glfw.KeyP: "p",
54 | glfw.KeyQ: "q",
55 | glfw.KeyR: "r",
56 | glfw.KeyS: "s",
57 | glfw.KeyT: "t",
58 | glfw.KeyU: "u",
59 | glfw.KeyV: "v",
60 | glfw.KeyW: "w",
61 | glfw.KeyX: "x",
62 | glfw.KeyY: "y",
63 | glfw.KeyZ: "z",
64 | glfw.KeyLeftBracket: "leftbracket",
65 | glfw.KeyBackslash: "backslash",
66 | glfw.KeyRightBracket: "rightbracket",
67 | glfw.KeyGraveAccent: "graveaccent",
68 | glfw.KeyWorld1: "world1",
69 | glfw.KeyWorld2: "world2",
70 | glfw.KeyEscape: "escape",
71 | glfw.KeyEnter: "enter",
72 | glfw.KeyTab: "tab",
73 | glfw.KeyBackspace: "backspace",
74 | glfw.KeyInsert: "insert",
75 | glfw.KeyDelete: "delete",
76 | glfw.KeyRight: "right",
77 | glfw.KeyLeft: "left",
78 | glfw.KeyDown: "down",
79 | glfw.KeyUp: "up",
80 | glfw.KeyPageUp: "pageup",
81 | glfw.KeyPageDown: "pagedown",
82 | glfw.KeyHome: "home",
83 | glfw.KeyEnd: "end",
84 | glfw.KeyCapsLock: "capslock",
85 | glfw.KeyScrollLock: "scrolllock",
86 | glfw.KeyNumLock: "numlock",
87 | glfw.KeyPrintScreen: "printscreen",
88 | glfw.KeyPause: "pause",
89 | glfw.KeyF1: "f1",
90 | glfw.KeyF2: "f2",
91 | glfw.KeyF3: "f3",
92 | glfw.KeyF4: "f4",
93 | glfw.KeyF5: "f5",
94 | glfw.KeyF6: "f6",
95 | glfw.KeyF7: "f7",
96 | glfw.KeyF8: "f8",
97 | glfw.KeyF9: "f9",
98 | glfw.KeyF10: "f10",
99 | glfw.KeyF11: "f11",
100 | glfw.KeyF12: "f12",
101 | glfw.KeyF13: "f13",
102 | glfw.KeyF14: "f14",
103 | glfw.KeyF15: "f15",
104 | glfw.KeyF16: "f16",
105 | glfw.KeyF17: "f17",
106 | glfw.KeyF18: "f18",
107 | glfw.KeyF19: "f19",
108 | glfw.KeyF20: "f20",
109 | glfw.KeyF21: "f21",
110 | glfw.KeyF22: "f22",
111 | glfw.KeyF23: "f23",
112 | glfw.KeyF24: "f24",
113 | glfw.KeyF25: "f25",
114 | glfw.KeyKP0: "kp0",
115 | glfw.KeyKP1: "kp1",
116 | glfw.KeyKP2: "kp2",
117 | glfw.KeyKP3: "kp3",
118 | glfw.KeyKP4: "kp4",
119 | glfw.KeyKP5: "kp5",
120 | glfw.KeyKP6: "kp6",
121 | glfw.KeyKP7: "kp7",
122 | glfw.KeyKP8: "kp8",
123 | glfw.KeyKP9: "kp9",
124 | glfw.KeyKPDecimal: "kpdecimal",
125 | glfw.KeyKPDivide: "kpdivide",
126 | glfw.KeyKPMultiply: "kpmultiply",
127 | glfw.KeyKPSubtract: "kpsubtract",
128 | glfw.KeyKPAdd: "kpadd",
129 | glfw.KeyKPEnter: "kpenter",
130 | glfw.KeyKPEqual: "kpequal",
131 | glfw.KeyLeftShift: "leftshift",
132 | glfw.KeyLeftControl: "leftcontrol",
133 | glfw.KeyLeftAlt: "leftalt",
134 | glfw.KeyLeftSuper: "leftsuper",
135 | glfw.KeyRightShift: "rightshift",
136 | glfw.KeyRightControl: "rightcontrol",
137 | glfw.KeyRightAlt: "rightalt",
138 | glfw.KeyRightSuper: "rightsuper",
139 | glfw.KeyMenu: "menu",
140 | }
141 |
--------------------------------------------------------------------------------
/gfx/text.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | type (
8 | // Text is a container of text, color and text formatting.
9 | Text struct {
10 | font *Font
11 | strings []string
12 | colors [][]float32
13 | wrapLimit float32
14 | align string
15 | batches map[*rasterizer]*SpriteBatch
16 | width float32
17 | height float32
18 | }
19 | )
20 |
21 | // Print will print out a colored string. It accepts the normal drawable arguments
22 | func Print(strs []string, colors [][]float32, argv ...float32) {
23 | NewText(GetFont(), strs, colors, -1, "left").Draw(argv...)
24 | }
25 |
26 | // Printf will print out a string with a wrap limit and alignment. It accepts the
27 | // normal drawable arguments
28 | func Printf(strs []string, colors [][]float32, wrapLimit float32, align string, argv ...float32) {
29 | NewText(GetFont(), strs, colors, wrapLimit, align).Draw(argv...)
30 | }
31 |
32 | // NewText will create a colored text object with the provided font and
33 | // text. A wrap and alignment can be provided as well. If wrapLimit is < 0 it will
34 | // not wrap
35 | func NewText(font *Font, strs []string, colors [][]float32, wrapLimit float32, align string) *Text {
36 | newText := &Text{
37 | font: font,
38 | strings: strs,
39 | colors: colors,
40 | wrapLimit: wrapLimit,
41 | align: align,
42 | batches: make(map[*rasterizer]*SpriteBatch),
43 | }
44 | registerVolatile(newText)
45 | return newText
46 | }
47 |
48 | func (text *Text) loadVolatile() bool {
49 | length := len(strings.Join(text.strings, ""))
50 | for _, rast := range text.font.rasterizers {
51 | text.batches[rast] = NewSpriteBatch(rast.texture, length, UsageDynamic)
52 | }
53 | text.generate()
54 | return true
55 | }
56 |
57 | func (text *Text) unloadVolatile() {}
58 |
59 | func (text *Text) generate() {
60 | for _, batch := range text.batches {
61 | batch.Clear()
62 | }
63 |
64 | var lines []*textLine
65 | lines, text.width, text.height = generateLines(text.font, text.strings, text.colors, text.wrapLimit)
66 |
67 | for _, l := range lines {
68 | var gx, spacing float32
69 |
70 | if spaceGlyph, _, ok := text.font.findGlyph(' '); ok {
71 | spacing = spaceGlyph.advance
72 | } else {
73 | spacing = text.font.rasterizers[0].advance
74 | }
75 |
76 | switch text.align {
77 | case "left":
78 | case "right":
79 | gx = text.wrapLimit - l.width
80 | case "center":
81 | gx = (text.wrapLimit - l.width) / 2.0
82 | case "justify":
83 | amountOfSpace := float32(l.spaceCount-1) * spacing
84 | widthWithoutSpace := l.width - amountOfSpace
85 | spacing = (text.wrapLimit - widthWithoutSpace) / float32(l.spaceCount)
86 | }
87 |
88 | for i := 0; i < l.size; i++ {
89 | ch := l.chars[i]
90 | if ch == ' ' {
91 | gx += spacing
92 | } else {
93 | glyph := l.glyphs[i]
94 | rast := l.rasts[i]
95 | text.batches[rast].SetColor(l.colors[i]...)
96 | gx += l.kern[i]
97 | text.batches[rast].Addq(glyph.quad, gx, l.y+glyph.descent)
98 | gx += glyph.advance
99 | }
100 | }
101 | }
102 |
103 | for _, batch := range text.batches {
104 | if batch.GetCount() > 0 {
105 | batch.SetBufferSize(batch.GetCount())
106 | }
107 | }
108 | }
109 |
110 | // GetWidth will return the text obejcts set width which will be <= wrapLimit
111 | func (text *Text) GetWidth() float32 {
112 | return text.width
113 | }
114 |
115 | // GetHeight will return the height of the text object after text wrap.
116 | func (text *Text) GetHeight() float32 {
117 | return text.height
118 | }
119 |
120 | // GetDimensions will return the width and height of the text object
121 | func (text *Text) GetDimensions() (float32, float32) {
122 | return text.width, text.height
123 | }
124 |
125 | // GetFont will return the font that this text object has been created with
126 | func (text *Text) GetFont() *Font {
127 | return text.font
128 | }
129 |
130 | // SetFont will set the font in which this text object will use to render the
131 | // string
132 | func (text *Text) SetFont(f *Font) {
133 | text.font = f
134 | text.loadVolatile()
135 | }
136 |
137 | // Set will set the string and colors for this text object to be rendered.
138 | func (text *Text) Set(strs []string, colors [][]float32) {
139 | text.strings = strs
140 | text.colors = colors
141 | text.generate()
142 | }
143 |
144 | // Draw satisfies the Drawable interface. Inputs are as follows
145 | // x, y, r, sx, sy, ox, oy, kx, ky
146 | // x, y are position
147 | // r is rotation
148 | // sx, sy is the scale, if sy is not given sy will equal sx
149 | // ox, oy are offset
150 | // kx, ky are the shear. If ky is not given ky will equal kx
151 | func (text *Text) Draw(args ...float32) {
152 | for _, batch := range text.batches {
153 | if batch.GetCount() > 0 {
154 | batch.Draw(args...)
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/audio/openal/al/al_pc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin linux,!android
6 |
7 | package al
8 |
9 | /*
10 | #cgo darwin CFLAGS: -DGOOS_darwin
11 | #cgo linux CFLAGS: -DGOOS_linux
12 | #cgo darwin LDFLAGS: -framework OpenAL
13 | #cgo linux LDFLAGS: -lopenal
14 |
15 | #ifdef GOOS_darwin
16 | #include
17 | #include
18 | #endif
19 |
20 | #ifdef GOOS_linux
21 | #include
22 | #include
23 | #endif
24 |
25 | #ifdef GOOS_windows
26 | #include
27 | #include
28 | #endif
29 | */
30 | import "C"
31 | import "unsafe"
32 |
33 | func alEnable(capability int32) {
34 | C.alEnable(C.ALenum(capability))
35 | }
36 |
37 | func alDisable(capability int32) {
38 | C.alDisable(C.ALenum(capability))
39 | }
40 |
41 | func alIsEnabled(capability int32) bool {
42 | return C.alIsEnabled(C.ALenum(capability)) == C.AL_TRUE
43 | }
44 |
45 | func alGetInteger(k int) int32 {
46 | return int32(C.alGetInteger(C.ALenum(k)))
47 | }
48 |
49 | func alGetFloat(k int) float32 {
50 | return float32(C.alGetFloat(C.ALenum(k)))
51 | }
52 |
53 | func alGetString(v int) string {
54 | value := C.alGetString(C.ALenum(v))
55 | return C.GoString((*C.char)(value))
56 | }
57 |
58 | func alDistanceModel(v int32) {
59 | C.alDistanceModel(C.ALenum(v))
60 | }
61 |
62 | func alDopplerFactor(v float32) {
63 | C.alDopplerFactor(C.ALfloat(v))
64 | }
65 |
66 | func alDopplerVelocity(v float32) {
67 | C.alDopplerVelocity(C.ALfloat(v))
68 | }
69 |
70 | func alSpeedOfSound(v float32) {
71 | C.alSpeedOfSound(C.ALfloat(v))
72 | }
73 |
74 | func alGetError() int32 {
75 | return int32(C.alGetError())
76 | }
77 |
78 | func alGenSources(n int) []Source {
79 | s := make([]Source, n)
80 | C.alGenSources(C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
81 | return s
82 | }
83 |
84 | func alSourcePlayv(s []Source) {
85 | C.alSourcePlayv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
86 | }
87 |
88 | func alSourcePausev(s []Source) {
89 | C.alSourcePausev(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
90 |
91 | }
92 |
93 | func alSourceStopv(s []Source) {
94 | C.alSourceStopv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
95 | }
96 |
97 | func alSourceRewindv(s []Source) {
98 | C.alSourceRewindv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
99 | }
100 |
101 | func alDeleteSources(s []Source) {
102 | C.alDeleteSources(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
103 | }
104 |
105 | func alGetSourcei(s Source, k int) int32 {
106 | var v C.ALint
107 | C.alGetSourcei(C.ALuint(s), C.ALenum(k), &v)
108 | return int32(v)
109 | }
110 |
111 | func alGetSourcef(s Source, k int) float32 {
112 | var v C.ALfloat
113 | C.alGetSourcef(C.ALuint(s), C.ALenum(k), &v)
114 | return float32(v)
115 | }
116 |
117 | func alGetSourcefv(s Source, k int, v []float32) {
118 | C.alGetSourcefv(C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
119 | }
120 |
121 | func alSourcei(s Source, k int, v int32) {
122 | C.alSourcei(C.ALuint(s), C.ALenum(k), C.ALint(v))
123 | }
124 |
125 | func alSourcef(s Source, k int, v float32) {
126 | C.alSourcef(C.ALuint(s), C.ALenum(k), C.ALfloat(v))
127 | }
128 |
129 | func alSourcefv(s Source, k int, v []float32) {
130 | C.alSourcefv(C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
131 | }
132 |
133 | func alSourceQueueBuffers(s Source, b []Buffer) {
134 | C.alSourceQueueBuffers(C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
135 | }
136 |
137 | func alSourceUnqueueBuffers(s Source, b []Buffer) {
138 | C.alSourceUnqueueBuffers(C.ALuint(s), C.ALsizei(1), (*C.ALuint)(unsafe.Pointer(&b[0])))
139 | }
140 |
141 | func alGetListenerf(k int) float32 {
142 | var v C.ALfloat
143 | C.alGetListenerf(C.ALenum(k), &v)
144 | return float32(v)
145 | }
146 |
147 | func alGetListenerfv(k int, v []float32) {
148 | C.alGetListenerfv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
149 | }
150 |
151 | func alListenerf(k int, v float32) {
152 | C.alListenerf(C.ALenum(k), C.ALfloat(v))
153 | }
154 |
155 | func alListenerfv(k int, v []float32) {
156 | C.alListenerfv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
157 | }
158 |
159 | func alGenBuffers(n int) []Buffer {
160 | s := make([]Buffer, n)
161 | C.alGenBuffers(C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
162 | return s
163 | }
164 |
165 | func alDeleteBuffers(b []Buffer) {
166 | C.alDeleteBuffers(C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
167 | }
168 |
169 | func alGetBufferi(b Buffer, k int) int32 {
170 | var v C.ALint
171 | C.alGetBufferi(C.ALuint(b), C.ALenum(k), &v)
172 | return int32(v)
173 | }
174 |
175 | func alBufferData(b Buffer, format uint32, data []byte, freq int32) {
176 | C.alBufferData(C.ALuint(b), C.ALenum(format), unsafe.Pointer(&data[0]), C.ALsizei(len(data)), C.ALsizei(freq))
177 | }
178 |
179 | func alIsBuffer(b Buffer) bool {
180 | return C.alIsBuffer(C.ALuint(b)) == C.AL_TRUE
181 | }
182 |
--------------------------------------------------------------------------------
/gfx/font.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "github.com/tanema/amore/gfx/font"
5 | )
6 |
7 | // Font is a rasterized font data
8 | type Font struct {
9 | rasterizers []*rasterizer
10 | lineHeight float32
11 | }
12 |
13 | // NewFont rasterizes a ttf font and returns a pointer to a new Font
14 | func NewFont(filename string, fontSize float32) (*Font, error) {
15 | face, err := font.NewTTFFace(filename, fontSize)
16 | if err != nil {
17 | return nil, err
18 | }
19 | return newFont(face, font.ASCII, font.Latin), nil
20 | }
21 |
22 | // NewImageFont rasterizes an image using the glyphHints. The glyphHints should
23 | // list all characters in the image. The characters should all have equal width
24 | // and height. Using the glyphHints, the image is split up into equal rectangles
25 | // for rasterization. The function will return a pointer to a new Font
26 | func NewImageFont(filename, glyphHints string) (*Font, error) {
27 | face, err := font.NewBitmapFace(filename, glyphHints)
28 | if err != nil {
29 | return nil, err
30 | }
31 | return newFont(face, []rune(glyphHints)), nil
32 | }
33 |
34 | func newFont(face font.Face, runeSets ...[]rune) *Font {
35 | if runeSets == nil || len(runeSets) == 0 {
36 | runeSets = append(runeSets, font.ASCII, font.Latin)
37 | }
38 | return &Font{rasterizers: []*rasterizer{newRasterizer(face, runeSets...)}}
39 | }
40 |
41 | // SetLineHeight sets the height between lines
42 | func (font *Font) SetLineHeight(height float32) {
43 | font.lineHeight = height
44 | }
45 |
46 | // GetLineHeight will return the current line height of the font
47 | func (font *Font) GetLineHeight() float32 {
48 | if font.lineHeight <= 0 {
49 | return float32(font.rasterizers[0].lineHeight)
50 | }
51 | return font.lineHeight
52 | }
53 |
54 | // SetFilter sets the filtering on the font.
55 | func (font *Font) SetFilter(min, mag FilterMode) error {
56 | for _, rasterizer := range font.rasterizers {
57 | if err := rasterizer.texture.SetFilter(min, mag); err != nil {
58 | return err
59 | }
60 | }
61 | return nil
62 | }
63 |
64 | // GetFilter will return the filter of the font
65 | func (font *Font) GetFilter() Filter {
66 | return font.rasterizers[0].texture.GetFilter()
67 | }
68 |
69 | // GetAscent gets the height of the font from the baseline
70 | func (font *Font) GetAscent() float32 {
71 | return font.rasterizers[0].ascent
72 | }
73 |
74 | // GetDescent gets the height of the font below the base line
75 | func (font *Font) GetDescent() float32 {
76 | return font.rasterizers[0].descent
77 | }
78 |
79 | // GetBaseline returns the position of the base line.
80 | func (font *Font) GetBaseline() float32 {
81 | return font.rasterizers[0].lineHeight
82 | }
83 |
84 | // HasGlyph checks if this font has a character for the given rune
85 | func (font *Font) HasGlyph(g rune) bool {
86 | _, _, ok := font.findGlyph(g)
87 | return ok
88 | }
89 |
90 | // findGlyph will fetch the glyphData for the given rune
91 | func (font *Font) findGlyph(r rune) (glyphData, *rasterizer, bool) {
92 | for _, rasterizer := range font.rasterizers {
93 | if g, ok := rasterizer.mapping[r]; ok {
94 | return g, rasterizer, ok
95 | }
96 | }
97 | rasterizer := font.rasterizers[0]
98 | return rasterizer.mapping[r], rasterizer, false
99 | }
100 |
101 | // Kern will return the space between two characters
102 | func (font *Font) Kern(first, second rune) float32 {
103 | for _, r := range font.rasterizers {
104 | _, hasFirst := r.mapping[first]
105 | _, hasSecond := r.mapping[second]
106 | if hasFirst && hasSecond {
107 | return float32(r.face.Kern(first, second))
108 | }
109 | }
110 |
111 | return float32(font.rasterizers[0].face.Kern(first, second))
112 | }
113 |
114 | // SetFallbacks will add extra fonts in case some characters are not available
115 | // in this font. If the character is not available it will be rendered with one
116 | // of the fallback characters
117 | func (font *Font) SetFallbacks(fallbacks ...*Font) {
118 | if fallbacks == nil || len(fallbacks) == 0 {
119 | return
120 | }
121 | for _, fallback := range fallbacks {
122 | font.rasterizers = append(font.rasterizers, fallback.rasterizers...)
123 | }
124 | }
125 |
126 | // GetHeight will get the height of the font.
127 | func (font *Font) GetHeight() float32 {
128 | return font.rasterizers[0].lineHeight
129 | }
130 |
131 | // GetWidth will get the width of a given string after rendering.
132 | func (font *Font) GetWidth(text string) float32 {
133 | _, width, _ := generateLines(font, []string{text}, [][]float32{GetColor()}, -1)
134 | return width
135 | }
136 |
137 | // GetWrap will split a string given a wrap limit. It will return the max width
138 | // of the longest string and it will return the string split into the strings that
139 | // are smaller than the wrap limit.
140 | func (font *Font) GetWrap(text string, wrapLimit float32) (float32, []string) {
141 | lines, width, _ := generateLines(font, []string{text}, [][]float32{GetColor()}, wrapLimit)
142 | stringLines := make([]string, len(lines))
143 | for i, l := range lines {
144 | stringLines[i] = string(l.chars)
145 | }
146 | return width, stringLines
147 | }
148 |
--------------------------------------------------------------------------------
/audio/wrap.go:
--------------------------------------------------------------------------------
1 | package audio
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/yuin/gopher-lua"
7 |
8 | "github.com/tanema/amore/runtime"
9 | )
10 |
11 | var audioFunctions = runtime.LuaFuncs{
12 | "new": audioNewSource,
13 | "getvolume": audioGetVolume,
14 | "setvolume": audioSetVolume,
15 | "pause": audioPauseAll,
16 | "play": audioPlayAll,
17 | "rewind": audioRewindAll,
18 | "stop": audioStopAll,
19 | }
20 |
21 | var audioMetaTables = runtime.LuaMetaTable{
22 | "Source": {
23 | "finished": audioSourceIsFinished,
24 | "looping": audioSourceIsLooping,
25 | "paused": audioSourceIsPaused,
26 | "playing": audioSourceIsPlaying,
27 | "static": audioSourceIsStatic,
28 | "stopped": audioSourceIsStopped,
29 | "duration": audioSourceGetDuration,
30 | "pitch": audioSourceGetPitch,
31 | "volume": audioSourceGetVolume,
32 | "state": audioSourceGetState,
33 | "setlooping": audioSourceSetLooping,
34 | "setpitch": audioSourceSetPitch,
35 | "setvolume": audioSourceSetVolume,
36 | "play": audioSourcePlay,
37 | "pause": audioSourcePause,
38 | "resume": audioSourceResume,
39 | "rewind": audioSourceRewind,
40 | "seek": audioSourceSeek,
41 | "stop": audioSourceStop,
42 | "tell": audioSourceTell,
43 | },
44 | }
45 |
46 | func init() {
47 | runtime.RegisterModule("audio", audioFunctions, audioMetaTables)
48 | }
49 |
50 | func audioNewSource(ls *lua.LState) int {
51 | source, err := NewSource(ls.ToString(1), ls.ToBool(2))
52 | if err != nil {
53 | return 0
54 | }
55 | return returnSource(ls, source)
56 | }
57 |
58 | func audioGetVolume(ls *lua.LState) int {
59 | ls.Push(lua.LNumber(GetVolume()))
60 | return 1
61 | }
62 |
63 | func audioSetVolume(ls *lua.LState) int {
64 | SetVolume(float32(ls.ToNumber(1)))
65 | return 0
66 | }
67 |
68 | func audioPlayAll(ls *lua.LState) int {
69 | PlayAll()
70 | return 0
71 | }
72 |
73 | func audioPauseAll(ls *lua.LState) int {
74 | PlayAll()
75 | return 0
76 | }
77 |
78 | func audioRewindAll(ls *lua.LState) int {
79 | RewindAll()
80 | return 0
81 | }
82 |
83 | func audioStopAll(ls *lua.LState) int {
84 | StopAll()
85 | return 0
86 | }
87 |
88 | func audioSourceIsFinished(ls *lua.LState) int {
89 | ls.Push(lua.LBool(toSource(ls, 1).IsFinished()))
90 | return 1
91 | }
92 |
93 | func audioSourceIsLooping(ls *lua.LState) int {
94 | ls.Push(lua.LBool(toSource(ls, 1).IsLooping()))
95 | return 1
96 | }
97 |
98 | func audioSourceIsPaused(ls *lua.LState) int {
99 | ls.Push(lua.LBool(toSource(ls, 1).IsPaused()))
100 | return 1
101 | }
102 |
103 | func audioSourceIsPlaying(ls *lua.LState) int {
104 | ls.Push(lua.LBool(toSource(ls, 1).IsPlaying()))
105 | return 1
106 | }
107 |
108 | func audioSourceIsStatic(ls *lua.LState) int {
109 | ls.Push(lua.LBool(toSource(ls, 1).IsStatic()))
110 | return 1
111 | }
112 |
113 | func audioSourceIsStopped(ls *lua.LState) int {
114 | ls.Push(lua.LBool(toSource(ls, 1).IsStopped()))
115 | return 1
116 | }
117 |
118 | func audioSourceGetDuration(ls *lua.LState) int {
119 | ls.Push(lua.LNumber(toSource(ls, 1).GetDuration().Seconds()))
120 | return 1
121 | }
122 |
123 | func audioSourceTell(ls *lua.LState) int {
124 | ls.Push(lua.LNumber(toSource(ls, 1).Tell().Seconds()))
125 | return 1
126 | }
127 |
128 | func audioSourceGetPitch(ls *lua.LState) int {
129 | ls.Push(lua.LNumber(toSource(ls, 1).GetPitch()))
130 | return 1
131 | }
132 |
133 | func audioSourceGetVolume(ls *lua.LState) int {
134 | ls.Push(lua.LNumber(toSource(ls, 1).GetVolume()))
135 | return 1
136 | }
137 |
138 | func audioSourceGetState(ls *lua.LState) int {
139 | ls.Push(lua.LString(toSource(ls, 1).GetState()))
140 | return 1
141 | }
142 |
143 | func audioSourceSetLooping(ls *lua.LState) int {
144 | toSource(ls, 1).SetLooping(ls.ToBool(2))
145 | return 0
146 | }
147 |
148 | func audioSourceSetPitch(ls *lua.LState) int {
149 | toSource(ls, 1).SetPitch(float32(ls.ToNumber(2)))
150 | return 0
151 | }
152 |
153 | func audioSourceSetVolume(ls *lua.LState) int {
154 | toSource(ls, 1).SetVolume(float32(ls.ToNumber(2)))
155 | return 0
156 | }
157 |
158 | func audioSourceSeek(ls *lua.LState) int {
159 | toSource(ls, 1).Seek(time.Duration(float32(ls.ToNumber(2)) * 1e09))
160 | return 0
161 | }
162 |
163 | func audioSourcePlay(ls *lua.LState) int {
164 | toSource(ls, 1).Play()
165 | return 0
166 | }
167 |
168 | func audioSourcePause(ls *lua.LState) int {
169 | toSource(ls, 1).Pause()
170 | return 0
171 | }
172 |
173 | func audioSourceResume(ls *lua.LState) int {
174 | toSource(ls, 1).Resume()
175 | return 0
176 | }
177 |
178 | func audioSourceRewind(ls *lua.LState) int {
179 | toSource(ls, 1).Rewind()
180 | return 0
181 | }
182 |
183 | func audioSourceStop(ls *lua.LState) int {
184 | toSource(ls, 1).Stop()
185 | return 0
186 | }
187 |
188 | func toSource(ls *lua.LState, offset int) Source {
189 | img := ls.CheckUserData(offset)
190 | if v, ok := img.Value.(Source); ok {
191 | return v
192 | }
193 | ls.ArgError(offset, "audio source expected")
194 | return nil
195 | }
196 |
197 | func returnSource(ls *lua.LState, source Source) int {
198 | f := ls.NewUserData()
199 | f.Value = source
200 | ls.SetMetatable(f, ls.GetTypeMetatable("Source"))
201 | ls.Push(f)
202 | return 1
203 | }
204 |
--------------------------------------------------------------------------------
/gfx/canvas.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "fmt"
5 | "image"
6 |
7 | "github.com/go-gl/mathgl/mgl32"
8 |
9 | "github.com/goxjs/gl"
10 | )
11 |
12 | // Canvas is an off-screen render target.
13 | type Canvas struct {
14 | *Texture
15 | fbo gl.Framebuffer
16 | depthStencil gl.Renderbuffer
17 | status uint32
18 | width, height int32
19 | systemViewport []int32
20 | }
21 |
22 | // NewCanvas creates a pointer to a new canvas with the privided width and height
23 | func NewCanvas(width, height int32) *Canvas {
24 | newCanvas := &Canvas{
25 | width: width,
26 | height: height,
27 | }
28 | registerVolatile(newCanvas)
29 | return newCanvas
30 | }
31 |
32 | // loadVolatile will create the framebuffer and return true if successful
33 | func (canvas *Canvas) loadVolatile() bool {
34 | canvas.status = gl.FRAMEBUFFER_COMPLETE
35 |
36 | // glTexImage2D is guaranteed to error in this case.
37 | if canvas.width > maxTextureSize || canvas.height > maxTextureSize {
38 | canvas.status = gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT
39 | return false
40 | }
41 |
42 | canvas.Texture = newTexture(canvas.width, canvas.height, false)
43 | //NULL means reserve texture memory, but texels are undefined
44 | gl.TexImage2D(gl.TEXTURE_2D, 0, int(canvas.width), int(canvas.height), gl.RGBA, gl.UNSIGNED_BYTE, nil)
45 | if gl.GetError() != gl.NO_ERROR {
46 | canvas.status = gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT
47 | return false
48 | }
49 |
50 | canvas.fbo, canvas.status = newFBO(canvas.getHandle())
51 |
52 | if canvas.status != gl.FRAMEBUFFER_COMPLETE {
53 | if canvas.fbo.Valid() {
54 | gl.DeleteFramebuffer(canvas.fbo)
55 | canvas.fbo = gl.Framebuffer{}
56 | }
57 | return false
58 | }
59 |
60 | return true
61 | }
62 |
63 | // unLoadVolatile will release the texture, framebuffer and depth buffer
64 | func (canvas *Canvas) unLoadVolatile() {
65 | if glState.currentCanvas == canvas {
66 | canvas.stopGrab(false)
67 | }
68 | gl.DeleteFramebuffer(canvas.fbo)
69 | gl.DeleteRenderbuffer(canvas.depthStencil)
70 |
71 | canvas.fbo = gl.Framebuffer{}
72 | canvas.depthStencil = gl.Renderbuffer{}
73 | }
74 |
75 | // startGrab will bind this canvas to grab all drawing operations
76 | func (canvas *Canvas) startGrab() error {
77 | if glState.currentCanvas == canvas {
78 | return nil // already grabbing
79 | }
80 |
81 | // cleanup after previous Canvas
82 | if glState.currentCanvas != nil {
83 | canvas.systemViewport = glState.currentCanvas.systemViewport
84 | glState.currentCanvas.stopGrab(true)
85 | } else {
86 | canvas.systemViewport = GetViewport()
87 | }
88 |
89 | // indicate we are using this Canvas.
90 | glState.currentCanvas = canvas
91 | // bind the framebuffer object.
92 | gl.BindFramebuffer(gl.FRAMEBUFFER, canvas.fbo)
93 | SetViewport(0, 0, canvas.width, canvas.height)
94 | // Set up the projection matrix
95 | glState.projectionStack.Push()
96 | glState.projectionStack.Load(mgl32.Ortho(0.0, float32(screenWidth), 0.0, float32(screenHeight), -1, 1))
97 |
98 | return nil
99 | }
100 |
101 | // stopGrab will bind the context back to the default framebuffer and set back
102 | // all the settings
103 | func (canvas *Canvas) stopGrab(switchingToOtherCanvas bool) error {
104 | // i am not grabbing. leave me alone
105 | if glState.currentCanvas != canvas {
106 | return nil
107 | }
108 | glState.projectionStack.Pop()
109 | if !switchingToOtherCanvas {
110 | // bind system framebuffer.
111 | glState.currentCanvas = nil
112 | gl.BindFramebuffer(gl.FRAMEBUFFER, getDefaultFBO())
113 | SetViewport(canvas.systemViewport[0], canvas.systemViewport[1], canvas.systemViewport[2], canvas.systemViewport[3])
114 | }
115 | return nil
116 | }
117 |
118 | // NewImageData will create an image from the canvas data. It will return an error
119 | // only if the dimensions given are invalid
120 | func (canvas *Canvas) NewImageData(x, y, w, h int32) (*Image, error) {
121 | if x < 0 || y < 0 || w <= 0 || h <= 0 || (x+w) > canvas.width || (y+h) > canvas.height {
122 | return nil, fmt.Errorf("invalid ImageData rectangle dimensions")
123 | }
124 | prevCanvas := GetCanvas()
125 | SetCanvas(canvas)
126 | screenshot := image.NewRGBA(image.Rect(int(x), int(y), int(w), int(h)))
127 | stride := int32(screenshot.Stride)
128 | pixels := make([]byte, len(screenshot.Pix))
129 | gl.ReadPixels(pixels, int(x), int(y), int(w), int(h), gl.RGBA, gl.UNSIGNED_BYTE)
130 | for y := int32(0); y < h; y++ {
131 | i := (h - 1 - y) * stride
132 | copy(screenshot.Pix[y*stride:], pixels[i:i+w*4])
133 | }
134 | SetCanvas(prevCanvas)
135 | newImage := &Image{Texture: newImageTexture(screenshot, false)}
136 | registerVolatile(newImage)
137 | return newImage, nil
138 | }
139 |
140 | // checkCreateStencil if a stencil is set on a canvas then we need to create
141 | // some buffers to handle this.
142 | func (canvas *Canvas) checkCreateStencil() bool {
143 | // Do nothing if we've already created the stencil buffer.
144 | if canvas.depthStencil.Valid() {
145 | return true
146 | }
147 |
148 | if glState.currentCanvas != canvas {
149 | gl.BindFramebuffer(gl.FRAMEBUFFER, canvas.fbo)
150 | }
151 |
152 | format := gl.STENCIL_INDEX8
153 | attachment := gl.STENCIL_ATTACHMENT
154 |
155 | canvas.depthStencil = gl.CreateRenderbuffer()
156 | gl.BindRenderbuffer(gl.RENDERBUFFER, canvas.depthStencil)
157 | gl.RenderbufferStorage(gl.RENDERBUFFER, gl.Enum(format), int(canvas.width), int(canvas.height))
158 |
159 | // Attach the stencil buffer to the framebuffer object.
160 | gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.Enum(attachment), gl.RENDERBUFFER, canvas.depthStencil)
161 | gl.BindRenderbuffer(gl.RENDERBUFFER, gl.Renderbuffer{})
162 |
163 | success := (gl.CheckFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE)
164 |
165 | // We don't want the stencil buffer filled with garbage.
166 | if success {
167 | gl.Clear(gl.STENCIL_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
168 | } else {
169 | gl.DeleteRenderbuffer(canvas.depthStencil)
170 | canvas.depthStencil = gl.Renderbuffer{}
171 | }
172 |
173 | if glState.currentCanvas != nil && glState.currentCanvas != canvas {
174 | gl.BindFramebuffer(gl.FRAMEBUFFER, glState.currentCanvas.fbo)
175 | } else if glState.currentCanvas == nil {
176 | gl.BindFramebuffer(gl.FRAMEBUFFER, getDefaultFBO())
177 | }
178 |
179 | return success
180 | }
181 |
182 | // newFBO will generate a new Frame Buffer Object for use with the canvas
183 | func newFBO(texture gl.Texture) (gl.Framebuffer, uint32) {
184 | // get currently bound fbo to reset to it later
185 | currentFBO := gl.GetBoundFramebuffer()
186 |
187 | framebuffer := gl.CreateFramebuffer()
188 | gl.BindFramebuffer(gl.FRAMEBUFFER, framebuffer)
189 | if texture.Valid() {
190 | gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)
191 | // Initialize the texture to transparent black.
192 | gl.ClearColor(0.0, 0.0, 0.0, 0.0)
193 | gl.Clear(gl.COLOR_BUFFER_BIT)
194 | }
195 | status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER)
196 |
197 | // unbind framebuffer
198 | gl.BindFramebuffer(gl.FRAMEBUFFER, currentFBO)
199 |
200 | return framebuffer, uint32(status)
201 | }
202 |
--------------------------------------------------------------------------------
/gfx/polyline.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "math"
5 |
6 | "github.com/goxjs/gl"
7 | )
8 |
9 | // treat adjacent segments with angles between their directions <5 degree as straight
10 | const linesParallelEPS float32 = 0.05
11 |
12 | type polyLine struct {
13 | join string
14 | halfwidth float32
15 | }
16 |
17 | func determinant(vec1, vec2 []float32) float32 {
18 | return vec1[0]*vec2[1] - vec1[1]*vec2[0]
19 | }
20 |
21 | func getNormal(v1 []float32, scale float32) []float32 {
22 | return []float32{-v1[1] * scale, v1[0] * scale}
23 | }
24 |
25 | func normalize(v1 []float32, length float32) []float32 {
26 | lengthCurrent := vecLen(v1)
27 | if lengthCurrent > 0 {
28 | scale := length / lengthCurrent
29 | return []float32{v1[0] * scale, v1[1] * scale}
30 | }
31 | return v1
32 | }
33 |
34 | func vecLen(v1 []float32) float32 {
35 | return float32(math.Hypot(float64(v1[0]), float64(v1[1])))
36 | }
37 |
38 | func abs(a float32) float32 {
39 | if a < 0 {
40 | return -a
41 | } else if a == 0 {
42 | return 0
43 | }
44 | return a
45 | }
46 |
47 | func newPolyLine(join string, lineWidth float32) polyLine {
48 | newPolyline := polyLine{
49 | join: join,
50 | halfwidth: lineWidth * 0.5,
51 | }
52 | return newPolyline
53 | }
54 |
55 | func (polyline *polyLine) render(coords []float32) {
56 | var sleeve, current, next []float32
57 | vertices := []float32{}
58 |
59 | coordsCount := len(coords)
60 | isLooping := (coords[0] == coords[coordsCount-2]) && (coords[1] == coords[coordsCount-1])
61 | if !isLooping { // virtual starting point at second point mirrored on first point
62 | sleeve = []float32{coords[2] - coords[0], coords[3] - coords[1]}
63 | } else { // virtual starting point at last vertex
64 | sleeve = []float32{coords[0] - coords[coordsCount-4], coords[1] - coords[coordsCount-3]}
65 | }
66 |
67 | for i := 0; i+3 < coordsCount; i += 2 {
68 | current = []float32{coords[i], coords[i+1]}
69 | next = []float32{coords[i+2], coords[i+3]}
70 | vertices = append(vertices, polyline.renderEdge(sleeve, current, next)...)
71 | sleeve = []float32{next[0] - current[0], next[1] - current[1]}
72 | }
73 |
74 | if isLooping {
75 | vertices = append(vertices, polyline.renderEdge(sleeve, next, []float32{coords[2], coords[3]})...)
76 | } else {
77 | vertices = append(vertices, polyline.renderEdge(sleeve, next, []float32{next[0] + sleeve[0], next[1] + sleeve[1]})...)
78 | }
79 |
80 | prepareDraw(nil)
81 | bindTexture(glState.defaultTexture)
82 | useVertexAttribArrays(shaderPos)
83 |
84 | buffer := newVertexBuffer(len(vertices), vertices, UsageStatic)
85 | buffer.bind()
86 | defer buffer.unbind()
87 |
88 | gl.VertexAttribPointer(gl.Attrib{Value: 0}, 2, gl.FLOAT, false, 0, 0)
89 | gl.DrawArrays(gl.TRIANGLE_STRIP, 0, len(vertices)/2)
90 | }
91 |
92 | func (polyline *polyLine) renderEdge(sleeve, current, next []float32) []float32 {
93 | if polyline.join == "bevel" {
94 | return polyline.renderBevelEdge(sleeve, current, next)
95 | }
96 | return polyline.renderMiterEdge(sleeve, current, next)
97 | }
98 |
99 | func (polyline *polyLine) generateEdges(current []float32, normals ...float32) []float32 {
100 | verts := make([]float32, len(normals))
101 | for i := 0; i < len(normals); i += 2 {
102 | verts[i] = current[0] + normals[i]
103 | verts[i+1] = current[1] + normals[i+1]
104 | }
105 | return verts
106 | }
107 |
108 | /** Calculate line boundary points.
109 | *
110 | * Sketch:
111 | *
112 | * u1
113 | * -------------+---...___
114 | * | ```'''-- ---
115 | * p- - - - - - q- - . _ _ | w/2
116 | * | ` ' ' r +
117 | * -------------+---...___ | w/2
118 | * u2 ```'''-- ---
119 | *
120 | * u1 and u2 depend on four things:
121 | * - the half line width w/2
122 | * - the previous line vertex p
123 | * - the current line vertex q
124 | * - the next line vertex r
125 | *
126 | * u1/u2 are the intersection points of the parallel lines to p-q and q-r,
127 | * i.e. the point where
128 | *
129 | * (q + w/2 * ns) + lambda * (q - p) = (q + w/2 * nt) + mu * (r - q) (u1)
130 | * (q - w/2 * ns) + lambda * (q - p) = (q - w/2 * nt) + mu * (r - q) (u2)
131 | *
132 | * with nt,nt being the normals on the segments s = p-q and t = q-r,
133 | *
134 | * ns = perp(s) / |s|
135 | * nt = perp(t) / |t|.
136 | *
137 | * Using the linear equation system (similar for u2)
138 | *
139 | * q + w/2 * ns + lambda * s - (q + w/2 * nt + mu * t) = 0 (u1)
140 | * <=> q-q + lambda * s - mu * t = (nt - ns) * w/2
141 | * <=> lambda * s - mu * t = (nt - ns) * w/2
142 | *
143 | * the intersection points can be efficiently calculated using Cramer's rule.
144 | */
145 | func (polyline *polyLine) renderMiterEdge(sleeve, current, next []float32) []float32 {
146 | sleeveNormal := getNormal(sleeve, polyline.halfwidth/vecLen(sleeve))
147 | t := []float32{next[0] - current[0], next[1] - current[1]}
148 | lenT := vecLen(t)
149 |
150 | det := determinant(sleeve, t)
151 | // lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2
152 | if abs(det)/(vecLen(sleeve)*lenT) < linesParallelEPS && (sleeve[0]*t[0]+sleeve[1]*t[1]) > 0 {
153 | return polyline.generateEdges(current, sleeveNormal[0], sleeveNormal[1], sleeveNormal[0]*-1, sleeveNormal[1]*-1)
154 | }
155 | // cramers rule
156 | nt := getNormal(t, polyline.halfwidth/lenT)
157 | lambda := determinant([]float32{nt[0] - sleeveNormal[0], nt[1] - sleeveNormal[1]}, t) / det
158 | sleeveChange := []float32{sleeve[0] * lambda, sleeve[1] * lambda}
159 | d := []float32{sleeveNormal[0] + sleeveChange[0], sleeveNormal[1] + sleeveChange[1]}
160 | return polyline.generateEdges(current, d[0], d[1], d[0]*-1, d[1]*-1)
161 | }
162 |
163 | /** Calculate line boundary points.
164 | *
165 | * Sketch:
166 | *
167 | * uh1___uh2
168 | * .' '.
169 | * .' q '.
170 | * .' ' ' '.
171 | *.' ' .'. ' '.
172 | * ' .' ul'. '
173 | * p .' '. r
174 | *
175 | *
176 | * ul can be found as above, uh1 and uh2 are much simpler:
177 | *
178 | * uh1 = q + ns * w/2, uh2 = q + nt * w/2
179 | */
180 | func (polyline *polyLine) renderBevelEdge(sleeve, current, next []float32) []float32 {
181 | t := []float32{next[0] - current[0], next[1] - current[1]}
182 | lenT := vecLen(t)
183 |
184 | det := determinant(sleeve, t)
185 | if abs(det)/(vecLen(sleeve)*lenT) < linesParallelEPS && (sleeve[0]*t[0]+sleeve[1]*t[1]) > 0 {
186 | // lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2
187 | n := getNormal(t, polyline.halfwidth/lenT)
188 | return polyline.generateEdges(current, n[0], n[1], n[0]*-1, n[1]*-1)
189 | }
190 |
191 | // cramers rule
192 | sleeveNormal := getNormal(sleeve, polyline.halfwidth/vecLen(sleeve))
193 | nt := getNormal(t, polyline.halfwidth/lenT)
194 | lambda := determinant([]float32{nt[0] - sleeveNormal[0], nt[1] - sleeveNormal[1]}, t) / det
195 | sleeveChange := []float32{sleeve[0] * lambda, sleeve[1] * lambda}
196 | d := []float32{sleeveNormal[0] + sleeveChange[0], sleeveNormal[1] + sleeveChange[1]}
197 |
198 | if det > 0 { // 'left' turn -> intersection on the top
199 | return polyline.generateEdges(current,
200 | d[0], d[1],
201 | sleeveNormal[0]*-1, sleeveNormal[1]*-1,
202 | d[0], d[1],
203 | nt[0]*-1, nt[1]*-1,
204 | )
205 | }
206 |
207 | return polyline.generateEdges(current,
208 | sleeveNormal[0], sleeveNormal[1],
209 | d[0]*-1, d[1]*-1,
210 | nt[0], nt[1],
211 | d[0]*-1, d[1]*-1,
212 | )
213 | }
214 |
--------------------------------------------------------------------------------
/gfx/sprite_batch.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "fmt"
5 | "math"
6 |
7 | "github.com/go-gl/mathgl/mgl32"
8 |
9 | "github.com/goxjs/gl"
10 | )
11 |
12 | // SpriteBatch is a collection of images/quads/textures all drawn with a single draw call
13 | type SpriteBatch struct {
14 | size int
15 | count int
16 | color []float32 // Current color. This color, if present, will be applied to the next added sprite.
17 | arrayBuf *vertexBuffer
18 | quadIndices *quadIndices
19 | usage Usage
20 | texture ITexture
21 | rangeMin int
22 | rangeMax int
23 | }
24 |
25 | // NewSpriteBatch is like NewSpriteBatch but allows you to set the usage.
26 | func NewSpriteBatch(texture ITexture, size int, usage Usage) *SpriteBatch {
27 | return &SpriteBatch{
28 | size: size,
29 | texture: texture,
30 | usage: usage,
31 | color: []float32{1, 1, 1, 1},
32 | arrayBuf: newVertexBuffer(size*4*8, []float32{}, usage),
33 | quadIndices: newQuadIndices(size),
34 | rangeMin: -1,
35 | rangeMax: -1,
36 | }
37 | }
38 |
39 | // Add adds a sprite to the batch. Sprites are drawn in the order they are added.
40 | // x, y The position to draw the object
41 | // r rotation of the object
42 | // sx, sy scale of the object
43 | // ox, oy offset of the object
44 | // kx, ky shear of the object
45 | func (spriteBatch *SpriteBatch) Add(args ...float32) error {
46 | return spriteBatch.addv(spriteBatch.texture.getVerticies(), generateModelMatFromArgs(args), -1)
47 | }
48 |
49 | // Addq adds a Quad to the batch. This is very useful for something like a tilemap.
50 | func (spriteBatch *SpriteBatch) Addq(quad *Quad, args ...float32) error {
51 | return spriteBatch.addv(quad.getVertices(), generateModelMatFromArgs(args), -1)
52 | }
53 |
54 | // Set changes a sprite in the batch with the same arguments as add
55 | func (spriteBatch *SpriteBatch) Set(index int, args ...float32) error {
56 | return spriteBatch.addv(spriteBatch.texture.getVerticies(), generateModelMatFromArgs(args), index)
57 | }
58 |
59 | // Setq changes a sprite in the batch with the same arguments as addq
60 | func (spriteBatch *SpriteBatch) Setq(index int, quad *Quad, args ...float32) error {
61 | return spriteBatch.addv(quad.getVertices(), generateModelMatFromArgs(args), index)
62 | }
63 |
64 | // Clear will remove all the sprites from the batch
65 | func (spriteBatch *SpriteBatch) Clear() {
66 | spriteBatch.arrayBuf = newVertexBuffer(spriteBatch.size*4*8, []float32{}, spriteBatch.usage)
67 | spriteBatch.count = 0
68 | }
69 |
70 | // flush will ensure the data is uploaded to the buffer
71 | func (spriteBatch *SpriteBatch) flush() {
72 | spriteBatch.arrayBuf.bufferData()
73 | }
74 |
75 | // SetTexture will change the texture of the batch to a new one
76 | func (spriteBatch *SpriteBatch) SetTexture(newtexture ITexture) {
77 | spriteBatch.texture = newtexture
78 | }
79 |
80 | // GetTexture will return the currently bound texture of this sprite batch.
81 | func (spriteBatch *SpriteBatch) GetTexture() ITexture {
82 | return spriteBatch.texture
83 | }
84 |
85 | // SetColor will set the color that will be used for the next add or set operations.
86 | func (spriteBatch *SpriteBatch) SetColor(vals ...float32) {
87 | spriteBatch.color = vals
88 | }
89 |
90 | // ClearColor will reset the color back to white
91 | func (spriteBatch *SpriteBatch) ClearColor() {
92 | spriteBatch.color = []float32{1, 1, 1, 1}
93 | }
94 |
95 | // GetColor will return the currently used color.
96 | func (spriteBatch *SpriteBatch) GetColor() []float32 {
97 | return spriteBatch.color
98 | }
99 |
100 | // GetCount will return the amount of sprites already added to the batch
101 | func (spriteBatch *SpriteBatch) GetCount() int {
102 | return spriteBatch.count
103 | }
104 |
105 | // SetBufferSize will resize the buffer, change the limit of sprites you can add
106 | // to this batch.
107 | func (spriteBatch *SpriteBatch) SetBufferSize(newsize int) error {
108 | if newsize <= 0 {
109 | return fmt.Errorf("invalid SpriteBatch size")
110 | } else if newsize == spriteBatch.size {
111 | return nil
112 | }
113 | spriteBatch.arrayBuf = newVertexBuffer(newsize*4*8, spriteBatch.arrayBuf.data, spriteBatch.usage)
114 | spriteBatch.quadIndices = newQuadIndices(newsize)
115 | spriteBatch.size = newsize
116 | return nil
117 | }
118 |
119 | // GetBufferSize will return the limit of sprites you can add to this batch.
120 | func (spriteBatch *SpriteBatch) GetBufferSize() int {
121 | return spriteBatch.size
122 | }
123 |
124 | // addv will add a sprite to the batch using the verts, a transform and an index to
125 | // place it
126 | func (spriteBatch *SpriteBatch) addv(verts []float32, mat *mgl32.Mat4, index int) error {
127 | if index == -1 && spriteBatch.count >= spriteBatch.size {
128 | return fmt.Errorf("Sprite Batch Buffer Full")
129 | }
130 |
131 | sprite := make([]float32, 8*4)
132 | for i := 0; i < 32; i += 8 {
133 | j := (i / 2)
134 | sprite[i+0] = (mat[0] * verts[j+0]) + (mat[4] * verts[j+1]) + mat[12]
135 | sprite[i+1] = (mat[1] * verts[j+0]) + (mat[5] * verts[j+1]) + mat[13]
136 | sprite[i+2] = verts[j+2]
137 | sprite[i+3] = verts[j+3]
138 | sprite[i+4] = spriteBatch.color[0]
139 | sprite[i+5] = spriteBatch.color[1]
140 | sprite[i+6] = spriteBatch.color[2]
141 | sprite[i+7] = spriteBatch.color[3]
142 | }
143 |
144 | if index == -1 {
145 | spriteBatch.arrayBuf.fill(spriteBatch.count*4*8, sprite)
146 | spriteBatch.count++
147 | } else {
148 | spriteBatch.arrayBuf.fill(index*4*8, sprite)
149 | }
150 |
151 | return nil
152 | }
153 |
154 | // SetDrawRange will set a range in the points to draw. This is useful if you only
155 | // need to render a portion of the batch.
156 | func (spriteBatch *SpriteBatch) SetDrawRange(min, max int) error {
157 | if min < 0 || max < 0 || min > max {
158 | return fmt.Errorf("invalid draw range")
159 | }
160 | spriteBatch.rangeMin = min
161 | spriteBatch.rangeMax = max
162 | return nil
163 | }
164 |
165 | // ClearDrawRange will reset the draw range if you want to draw the whole batch again.
166 | func (spriteBatch *SpriteBatch) ClearDrawRange() {
167 | spriteBatch.rangeMin = -1
168 | spriteBatch.rangeMax = -1
169 | }
170 |
171 | // GetDrawRange will return the min, max range set on the batch. If no range is set
172 | // the range will return -1, -1
173 | func (spriteBatch *SpriteBatch) GetDrawRange() (int, int) {
174 | min := 0
175 | max := spriteBatch.count - 1
176 | if spriteBatch.rangeMax >= 0 {
177 | max = int(math.Min(float64(spriteBatch.rangeMax), float64(max)))
178 | }
179 | if spriteBatch.rangeMin >= 0 {
180 | min = int(math.Min(float64(spriteBatch.rangeMin), float64(max)))
181 | }
182 | return min, max
183 | }
184 |
185 | // Draw satisfies the Drawable interface. Inputs are as follows
186 | // x, y, r, sx, sy, ox, oy, kx, ky
187 | // x, y are position
188 | // r is rotation
189 | // sx, sy is the scale, if sy is not given sy will equal sx
190 | // ox, oy are offset
191 | // kx, ky are the shear. If ky is not given ky will equal kx
192 | func (spriteBatch *SpriteBatch) Draw(args ...float32) {
193 | if spriteBatch.count == 0 {
194 | return
195 | }
196 |
197 | prepareDraw(generateModelMatFromArgs(args))
198 | bindTexture(spriteBatch.texture.getHandle())
199 | useVertexAttribArrays(shaderPos, shaderTexCoord, shaderColor)
200 |
201 | spriteBatch.arrayBuf.bind()
202 | defer spriteBatch.arrayBuf.unbind()
203 |
204 | gl.VertexAttribPointer(gl.Attrib{Value: 0}, 2, gl.FLOAT, false, 8*4, 0)
205 | gl.VertexAttribPointer(gl.Attrib{Value: 1}, 2, gl.FLOAT, false, 8*4, 2*4)
206 | gl.VertexAttribPointer(gl.Attrib{Value: 2}, 4, gl.FLOAT, false, 8*4, 4*4)
207 |
208 | min, max := spriteBatch.GetDrawRange()
209 | spriteBatch.quadIndices.drawElements(gl.TRIANGLES, min, max-min+1)
210 | }
211 |
--------------------------------------------------------------------------------
/gfx/wrap/utils.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import (
4 | "github.com/yuin/gopher-lua"
5 |
6 | "github.com/tanema/amore/gfx"
7 | )
8 |
9 | func extractCoords(ls *lua.LState, offset int) []float32 {
10 | coords := extractFloatArray(ls, offset)
11 | if len(coords)%2 != 0 {
12 | ls.ArgError(0, "coordinates need to be given in pairs, arguments are not even")
13 | }
14 | return coords
15 | }
16 |
17 | func toFloat(ls *lua.LState, offset int) float32 {
18 | val := ls.Get(offset)
19 | lv, ok := val.(lua.LNumber)
20 | if !ok {
21 | ls.ArgError(offset, "invalid required argument")
22 | return 0
23 | }
24 | return float32(lv)
25 | }
26 |
27 | func toFloatD(ls *lua.LState, offset int, fallback float32) float32 {
28 | val := ls.Get(offset)
29 | if lv, ok := val.(lua.LNumber); ok {
30 | return float32(lv)
31 | }
32 | return fallback
33 | }
34 |
35 | func toInt(ls *lua.LState, offset int) int {
36 | val := ls.Get(offset)
37 | lv, ok := val.(lua.LNumber)
38 | if !ok {
39 | ls.ArgError(offset, "invalid required argument")
40 | return 0
41 | }
42 | return int(lv)
43 | }
44 |
45 | func toIntD(ls *lua.LState, offset int, fallback int) int {
46 | val := ls.Get(offset)
47 | if lv, ok := val.(lua.LNumber); ok {
48 | return int(lv)
49 | }
50 | return fallback
51 | }
52 |
53 | func toString(ls *lua.LState, offset int) string {
54 | val := ls.Get(offset)
55 | lv, ok := val.(lua.LString)
56 | if !ok {
57 | ls.ArgError(offset, "invalid required argument")
58 | return ""
59 | }
60 | return string(lv)
61 | }
62 |
63 | func toStringD(ls *lua.LState, offset int, fallback string) string {
64 | val := ls.Get(offset)
65 | if lv, ok := val.(lua.LString); ok {
66 | return string(lv)
67 | }
68 | return fallback
69 | }
70 |
71 | func extractMode(ls *lua.LState, offset int) string {
72 | mode := ls.ToString(offset)
73 | if mode == "" || (mode != "fill" && mode != "line") {
74 | ls.ArgError(offset, "invalid drawmode")
75 | }
76 | return mode
77 | }
78 |
79 | func extractBlendmode(ls *lua.LState, offset int) string {
80 | mode := ls.ToString(offset)
81 | if mode == "" || (mode != "multiplicative" && mode != "premultiplied" &&
82 | mode != "subtractive" && mode != "additive" && mode != "screen" && mode != "replace" && mode != "alpha") {
83 | ls.ArgError(offset, "invalid blendmode")
84 | }
85 | return mode
86 | }
87 |
88 | func extractLineJoin(ls *lua.LState, offset int) string {
89 | join := ls.ToString(offset)
90 | if join == "" || (join != "bevel" && join != "miter") {
91 | ls.ArgError(offset, "invalid drawmode")
92 | }
93 | return join
94 | }
95 |
96 | func extractFloatArray(ls *lua.LState, offset int) []float32 {
97 | args := []float32{}
98 | for x := ls.Get(offset); x != nil; offset++ {
99 | val := ls.Get(offset)
100 | if lv, ok := val.(lua.LNumber); ok {
101 | args = append(args, float32(lv))
102 | } else if val.Type() == lua.LTNil {
103 | break
104 | } else {
105 | ls.ArgError(offset, "argument wrong type, should be number")
106 | }
107 | }
108 |
109 | return args
110 | }
111 |
112 | func extractIntArray(ls *lua.LState, offset int) []int32 {
113 | args := []int32{}
114 | for x := ls.Get(offset); x != nil; offset++ {
115 | val := ls.Get(offset)
116 | if lv, ok := val.(lua.LNumber); ok {
117 | args = append(args, int32(lv))
118 | } else if val.Type() == lua.LTNil {
119 | break
120 | } else {
121 | ls.ArgError(offset, "argument wrong type, should be number")
122 | }
123 | }
124 |
125 | return args
126 | }
127 |
128 | func returnUD(ls *lua.LState, metatable string, item interface{}) int {
129 | f := ls.NewUserData()
130 | f.Value = item
131 | ls.SetMetatable(f, ls.GetTypeMetatable(metatable))
132 | ls.Push(f)
133 | return 1
134 | }
135 |
136 | func extractColor(ls *lua.LState, offset int) (r, g, b, a float32) {
137 | args := extractFloatArray(ls, offset)
138 | if len(args) == 0 {
139 | return 1, 1, 1, 1
140 | }
141 | argsLength := len(args)
142 | switch argsLength {
143 | case 4:
144 | return args[0], args[1], args[2], args[3]
145 | case 3:
146 | return args[0], args[1], args[2], 1
147 | case 2:
148 | ls.ArgError(offset, "argument wrong type, should be number")
149 | case 1:
150 | return args[0], args[0], args[0], 1
151 | }
152 | return 1, 1, 1, 1
153 | }
154 |
155 | func toWrap(ls *lua.LState, offset int) gfx.WrapMode {
156 | wrapStr := toStringD(ls, offset, "clamp")
157 | switch wrapStr {
158 | case "clamp":
159 | return gfx.WrapClamp
160 | case "repeat":
161 | return gfx.WrapRepeat
162 | case "mirror":
163 | return gfx.WrapMirroredRepeat
164 | default:
165 | ls.ArgError(offset, "invalid wrap mode")
166 | }
167 | return gfx.WrapClamp
168 | }
169 |
170 | func fromWrap(mode gfx.WrapMode) string {
171 | switch mode {
172 | case gfx.WrapRepeat:
173 | return "repeat"
174 | case gfx.WrapMirroredRepeat:
175 | return "mirror"
176 | case gfx.WrapClamp:
177 | fallthrough
178 | default:
179 | return "clamp"
180 | }
181 | }
182 |
183 | func toFilter(ls *lua.LState, offset int) gfx.FilterMode {
184 | wrapStr := toStringD(ls, offset, "near")
185 | switch wrapStr {
186 | case "none":
187 | return gfx.FilterNone
188 | case "near":
189 | return gfx.FilterNearest
190 | case "linear":
191 | return gfx.FilterLinear
192 | default:
193 | ls.ArgError(offset, "invalid filter mode")
194 | }
195 | return gfx.FilterNearest
196 | }
197 |
198 | func fromFilter(mode gfx.FilterMode) string {
199 | switch mode {
200 | case gfx.FilterLinear:
201 | return "linear"
202 | case gfx.FilterNone:
203 | return "none"
204 | case gfx.FilterNearest:
205 | fallthrough
206 | default:
207 | return "near"
208 | }
209 | }
210 |
211 | func toUsage(ls *lua.LState, offset int) gfx.Usage {
212 | wrapStr := toStringD(ls, offset, "dynamic")
213 | switch wrapStr {
214 | case "dynamic":
215 | return gfx.UsageDynamic
216 | case "static":
217 | return gfx.UsageStatic
218 | case "stream":
219 | return gfx.UsageStream
220 | default:
221 | ls.ArgError(offset, "invalid usage mode")
222 | }
223 | return gfx.UsageDynamic
224 | }
225 |
226 | func fromUsage(mode gfx.Usage) string {
227 | switch mode {
228 | case gfx.UsageStatic:
229 | return "static"
230 | case gfx.UsageStream:
231 | return "stream"
232 | case gfx.UsageDynamic:
233 | fallthrough
234 | default:
235 | return "dynamic"
236 | }
237 | }
238 |
239 | func toCompareMode(wrapStr string, offset int) gfx.CompareMode {
240 | switch wrapStr {
241 | case "always":
242 | return gfx.CompareAlways
243 | case "greater":
244 | return gfx.CompareGreater
245 | case "equal":
246 | return gfx.CompareEqual
247 | case "gequal":
248 | return gfx.CompareGequal
249 | case "less":
250 | return gfx.CompareLess
251 | case "nequal":
252 | return gfx.CompareNotequal
253 | case "lequal":
254 | return gfx.CompareLequal
255 | }
256 | return gfx.CompareAlways
257 | }
258 |
259 | func fromCompareMode(mode gfx.CompareMode) string {
260 | switch mode {
261 | case gfx.CompareLequal:
262 | return "lequal"
263 | case gfx.CompareNotequal:
264 | return "nequal"
265 | case gfx.CompareLess:
266 | return "less"
267 | case gfx.CompareGequal:
268 | return "gequal"
269 | case gfx.CompareEqual:
270 | return "equal"
271 | case gfx.CompareGreater:
272 | return "greater"
273 | case gfx.CompareAlways:
274 | fallthrough
275 | default:
276 | return "always"
277 | }
278 | }
279 |
280 | func toStencilAction(wrapStr string, offset int) gfx.StencilAction {
281 | switch wrapStr {
282 | case "replace":
283 | return gfx.StencilReplace
284 | case "increment":
285 | return gfx.StencilIncrement
286 | case "decrement":
287 | return gfx.StencilDecrement
288 | case "incrementwrap":
289 | return gfx.StencilIncrementWrap
290 | case "decrementwrap":
291 | return gfx.StencilDecrementWrap
292 | case "invert":
293 | return gfx.StencilInvert
294 | }
295 | return gfx.StencilReplace
296 | }
297 |
--------------------------------------------------------------------------------
/gfx/wrap/graphics.go:
--------------------------------------------------------------------------------
1 | package wrap
2 |
3 | import (
4 | "github.com/yuin/gopher-lua"
5 |
6 | "github.com/tanema/amore/gfx"
7 | )
8 |
9 | func gfxCirle(ls *lua.LState) int {
10 | gfx.Circle(extractMode(ls, 1), toFloat(ls, 2), toFloat(ls, 3), toFloat(ls, 4), toIntD(ls, 5, 30))
11 | return 0
12 | }
13 |
14 | func gfxArc(ls *lua.LState) int {
15 | gfx.Arc(extractMode(ls, 1), toFloat(ls, 2), toFloat(ls, 3), toFloat(ls, 4), toFloat(ls, 5), toFloat(ls, 6), toIntD(ls, 6, 30))
16 | return 0
17 | }
18 |
19 | func gfxEllipse(ls *lua.LState) int {
20 | gfx.Ellipse(extractMode(ls, 1), toFloat(ls, 2), toFloat(ls, 3), toFloat(ls, 4), toFloat(ls, 5), toIntD(ls, 6, 30))
21 | return 0
22 | }
23 |
24 | func gfxPoints(ls *lua.LState) int {
25 | gfx.Points(extractCoords(ls, 1))
26 | return 0
27 | }
28 |
29 | func gfxLine(ls *lua.LState) int {
30 | gfx.PolyLine(extractCoords(ls, 1))
31 | return 0
32 | }
33 |
34 | func gfxRectangle(ls *lua.LState) int {
35 | gfx.Rect(extractMode(ls, 1), toFloat(ls, 2), toFloat(ls, 3), toFloat(ls, 4), toFloat(ls, 5))
36 | return 0
37 | }
38 |
39 | func gfxPolygon(ls *lua.LState) int {
40 | gfx.Polygon(extractMode(ls, 1), extractCoords(ls, 2))
41 | return 0
42 | }
43 |
44 | func gfxScreenShot(ls *lua.LState) int {
45 | return returnUD(ls, "Image", gfx.NewScreenshot())
46 | }
47 |
48 | func gfxGetViewport(ls *lua.LState) int {
49 | for _, x := range gfx.GetViewport() {
50 | ls.Push(lua.LNumber(x))
51 | }
52 | return 4
53 | }
54 |
55 | func gfxSetViewport(ls *lua.LState) int {
56 | viewport := extractCoords(ls, 1)
57 | if len(viewport) == 0 {
58 | w, h := gfx.GetDimensions()
59 | gfx.SetViewportSize(int32(w), int32(h))
60 | } else if len(viewport) == 2 {
61 | gfx.SetViewportSize(int32(viewport[0]), int32(viewport[1]))
62 | } else if len(viewport) == 4 {
63 | gfx.SetViewport(int32(viewport[0]), int32(viewport[1]), int32(viewport[2]), int32(viewport[3]))
64 | } else {
65 | ls.ArgError(1, "either provide (x, y, w, h) or (w, h) nothing at all to reset the viewport")
66 | }
67 | return 0
68 | }
69 |
70 | func gfxGetWidth(ls *lua.LState) int {
71 | ls.Push(lua.LNumber(gfx.GetWidth()))
72 | return 1
73 | }
74 |
75 | func gfxGetHeight(ls *lua.LState) int {
76 | ls.Push(lua.LNumber(gfx.GetHeight()))
77 | return 1
78 | }
79 |
80 | func gfxGetDimensions(ls *lua.LState) int {
81 | w, h := gfx.GetDimensions()
82 | ls.Push(lua.LNumber(w))
83 | ls.Push(lua.LNumber(h))
84 | return 2
85 | }
86 |
87 | func gfxOrigin(ls *lua.LState) int {
88 | gfx.Origin()
89 | return 0
90 | }
91 |
92 | func gfxTranslate(ls *lua.LState) int {
93 | x, y := toFloat(ls, 1), toFloat(ls, 2)
94 | gfx.Translate(x, y)
95 | return 0
96 | }
97 |
98 | func gfxRotate(ls *lua.LState) int {
99 | gfx.Rotate(toFloat(ls, 1))
100 | return 0
101 | }
102 |
103 | func gfxScale(ls *lua.LState) int {
104 | sx := toFloat(ls, 1)
105 | sy := toFloatD(ls, 2, sx)
106 | gfx.Scale(sx, sy)
107 | return 0
108 | }
109 |
110 | func gfxShear(ls *lua.LState) int {
111 | kx := toFloat(ls, 1)
112 | ky := toFloatD(ls, 2, kx)
113 | gfx.Shear(kx, ky)
114 | return 0
115 | }
116 |
117 | func gfxPush(ls *lua.LState) int {
118 | gfx.Push()
119 | return 0
120 | }
121 |
122 | func gfxPop(ls *lua.LState) int {
123 | gfx.Pop()
124 | return 0
125 | }
126 |
127 | func gfxClear(ls *lua.LState) int {
128 | gfx.Clear(extractColor(ls, 1))
129 | return 0
130 | }
131 |
132 | func gfxSetScissor(ls *lua.LState) int {
133 | args := extractFloatArray(ls, 1)
134 | if len(args) == 2 {
135 | gfx.SetScissor(0, 0, int32(args[0]), int32(args[1]))
136 | } else if len(args) == 4 {
137 | gfx.SetScissor(int32(args[0]), int32(args[1]), int32(args[2]), int32(args[3]))
138 | } else if len(args) == 0 {
139 | gfx.ClearScissor()
140 | } else {
141 | ls.ArgError(1, "either pass 2, 4 or no arguments")
142 | }
143 | return 0
144 | }
145 |
146 | func gfxGetScissor(ls *lua.LState) int {
147 | x, y, w, h := gfx.GetScissor()
148 | ls.Push(lua.LNumber(x))
149 | ls.Push(lua.LNumber(y))
150 | ls.Push(lua.LNumber(w))
151 | ls.Push(lua.LNumber(h))
152 | return 4
153 | }
154 |
155 | func gfxSetLineWidth(ls *lua.LState) int {
156 | gfx.SetLineWidth(toFloat(ls, 1))
157 | return 0
158 | }
159 |
160 | func gfxSetLineJoin(ls *lua.LState) int {
161 | gfx.SetLineJoin(extractLineJoin(ls, 1))
162 | return 0
163 | }
164 |
165 | func gfxGetLineWidth(ls *lua.LState) int {
166 | ls.Push(lua.LNumber(gfx.GetLineWidth()))
167 | return 1
168 | }
169 |
170 | func gfxGetLineJoin(ls *lua.LState) int {
171 | ls.Push(lua.LString(gfx.GetLineJoin()))
172 | return 1
173 | }
174 |
175 | func gfxSetPointSize(ls *lua.LState) int {
176 | gfx.SetPointSize(toFloat(ls, 1))
177 | return 0
178 | }
179 |
180 | func gfxGetPointSize(ls *lua.LState) int {
181 | ls.Push(lua.LNumber(gfx.GetPointSize()))
182 | return 1
183 | }
184 |
185 | func gfxSetColor(ls *lua.LState) int {
186 | gfx.SetColor(extractColor(ls, 1))
187 | return 0
188 | }
189 |
190 | func gfxSetBackgroundColor(ls *lua.LState) int {
191 | gfx.SetBackgroundColor(extractColor(ls, 1))
192 | return 0
193 | }
194 |
195 | func gfxGetColor(ls *lua.LState) int {
196 | for _, x := range gfx.GetColor() {
197 | ls.Push(lua.LNumber(x))
198 | }
199 | return 4
200 | }
201 |
202 | func gfxGetBackgroundColor(ls *lua.LState) int {
203 | for _, x := range gfx.GetBackgroundColor() {
204 | ls.Push(lua.LNumber(x))
205 | }
206 | return 4
207 | }
208 |
209 | func gfxGetColorMask(ls *lua.LState) int {
210 | r, g, b, a := gfx.GetColorMask()
211 | ls.Push(lua.LBool(r))
212 | ls.Push(lua.LBool(g))
213 | ls.Push(lua.LBool(b))
214 | ls.Push(lua.LBool(a))
215 | return 4
216 | }
217 |
218 | func gfxSetColorMask(ls *lua.LState) int {
219 | args := []bool{}
220 | offset := 1
221 | for x := ls.Get(offset); x != nil; offset++ {
222 | val := ls.Get(offset)
223 | if lv, ok := val.(lua.LBool); ok {
224 | args = append(args, bool(lv))
225 | } else if val.Type() == lua.LTNil {
226 | break
227 | } else {
228 | ls.ArgError(offset, "argument wrong type, should be boolean")
229 | }
230 | }
231 |
232 | if len(args) == 4 {
233 | gfx.SetColorMask(args[0], args[1], args[2], args[3])
234 | } else if len(args) == 3 {
235 | gfx.SetColorMask(args[0], args[1], args[2], true)
236 | } else if len(args) == 1 {
237 | gfx.SetColorMask(args[0], args[0], args[0], args[0])
238 | } else if len(args) == 0 {
239 | gfx.ClearStencilTest()
240 | } else {
241 | ls.ArgError(offset, "invalid argument count")
242 | }
243 |
244 | return 0
245 | }
246 |
247 | func gfxSetFont(ls *lua.LState) int {
248 | gfx.SetFont(toFont(ls, 1))
249 | return 0
250 | }
251 |
252 | func gfxGetFont(ls *lua.LState) int {
253 | return returnUD(ls, "Font", gfx.GetFont())
254 | }
255 |
256 | func gfxSetBlendMode(ls *lua.LState) int {
257 | gfx.SetBlendMode(extractBlendmode(ls, 1))
258 | return 0
259 | }
260 |
261 | func gfxGetCanvas(ls *lua.LState) int {
262 | return returnUD(ls, "Canvas", gfx.GetCanvas())
263 | }
264 |
265 | func gfxSetCanvas(ls *lua.LState) int {
266 | gfx.SetCanvas(toCanvas(ls, 1))
267 | return 0
268 | }
269 |
270 | func gfxGetStencilTest(ls *lua.LState) int {
271 | mode, value := gfx.GetStencilTest()
272 | ls.Push(lua.LString(fromCompareMode(mode)))
273 | ls.Push(lua.LNumber(value))
274 | return 2
275 | }
276 |
277 | func gfxSetStencilTest(ls *lua.LState) int {
278 | wrapStr := toStringD(ls, 1, "")
279 | if wrapStr == "" {
280 | gfx.ClearStencilTest()
281 | return 0
282 | }
283 | gfx.SetStencilTest(toCompareMode(wrapStr, 1), int32(toInt(ls, 2)))
284 | return 0
285 | }
286 |
287 | func gfxStencil(ls *lua.LState) int {
288 | fn := ls.ToFunction(1)
289 | if fn == lua.LNil {
290 | ls.ArgError(1, "a function is required for a stencil")
291 | }
292 | fnWrap := func() {
293 | ls.CallByParam(lua.P{Fn: fn, Protect: true})
294 | }
295 | gfx.Stencil(fnWrap, toStencilAction(toStringD(ls, 2, "replace"), 2), int32(toIntD(ls, 3, 1)), ls.ToBool(4))
296 | return 0
297 | }
298 |
299 | func gfxSetShader(ls *lua.LState) int {
300 | gfx.SetShader(toShader(ls, 1))
301 | return 0
302 | }
303 |
--------------------------------------------------------------------------------
/gfx/texture.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "fmt"
5 | "image"
6 | "image/draw"
7 | // import all image packages to support them all
8 | _ "image/gif"
9 | _ "image/jpeg"
10 | _ "image/png"
11 | "runtime"
12 |
13 | "github.com/go-gl/mathgl/mgl32"
14 |
15 | "github.com/goxjs/gl"
16 | )
17 |
18 | type (
19 | // Filter is a representation of texture filtering that contains both min and
20 | // mag filter modes
21 | Filter struct {
22 | min, mag, mipmap FilterMode
23 | anisotropy float32
24 | }
25 | // Wrap is a representation of texture rapping containing both s and t wrap
26 | Wrap struct {
27 | s, t WrapMode
28 | }
29 | // Texture is a struct to wrap the opengl texture object
30 | Texture struct {
31 | textureID gl.Texture
32 | Width, Height int32
33 | vertices []float32
34 | filter Filter
35 | wrap Wrap
36 | mipmaps bool
37 | }
38 | // ITexture is an interface for any object that can be used like a texture.
39 | ITexture interface {
40 | getHandle() gl.Texture
41 | GetWidth() int32
42 | GetHeight() int32
43 | getVerticies() []float32
44 | }
45 | )
46 |
47 | // newFilter will create a Filter with default values
48 | func newFilter() Filter {
49 | return Filter{
50 | min: FilterLinear,
51 | mag: FilterLinear,
52 | mipmap: FilterNone,
53 | anisotropy: 1.0,
54 | }
55 | }
56 |
57 | // newTexture will return a new generated texture will not data uploaded to it.
58 | func newTexture(width, height int32, mipmaps bool) *Texture {
59 | newTexture := &Texture{
60 | textureID: gl.CreateTexture(),
61 | Width: width,
62 | Height: height,
63 | wrap: Wrap{s: WrapClamp, t: WrapClamp},
64 | filter: newFilter(),
65 | mipmaps: mipmaps,
66 | }
67 |
68 | newTexture.SetFilter(FilterNearest, FilterNearest)
69 | newTexture.SetWrap(WrapClamp, WrapClamp)
70 |
71 | if newTexture.mipmaps {
72 | newTexture.filter.mipmap = FilterNearest
73 | }
74 |
75 | newTexture.generateVerticies()
76 |
77 | return newTexture
78 | }
79 |
80 | func newImageTexture(img image.Image, mipmaps bool) *Texture {
81 | bounds := img.Bounds()
82 | newTexture := newTexture(int32(bounds.Dx()), int32(bounds.Dy()), mipmaps)
83 | rgba := image.NewRGBA(img.Bounds()) //generate a uniform image and upload to vram
84 | draw.Draw(rgba, bounds, img, image.Point{0, 0}, draw.Src)
85 | bindTexture(newTexture.getHandle())
86 | gl.TexImage2D(gl.TEXTURE_2D, 0, bounds.Dx(), bounds.Dy(), gl.RGBA, gl.UNSIGNED_BYTE, rgba.Pix)
87 | if newTexture.mipmaps {
88 | newTexture.generateMipmaps()
89 | }
90 | return newTexture
91 | }
92 |
93 | // getHandle will return the gl texutre handle
94 | func (texture *Texture) getHandle() gl.Texture {
95 | return texture.textureID
96 | }
97 |
98 | // generate both the x, y coords at origin and the uv coords.
99 | func (texture *Texture) generateVerticies() {
100 | w := float32(texture.Width)
101 | h := float32(texture.Height)
102 | texture.vertices = []float32{
103 | 0, 0, 0, 0,
104 | 0, h, 0, 1,
105 | w, 0, 1, 0,
106 | w, h, 1, 1,
107 | }
108 | }
109 |
110 | // GetWidth will return the height of the texture.
111 | func (texture *Texture) GetWidth() int32 {
112 | return texture.Width
113 | }
114 |
115 | // GetHeight will return the height of the texture.
116 | func (texture *Texture) GetHeight() int32 {
117 | return texture.Height
118 | }
119 |
120 | // GetDimensions will return the width and height of the texture.
121 | func (texture *Texture) GetDimensions() (int32, int32) {
122 | return texture.Width, texture.Height
123 | }
124 |
125 | // getVerticies will return the verticies generated when this texture was created.
126 | func (texture *Texture) getVerticies() []float32 {
127 | return texture.vertices
128 | }
129 |
130 | // generateMipmaps will generate mipmaps for the gl texture
131 | func (texture *Texture) generateMipmaps() {
132 | // The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't
133 | // have support for glGenerateMipmap.
134 | if texture.mipmaps {
135 | // Driver bug: http://www.opengl.org/wiki/Common_Mistakes#Automatic_mipmap_generation
136 | if runtime.GOOS == "windows" || runtime.GOOS == "linux" {
137 | gl.Enable(gl.TEXTURE_2D)
138 | }
139 |
140 | gl.GenerateMipmap(gl.TEXTURE_2D)
141 | }
142 | }
143 |
144 | // SetWrap will set how the texture behaves when applies to a plane that is larger
145 | // than itself.
146 | func (texture *Texture) SetWrap(wrapS, wrapT WrapMode) {
147 | texture.wrap.s = wrapS
148 | texture.wrap.t = wrapT
149 | bindTexture(texture.getHandle())
150 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, int(wrapS))
151 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, int(wrapT))
152 | }
153 |
154 | // GetWrap will return the wrapping for how the texture behaves on a plane that
155 | // is larger than itself
156 | func (texture *Texture) GetWrap() Wrap {
157 | return texture.wrap
158 | }
159 |
160 | // SetFilter will set the min, mag filters for the texture filtering.
161 | func (texture *Texture) SetFilter(min, mag FilterMode) error {
162 | if !texture.validateFilter() {
163 | if texture.filter.mipmap != FilterNone && !texture.mipmaps {
164 | return fmt.Errorf("non-mipmapped image cannot have mipmap filtering")
165 | }
166 | return fmt.Errorf("invalid texture filter")
167 | }
168 | texture.filter.min = min
169 | texture.filter.mag = mag
170 | texture.setTextureFilter()
171 | return nil
172 | }
173 |
174 | // setTextureFilter will set the texture filter on the actual gl texture. It will
175 | // not reach this state if the filter is not valid.
176 | func (texture *Texture) setTextureFilter() {
177 | var gmin, gmag uint32
178 |
179 | bindTexture(texture.getHandle())
180 |
181 | if texture.filter.mipmap == FilterNone {
182 | if texture.filter.min == FilterNearest {
183 | gmin = gl.NEAREST
184 | } else { // f.min == FilterLinear
185 | gmin = gl.LINEAR
186 | }
187 | } else {
188 | if texture.filter.min == FilterNearest && texture.filter.mipmap == FilterNearest {
189 | gmin = gl.NEAREST_MIPMAP_NEAREST
190 | } else if texture.filter.min == FilterNearest && texture.filter.mipmap == FilterLinear {
191 | gmin = gl.NEAREST_MIPMAP_LINEAR
192 | } else if texture.filter.min == FilterLinear && texture.filter.mipmap == FilterNearest {
193 | gmin = gl.LINEAR_MIPMAP_NEAREST
194 | } else if texture.filter.min == FilterLinear && texture.filter.mipmap == FilterLinear {
195 | gmin = gl.LINEAR_MIPMAP_LINEAR
196 | } else {
197 | gmin = gl.LINEAR
198 | }
199 | }
200 |
201 | switch texture.filter.mag {
202 | case FilterNearest:
203 | gmag = gl.NEAREST
204 | case FilterLinear:
205 | fallthrough
206 | default:
207 | gmag = gl.LINEAR
208 | }
209 |
210 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, int(gmin))
211 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, int(gmag))
212 | //gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAX_ANISOTROPY_EXT, texture.filter.anisotropy)
213 | }
214 |
215 | // GetFilter will return the filter set on this texture.
216 | func (texture *Texture) GetFilter() Filter {
217 | return texture.filter
218 | }
219 |
220 | // validateFilter will the the near and far filters and makes sure that it is possible
221 | func (texture *Texture) validateFilter() bool {
222 | if !texture.mipmaps && texture.filter.mipmap != FilterNone {
223 | return false
224 | }
225 |
226 | if texture.filter.mag != FilterLinear && texture.filter.mag != FilterNearest {
227 | return false
228 | }
229 |
230 | if texture.filter.min != FilterLinear && texture.filter.min != FilterNearest {
231 | return false
232 | }
233 |
234 | if texture.filter.mipmap != FilterLinear && texture.filter.mipmap != FilterNearest && texture.filter.mipmap != FilterNone {
235 | return false
236 | }
237 |
238 | return true
239 | }
240 |
241 | // loadVolatile satisfies the volatile interface, so that it can be unloaded
242 | func (texture *Texture) loadVolatile() bool {
243 | return false
244 | }
245 |
246 | // unloadVolatile release the texture data
247 | func (texture *Texture) unloadVolatile() {
248 | if texture != nil {
249 | return
250 | }
251 | deleteTexture(texture.textureID)
252 | texture = nil
253 | }
254 |
255 | // drawv will take in verticies from the public draw calls and draw the texture
256 | // with the verticies and the model matrix
257 | func (texture *Texture) drawv(model *mgl32.Mat4, vertices []float32) {
258 | prepareDraw(model)
259 | bindTexture(texture.getHandle())
260 | useVertexAttribArrays(shaderPos, shaderTexCoord)
261 |
262 | buffer := newVertexBuffer(len(vertices), vertices, UsageStatic)
263 | buffer.bind()
264 | defer buffer.unbind()
265 |
266 | gl.VertexAttribPointer(shaderPos, 2, gl.FLOAT, false, 4*4, 0)
267 | gl.VertexAttribPointer(shaderTexCoord, 2, gl.FLOAT, false, 4*4, 2*4)
268 |
269 | gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
270 | }
271 |
272 | // Draw satisfies the Drawable interface. Inputs are as follows
273 | // x, y, r, sx, sy, ox, oy, kx, ky
274 | // x, y are position
275 | // r is rotation
276 | // sx, sy is the scale, if sy is not given sy will equal sx
277 | // ox, oy are offset
278 | // kx, ky are the shear. If ky is not given ky will equal kx
279 | func (texture *Texture) Draw(args ...float32) {
280 | texture.drawv(generateModelMatFromArgs(args), texture.vertices)
281 | }
282 |
283 | // Drawq satisfies the QuadDrawable interface.
284 | // Inputs are as follows
285 | // quad is the quad to crop the texture
286 | // x, y, r, sx, sy, ox, oy, kx, ky
287 | // x, y are position
288 | // r is rotation
289 | // sx, sy is the scale, if sy is not given sy will equal sx
290 | // ox, oy are offset
291 | // kx, ky are the shear. If ky is not given ky will equal kx
292 | func (texture *Texture) Drawq(quad *Quad, args ...float32) {
293 | texture.drawv(generateModelMatFromArgs(args), quad.getVertices())
294 | }
295 |
--------------------------------------------------------------------------------
/audio/openal/source.go:
--------------------------------------------------------------------------------
1 | package openal
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/tanema/amore/audio/openal/al"
7 | "github.com/tanema/amore/audio/openal/decoding"
8 | )
9 |
10 | const (
11 | maxAttenuationDistance = 1000000.0 // upper limit of sound attentuation time.
12 | maxBuffers = 8 //arbitrary limit of umber of buffers a source can use to stream
13 | )
14 |
15 | // Source manages decoding sound data, creates an openal sound and manages the
16 | // data associated with the source.
17 | type Source struct {
18 | decoder *decoding.Decoder
19 | source al.Source
20 | isStatic bool
21 | pitch float32
22 | volume float32
23 | looping bool
24 | paused bool
25 | staticBuffer al.Buffer
26 | offsetBytes int32
27 | }
28 |
29 | // NewSource creates a new Source from a file at the path provided. If you
30 | // specify a static source it will all be buffered into a single buffer. If
31 | // false then it will create many buffers a cycle through them with data chunks.
32 | // This allows a smaller memory footprint while playing bigger music files. You
33 | // may want a static file if the sound is less than 2 seconds. It allows for faster
34 | // cleaning playing of shorter sounds like footsteps.
35 | func NewSource(filepath string, static bool) (*Source, error) {
36 | if pool == nil {
37 | createPool()
38 | }
39 |
40 | decoder, err := decoding.Decode(filepath)
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | newSource := &Source{
46 | decoder: decoder,
47 | isStatic: static,
48 | pitch: 1,
49 | volume: 1,
50 | }
51 |
52 | if static {
53 | newSource.staticBuffer = al.GenBuffers(1)[0]
54 | newSource.staticBuffer.BufferData(decoder.Format, decoder.GetData(), decoder.SampleRate)
55 | }
56 |
57 | return newSource, nil
58 | }
59 |
60 | // isValid will return true if the source is associated with an openal source anymore.
61 | // if not it will return false and will disable most funtionality
62 | func (s *Source) isValid() bool {
63 | return s.source != 0
64 | }
65 |
66 | // IsFinished will return true if the source is at the end of its duration and
67 | // it is not a looping source.
68 | func (s *Source) IsFinished() bool {
69 | if s.isStatic {
70 | return s.IsStopped()
71 | }
72 | return s.IsStopped() && !s.IsLooping() && s.decoder.IsFinished()
73 | }
74 |
75 | // update will return true if successfully updated the source. If the source is
76 | // static it will return if the item is still playing. If the item is a streamed
77 | // source it will return true if it is still playing but after updating it's buffers.
78 | func (s *Source) update() bool {
79 | if !s.isValid() {
80 | return false
81 | }
82 |
83 | if s.isStatic {
84 | return !s.IsStopped()
85 | } else if s.IsLooping() || !s.IsFinished() {
86 | pool.mutex.Lock()
87 | defer pool.mutex.Unlock()
88 | for i := s.source.BuffersProcessed(); i > 0; i-- {
89 | curOffsetBytes := s.source.OffsetByte()
90 | buffer := s.source.UnqueueBuffer()
91 | newOffsetBytes := s.source.OffsetByte()
92 | s.offsetBytes += (curOffsetBytes - newOffsetBytes)
93 | if s.stream(buffer) > 0 {
94 | s.source.QueueBuffers(buffer)
95 | }
96 | }
97 | return true
98 | }
99 |
100 | return false
101 | }
102 |
103 | // reset sets all the source's values in openal to the preset values.
104 | func (s *Source) reset() {
105 | if !s.isValid() {
106 | return
107 | }
108 | s.source.SetGain(s.volume)
109 | s.source.SetPitch(s.pitch)
110 | if s.isStatic {
111 | s.source.SetLooping(s.looping)
112 | }
113 | }
114 |
115 | // GetDuration returns the total duration of the source.
116 | func (s *Source) GetDuration() time.Duration {
117 | return s.decoder.Duration()
118 | }
119 |
120 | // GetPitch returns the current pitch of the Source in the range 0.0, 1.0
121 | func (s *Source) GetPitch() float32 {
122 | if s.isValid() {
123 | return s.source.Pitch()
124 | }
125 | return s.pitch
126 | }
127 |
128 | // GetVolume returns the current volume of the Source.
129 | func (s *Source) GetVolume() float32 {
130 | if s.isValid() {
131 | return s.source.Gain()
132 | }
133 | return s.volume
134 | }
135 |
136 | // GetState returns the playing state of the source.
137 | func (s *Source) GetState() string {
138 | switch s.source.State() {
139 | case al.Initial:
140 | return "initial"
141 | case al.Playing:
142 | return "playing"
143 | case al.Paused:
144 | return "paused"
145 | case al.Stopped:
146 | return "stopped"
147 | default:
148 | return "unknown"
149 | }
150 | }
151 |
152 | // IsLooping returns whether the Source will loop.
153 | func (s *Source) IsLooping() bool {
154 | return s.looping
155 | }
156 |
157 | // IsPaused returns whether the Source is paused.
158 | func (s *Source) IsPaused() bool {
159 | if s.isValid() {
160 | return s.GetState() == "paused"
161 | }
162 | return false
163 | }
164 |
165 | // IsPlaying returns whether the Source is playing.
166 | func (s *Source) IsPlaying() bool {
167 | if s.isValid() {
168 | return s.GetState() == "playing"
169 | }
170 | return false
171 | }
172 |
173 | // IsStatic returns whether the Source is static or stream.
174 | func (s *Source) IsStatic() bool {
175 | return s.isStatic
176 | }
177 |
178 | // IsStopped returns whether the Source is stopped.
179 | func (s *Source) IsStopped() bool {
180 | if s.isValid() {
181 | return s.GetState() == "stopped"
182 | }
183 | return true
184 | }
185 |
186 | // SetLooping sets whether the Source should loop when the source is complete.
187 | func (s *Source) SetLooping(loop bool) {
188 | s.looping = loop
189 | s.reset()
190 | }
191 |
192 | // SetPitch sets the pitch of the Source, the value should be between 0.0, 1.0
193 | func (s *Source) SetPitch(p float32) {
194 | s.pitch = p
195 | s.reset()
196 | }
197 |
198 | // SetVolume sets the current volume of the Source.
199 | func (s *Source) SetVolume(v float32) {
200 | s.volume = v
201 | s.reset()
202 | }
203 |
204 | // Play starts playing the source.
205 | func (s *Source) Play() bool {
206 | if s.IsPlaying() {
207 | return true
208 | }
209 |
210 | if s.IsPaused() {
211 | s.Resume()
212 | return true
213 | }
214 |
215 | //claim a source for ourselves and make sure it worked
216 | if !pool.claim(s) || !s.isValid() {
217 | return false
218 | }
219 |
220 | pool.mutex.Lock()
221 | defer pool.mutex.Unlock()
222 |
223 | if s.isStatic {
224 | s.source.SetBuffer(s.staticBuffer)
225 | } else {
226 | buffers := []al.Buffer{}
227 | for i := 0; i < maxBuffers; i++ {
228 | buffer := al.GenBuffers(1)[0]
229 | if s.stream(buffer) > 0 {
230 | buffers = append(buffers, buffer)
231 | }
232 | if s.decoder.IsFinished() {
233 | break
234 | }
235 | }
236 | if len(buffers) > 0 {
237 | s.source.QueueBuffers(buffers...)
238 | }
239 | }
240 |
241 | // This Source may now be associated with an OpenAL source that still has
242 | // the properties of another Source. Let's reset it to the settings
243 | // of the new one.
244 | s.reset()
245 |
246 | // Clear errors.
247 | al.Error()
248 |
249 | al.PlaySources(s.source)
250 |
251 | // alSourcePlay may fail if the system has reached its limit of simultaneous
252 | // playing sources.
253 | return al.Error() == al.NoError
254 | }
255 |
256 | // stream fills a buffer with the next chunk of data
257 | func (s *Source) stream(buffer al.Buffer) int {
258 | decoded := s.decoder.Decode() //get more data
259 | if decoded > 0 {
260 | buffer.BufferData(s.decoder.Format, s.decoder.Buffer, s.decoder.SampleRate)
261 | }
262 | if s.decoder.IsFinished() && s.IsLooping() {
263 | s.Rewind()
264 | }
265 | return decoded
266 | }
267 |
268 | // Pause pauses the source.
269 | func (s *Source) Pause() {
270 | if s.isValid() {
271 | pool.mutex.Lock()
272 | defer pool.mutex.Unlock()
273 | al.PauseSources(s.source)
274 | s.paused = true
275 | }
276 | }
277 |
278 | // Resume resumes a paused source.
279 | func (s *Source) Resume() {
280 | if s.isValid() && s.paused {
281 | pool.mutex.Lock()
282 | defer pool.mutex.Unlock()
283 | al.PlaySources(s.source)
284 | s.paused = false
285 | }
286 | }
287 |
288 | // Rewind rewinds the source source to its start time.
289 | func (s *Source) Rewind() { s.Seek(0) }
290 |
291 | // Seek sets the currently playing position of the Source.
292 | func (s *Source) Seek(offset time.Duration) {
293 | if !s.isValid() {
294 | return
295 | }
296 | s.offsetBytes = s.decoder.DurToByteOffset(offset)
297 | if !s.isStatic {
298 | al.StopSources(s.source)
299 | s.decoder.Seek(int64(s.offsetBytes))
300 | for i := s.source.BuffersQueued(); i > 0; i-- {
301 | buffer := s.source.UnqueueBuffer()
302 | if s.stream(buffer) > 0 {
303 | s.source.QueueBuffers(buffer)
304 | } else {
305 | al.DeleteBuffers(buffer)
306 | }
307 | }
308 | if !s.paused {
309 | al.PlaySources(s.source)
310 | }
311 | } else {
312 | pool.mutex.Lock()
313 | defer pool.mutex.Unlock()
314 | s.source.SetOffsetBytes(s.offsetBytes)
315 | }
316 | }
317 |
318 | // Stop stops a playing source.
319 | func (s *Source) Stop() {
320 | if s.isValid() {
321 | pool.mutex.Lock()
322 | defer pool.mutex.Unlock()
323 | al.StopSources(s.source)
324 | s.offsetBytes = 0
325 | if !s.isStatic {
326 | queued := s.source.BuffersQueued()
327 | for i := queued; i > 0; i-- {
328 | buffer := s.source.UnqueueBuffer()
329 | al.DeleteBuffers(buffer)
330 | }
331 | s.decoder.Seek(0)
332 | } else {
333 | s.source.SetOffsetBytes(0)
334 | }
335 | s.source.ClearBuffers()
336 | pool.release(s)
337 | }
338 | }
339 |
340 | // Tell returns the currently playing position of the Source.
341 | func (s *Source) Tell() time.Duration {
342 | if s.isValid() {
343 | pool.mutex.Lock()
344 | defer pool.mutex.Unlock()
345 | if s.isStatic {
346 | return s.decoder.ByteOffsetToDur(s.source.OffsetByte())
347 | }
348 | return s.decoder.ByteOffsetToDur(s.offsetBytes + s.source.OffsetByte())
349 | }
350 | return time.Duration(0.0)
351 | }
352 |
--------------------------------------------------------------------------------
/gfx/graphics.go:
--------------------------------------------------------------------------------
1 | // Package gfx is used largly to simplify OpenGL calls and to manage state
2 | // of transformations. Anything meant to be drawn to screen will come from this
3 | // pacakge.
4 | package gfx
5 |
6 | import (
7 | "image"
8 | "math"
9 |
10 | "github.com/go-gl/mathgl/mgl32"
11 |
12 | "github.com/goxjs/gl"
13 | )
14 |
15 | // this is the default amount of points to allow a circle or arc to use when
16 | // generating points
17 | //const defaultPointCount = 30
18 |
19 | // Circle will draw a circle at x, y with a radius as specified.
20 | // points specifies how many points should be generated in the arc.
21 | // If it is lower it will look jagged. If it is higher it will hit performace.
22 | // The drawmode specifies either a fill or line draw
23 | func Circle(mode string, x, y, radius float32, points int) {
24 | Ellipse(mode, x, y, radius, radius, points)
25 | }
26 |
27 | // Arc is like Arc except that you can define how many points you want to generate
28 | // the arc.
29 | // If it is lower it will look jagged. If it is higher it will hit performace.
30 | // The drawmode specifies either a fill or line draw
31 | func Arc(mode string, x, y, radius, angle1, angle2 float32, points int) {
32 | // Nothing to display with no points or equal angles. (Or is there with line mode?)
33 | if points <= 0 || angle1 == angle2 {
34 | return
35 | }
36 |
37 | // Oh, you want to draw a circle?
38 | if math.Abs(float64(angle1-angle2)) >= (2.0 * math.Pi) {
39 | Circle(mode, x, y, radius, points)
40 | return
41 | }
42 |
43 | angleShift := (angle2 - angle1) / float32(points)
44 | // Bail on precision issues.
45 | if angleShift == 0.0 {
46 | return
47 | }
48 |
49 | phi := angle1
50 | numCoords := (points + 3) * 2
51 | coords := make([]float32, numCoords)
52 | coords[0] = x
53 | coords[numCoords-2] = x
54 | coords[1] = y
55 | coords[numCoords-1] = y
56 |
57 | for i := 0; i <= points; i++ {
58 | phi = phi + angleShift
59 | coords[2*(i+1)] = x + radius*float32(math.Cos(float64(phi)))
60 | coords[2*(i+1)+1] = y + radius*float32(math.Sin(float64(phi)))
61 | }
62 |
63 | if mode == "line" {
64 | PolyLine(coords)
65 | } else {
66 | prepareDraw(nil)
67 | bindTexture(glState.defaultTexture)
68 | useVertexAttribArrays(shaderPos)
69 |
70 | buffer := newVertexBuffer(len(coords), coords, UsageStatic)
71 | buffer.bind()
72 | defer buffer.unbind()
73 |
74 | gl.VertexAttribPointer(shaderPos, 2, gl.FLOAT, false, 0, 0)
75 | gl.DrawArrays(gl.TRIANGLE_FAN, 0, len(coords)/2-1)
76 | }
77 | }
78 |
79 | // Ellipse will draw a circle at x, y with a radius as specified.
80 | // radiusx and radiusy will specify how much the width will be along those axis
81 | // points specifies how many points should be generated in the arc.
82 | // If it is lower it will look jagged. If it is higher it will hit performace.
83 | // The drawmode specifies either a fill or line draw
84 | func Ellipse(mode string, x, y, radiusx, radiusy float32, points int) {
85 | twoPi := math.Pi * 2.0
86 | if points <= 0 {
87 | points = 1
88 | }
89 |
90 | angleShift := float32(twoPi) / float32(points)
91 | phi := float32(0.0)
92 |
93 | coords := make([]float32, 2*(points+1))
94 | for i := 0; i < points; i++ {
95 | phi += angleShift
96 | coords[2*i+0] = x + radiusx*float32(math.Cos(float64(phi)))
97 | coords[2*i+1] = y + radiusy*float32(math.Sin(float64(phi)))
98 | }
99 |
100 | coords[2*points+0] = coords[0]
101 | coords[2*points+1] = coords[1]
102 |
103 | if mode == "line" {
104 | PolyLine(coords)
105 | } else {
106 | prepareDraw(nil)
107 | bindTexture(glState.defaultTexture)
108 | useVertexAttribArrays(shaderPos)
109 |
110 | buffer := newVertexBuffer(len(coords), coords, UsageStatic)
111 | buffer.bind()
112 | defer buffer.unbind()
113 |
114 | gl.VertexAttribPointer(shaderPos, 2, gl.FLOAT, false, 0, 0)
115 | gl.DrawArrays(gl.TRIANGLE_FAN, 0, len(coords)/2-1)
116 | }
117 | }
118 |
119 | // Points will draw a point on the screen at x, y position. The size of the point
120 | // is dependant on the point size set with SetPointSize.
121 | func Points(coords []float32) {
122 | prepareDraw(nil)
123 | bindTexture(glState.defaultTexture)
124 | useVertexAttribArrays(shaderPos)
125 |
126 | buffer := newVertexBuffer(len(coords), coords, UsageStatic)
127 | buffer.bind()
128 | defer buffer.unbind()
129 |
130 | gl.VertexAttribPointer(shaderPos, 2, gl.FLOAT, false, 0, 0)
131 | gl.DrawArrays(gl.POINTS, 0, len(coords)/2)
132 | }
133 |
134 | // PolyLine will draw a line with an array in the form of x1, y1, x2, y2, x3, y3, ..... xn, yn
135 | func PolyLine(coords []float32) {
136 | polyline := newPolyLine(states.back().lineJoin, states.back().lineWidth)
137 | polyline.render(coords)
138 | }
139 |
140 | // Rect draws a rectangle with the top left corner at x, y with the specified width
141 | // and height
142 | // The drawmode specifies either a fill or line draw
143 | func Rect(mode string, x, y, width, height float32) {
144 | Polygon(mode, []float32{x, y, x, y + height, x + width, y + height, x + width, y})
145 | }
146 |
147 | // Polygon will draw a closed polygon with an array in the form of x1, y1, x2, y2, x3, y3, ..... xn, yn
148 | // The drawmode specifies either a fill or line draw
149 | func Polygon(mode string, coords []float32) {
150 | coords = append(coords, coords[0], coords[1])
151 | if mode == "line" {
152 | PolyLine(coords)
153 | } else {
154 | prepareDraw(nil)
155 | bindTexture(glState.defaultTexture)
156 | useVertexAttribArrays(shaderPos)
157 |
158 | buffer := newVertexBuffer(len(coords), coords, UsageStatic)
159 | buffer.bind()
160 | defer buffer.unbind()
161 |
162 | gl.VertexAttribPointer(shaderPos, 2, gl.FLOAT, false, 0, 0)
163 | gl.DrawArrays(gl.TRIANGLE_FAN, 0, len(coords)/2-1)
164 | }
165 | }
166 |
167 | // NewScreenshot will take a screenshot of the screen and convert it to an image.Image
168 | func NewScreenshot() *Image {
169 | // Temporarily unbind the currently active canvas (glReadPixels reads the active framebuffer, not the main one.)
170 | canvas := GetCanvas()
171 | SetCanvas(nil)
172 |
173 | w, h := int32(screenWidth), int32(screenHeight)
174 | screenshot := image.NewRGBA(image.Rect(0, 0, int(w), int(h)))
175 | stride := int32(screenshot.Stride)
176 | pixels := make([]byte, len(screenshot.Pix))
177 | gl.ReadPixels(pixels, 0, 0, int(w), int(h), gl.RGBA, gl.UNSIGNED_BYTE)
178 |
179 | // OpenGL sucks and reads pixels from the lower-left. Let's fix that.
180 | for y := int32(0); y < h; y++ {
181 | i := (h - 1 - y) * stride
182 | copy(screenshot.Pix[y*stride:], pixels[i:i+w*4])
183 | }
184 |
185 | // Re-bind the active canvas, if necessary.
186 | SetCanvas(canvas)
187 | newImage := &Image{Texture: newImageTexture(screenshot, false)}
188 | registerVolatile(newImage)
189 | return newImage
190 | }
191 |
192 | // Normalized an array of floats into these params if they exist
193 | // if they are not present then thier default values are returned
194 | // x The position of the object along the x-axis.
195 | // y The position of the object along the y-axis.
196 | // angle The angle of the object (in radians).
197 | // sx The scale factor along the x-axis.
198 | // sy The scale factor along the y-axis.
199 | // ox The origin offset along the x-axis.
200 | // oy The origin offset along the y-axis.
201 | // kx Shear along the x-axis.
202 | // ky Shear along the y-axis.
203 | func normalizeDrawCallArgs(args []float32) (float32, float32, float32, float32, float32, float32, float32, float32, float32) {
204 | var x, y, angle, sx, sy, ox, oy, kx, ky float32
205 | sx = 1
206 | sy = 1
207 |
208 | if args == nil || len(args) < 2 {
209 | return x, y, angle, sx, sy, ox, oy, kx, ky
210 | }
211 |
212 | argsLength := len(args)
213 |
214 | switch argsLength {
215 | case 9:
216 | ky = args[8]
217 | fallthrough
218 | case 8:
219 | kx = args[7]
220 | if argsLength == 8 {
221 | ky = kx
222 | }
223 | fallthrough
224 | case 7:
225 | oy = args[6]
226 | fallthrough
227 | case 6:
228 | ox = args[5]
229 | if argsLength == 6 {
230 | oy = ox
231 | }
232 | fallthrough
233 | case 5:
234 | sy = args[4]
235 | fallthrough
236 | case 4:
237 | sx = args[3]
238 | if argsLength == 4 {
239 | sy = sx
240 | }
241 | fallthrough
242 | case 3:
243 | angle = args[2]
244 | fallthrough
245 | case 2:
246 | x = args[0]
247 | y = args[1]
248 | }
249 |
250 | return x, y, angle, sx, sy, ox, oy, kx, ky
251 | }
252 |
253 | // generateModelMatFromArgs will take in the arguments
254 | // x, y, r, sx, sy, ox, oy, kx, ky
255 | // and generate a matrix to be applied to the model transformation.
256 | func generateModelMatFromArgs(args []float32) *mgl32.Mat4 {
257 | x, y, angle, sx, sy, ox, oy, kx, ky := normalizeDrawCallArgs(args)
258 | mat := mgl32.Ident4()
259 | c := float32(math.Cos(float64(angle)))
260 | s := float32(math.Sin(float64(angle)))
261 | // matrix multiplication carried out on paper:
262 | // |1 x| |c -s | |sx | | 1 ky | |1 -ox|
263 | // | 1 y| |s c | | sy | |kx 1 | | 1 -oy|
264 | // | 1 | | 1 | | 1 | | 1 | | 1 |
265 | // | 1| | 1| | 1| | 1| | 1 |
266 | // move rotate scale skew origin
267 | mat[10] = 1
268 | mat[15] = 1
269 | mat[0] = c*sx - ky*s*sy // = a
270 | mat[1] = s*sx + ky*c*sy // = b
271 | mat[4] = kx*c*sx - s*sy // = c
272 | mat[5] = kx*s*sx + c*sy // = d
273 | mat[12] = x - ox*mat[0] - oy*mat[4]
274 | mat[13] = y - ox*mat[1] - oy*mat[5]
275 |
276 | return &mat
277 | }
278 |
279 | func f32Bytes(values []float32) []byte {
280 | b := make([]byte, 4*len(values))
281 | for i, v := range values {
282 | u := math.Float32bits(v)
283 | b[4*i+0] = byte(u >> 0)
284 | b[4*i+1] = byte(u >> 8)
285 | b[4*i+2] = byte(u >> 16)
286 | b[4*i+3] = byte(u >> 24)
287 | }
288 | return b
289 | }
290 |
291 | func ui32Bytes(values []uint32) []byte {
292 | b := make([]byte, 4*len(values))
293 | for i, v := range values {
294 | b[4*i+0] = byte(v)
295 | b[4*i+1] = byte(v >> 8)
296 | b[4*i+2] = byte(v >> 16)
297 | b[4*i+3] = byte(v >> 24)
298 | }
299 | return b
300 | }
301 |
--------------------------------------------------------------------------------
/gfx/shader.go:
--------------------------------------------------------------------------------
1 | package gfx
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "regexp"
8 | "strings"
9 |
10 | "github.com/go-gl/mathgl/mgl32"
11 | "github.com/goxjs/gl"
12 |
13 | "github.com/tanema/amore/file"
14 | )
15 |
16 | // Shader is a glsl program that can be applied while drawing.
17 | type Shader struct {
18 | vertexCode string
19 | fragmentCode string
20 | program gl.Program
21 | uniforms map[string]uniform // uniform location buffer map
22 | texUnitPool map[string]int
23 | activeTexUnits []gl.Texture
24 | }
25 |
26 | // NewShader will create a new shader program. It takes in either paths to glsl
27 | // files or shader code directly
28 | func NewShader(paths ...string) *Shader {
29 | newShader := &Shader{}
30 | code := pathsToCode(paths...)
31 | newShader.vertexCode, newShader.fragmentCode = shaderCodeToGLSL(code...)
32 | registerVolatile(newShader)
33 | return newShader
34 | }
35 |
36 | func (shader *Shader) loadVolatile() bool {
37 | vert := compileCode(gl.VERTEX_SHADER, shader.vertexCode)
38 | frag := compileCode(gl.FRAGMENT_SHADER, shader.fragmentCode)
39 | shader.program = gl.CreateProgram()
40 | shader.texUnitPool = make(map[string]int)
41 | shader.activeTexUnits = make([]gl.Texture, maxTextureUnits)
42 |
43 | gl.AttachShader(shader.program, vert)
44 | gl.AttachShader(shader.program, frag)
45 |
46 | gl.BindAttribLocation(shader.program, shaderPos, "VertexPosition")
47 | gl.BindAttribLocation(shader.program, shaderTexCoord, "VertexTexCoord")
48 | gl.BindAttribLocation(shader.program, shaderColor, "VertexColor")
49 | gl.BindAttribLocation(shader.program, shaderConstantColor, "ConstantColor")
50 |
51 | gl.LinkProgram(shader.program)
52 | gl.DeleteShader(vert)
53 | gl.DeleteShader(frag)
54 |
55 | if gl.GetProgrami(shader.program, gl.LINK_STATUS) == 0 {
56 | gl.DeleteProgram(shader.program)
57 | panic(fmt.Errorf("shader link error: %s", gl.GetProgramInfoLog(shader.program)))
58 | }
59 |
60 | shader.mapUniforms()
61 |
62 | return true
63 | }
64 |
65 | func (shader *Shader) unloadVolatile() {
66 | // active texture list is probably invalid, clear it
67 | gl.DeleteProgram(shader.program)
68 |
69 | // decrement global texture id counters for texture units which had textures bound from this shader
70 | for i := 0; i < len(shader.activeTexUnits); i++ {
71 | if shader.activeTexUnits[i].Valid() {
72 | glState.textureCounters[i] = glState.textureCounters[i] - 1
73 | }
74 | }
75 | }
76 |
77 | func (shader *Shader) mapUniforms() {
78 | // Built-in uniform locations default to -1 (nonexistent.)
79 | shader.uniforms = map[string]uniform{}
80 |
81 | for i := 0; i < gl.GetProgrami(shader.program, gl.ACTIVE_UNIFORMS); i++ {
82 | u := uniform{}
83 |
84 | u.Name, u.Count, u.Type = gl.GetActiveUniform(shader.program, uint32(i))
85 | u.Location = gl.GetUniformLocation(shader.program, u.Name)
86 | u.CalculateTypeInfo()
87 |
88 | // glGetActiveUniform appends "[0]" to the end of array uniform names...
89 | if len(u.Name) > 3 {
90 | if strings.Contains(u.Name, "[0]") {
91 | u.Name = u.Name[:len(u.Name)-3]
92 | }
93 | }
94 |
95 | shader.uniforms[u.Name] = u
96 | }
97 | }
98 |
99 | func (shader *Shader) attach(temporary bool) {
100 | if glState.currentShader != shader {
101 | gl.UseProgram(shader.program)
102 | glState.currentShader = shader
103 | }
104 | if !temporary {
105 | // make sure all sent textures are properly bound to their respective texture units
106 | // note: list potentially contains texture ids of deleted/invalid textures!
107 | for i := 0; i < len(shader.activeTexUnits); i++ {
108 | if shader.activeTexUnits[i].Valid() {
109 | bindTextureToUnit(shader.activeTexUnits[i], i+1, false)
110 | }
111 | }
112 |
113 | // We always want to use texture unit 0 for everyhing else.
114 | setTextureUnit(0)
115 | }
116 | }
117 |
118 | // GetUniformType will return the type and count if it exists and false if it doesn't
119 | func (shader *Shader) GetUniformType(name string) (UniformType, bool) {
120 | u, ok := shader.uniforms[name]
121 | if ok {
122 | return u.BaseType, ok
123 | }
124 | return UniformType(-1), false
125 | }
126 |
127 | func (shader *Shader) getUniformAndCheck(name string, expected UniformType, count int) (uniform, error) {
128 | u, ok := shader.uniforms[name]
129 | if !ok {
130 | return u, fmt.Errorf("no uniform with the name %v", name)
131 | }
132 | if u.BaseType != expected {
133 | return u, errors.New("Invalid type for uniform " + name + ". expected " + translateUniformBaseType(u.BaseType) + " and got " + translateUniformBaseType(expected))
134 | }
135 | if count != u.Count*u.TypeSize {
136 | return u, fmt.Errorf("invalid number of arguments for uniform %v expected %v and got %v", name, (u.Count * u.TypeSize), count)
137 | }
138 | return u, nil
139 | }
140 |
141 | // SendInt allows you to pass in integer values into your shader, by the name of
142 | // the variable
143 | func (shader *Shader) SendInt(name string, values ...int32) error {
144 | shader.attach(true)
145 | defer states.back().shader.attach(false)
146 |
147 | u, err := shader.getUniformAndCheck(name, UniformInt, len(values))
148 | if err != nil {
149 | return err
150 | }
151 |
152 | switch u.TypeSize {
153 | case 4:
154 | gl.Uniform4iv(u.Location, values)
155 | return nil
156 | case 3:
157 | gl.Uniform3iv(u.Location, values)
158 | return nil
159 | case 2:
160 | gl.Uniform2iv(u.Location, values)
161 | return nil
162 | case 1:
163 | gl.Uniform1iv(u.Location, values)
164 | return nil
165 | }
166 | return errors.New("Invalid type size for uniform: " + name)
167 | }
168 |
169 | // SendFloat allows you to pass in float32 values into your shader, by the name of
170 | // the variable
171 | func (shader *Shader) SendFloat(name string, values ...float32) error {
172 | shader.attach(true)
173 | defer states.back().shader.attach(false)
174 |
175 | u, err := shader.getUniformAndCheck(name, UniformFloat, len(values))
176 | if err != nil {
177 | return err
178 | }
179 |
180 | switch u.TypeSize {
181 | case 4:
182 | gl.Uniform4fv(u.Location, values)
183 | return nil
184 | case 3:
185 | gl.Uniform3fv(u.Location, values)
186 | return nil
187 | case 2:
188 | gl.Uniform2fv(u.Location, values)
189 | return nil
190 | case 1:
191 | gl.Uniform1fv(u.Location, values)
192 | return nil
193 | }
194 | return errors.New("Invalid type size for uniform: " + name)
195 | }
196 |
197 | // SendMat4 allows you to pass in a 4x4 matrix value into your shader, by the name of
198 | // the variable
199 | func (shader *Shader) SendMat4(name string, mat mgl32.Mat4) error {
200 | shader.attach(true)
201 | defer states.back().shader.attach(false)
202 |
203 | u, err := shader.getUniformAndCheck(name, UniformFloat, 4)
204 | if err != nil {
205 | return err
206 | }
207 | gl.UniformMatrix4fv(u.Location, []float32{
208 | mat[0], mat[1], mat[2], mat[3],
209 | mat[4], mat[5], mat[6], mat[7],
210 | mat[8], mat[9], mat[10], mat[11],
211 | mat[12], mat[13], mat[14], mat[15],
212 | })
213 | return nil
214 | }
215 |
216 | // SendMat3 allows you to pass in a 3x3 matrix value into your shader, by the name of
217 | // the variable
218 | func (shader *Shader) SendMat3(name string, mat mgl32.Mat3) error {
219 | shader.attach(true)
220 | defer states.back().shader.attach(false)
221 |
222 | u, err := shader.getUniformAndCheck(name, UniformFloat, 3)
223 | if err != nil {
224 | return err
225 | }
226 | gl.UniformMatrix3fv(u.Location, []float32{
227 | mat[0], mat[1], mat[2],
228 | mat[3], mat[4], mat[5],
229 | mat[6], mat[7], mat[8],
230 | })
231 | return nil
232 | }
233 |
234 | // SendMat2 allows you to pass in a 2x2 matrix value into your shader, by the name of
235 | // the variable
236 | func (shader *Shader) SendMat2(name string, mat mgl32.Mat2) error {
237 | shader.attach(true)
238 | defer states.back().shader.attach(false)
239 |
240 | u, err := shader.getUniformAndCheck(name, UniformFloat, 3)
241 | if err != nil {
242 | return err
243 | }
244 | gl.UniformMatrix2fv(u.Location, []float32{
245 | mat[0], mat[1],
246 | mat[2], mat[3],
247 | })
248 | return nil
249 | }
250 |
251 | // SendTexture allows you to pass in a ITexture to your shader as a sampler, by the name of
252 | // the variable. This means you can pass in an image but also a canvas.
253 | func (shader *Shader) SendTexture(name string, texture ITexture) error {
254 | shader.attach(true)
255 | defer states.back().shader.attach(false)
256 |
257 | gltex := texture.getHandle()
258 | texunit := shader.getTextureUnit(name)
259 |
260 | u, err := shader.getUniformAndCheck(name, UniformSampler, 1)
261 | if err != nil {
262 | return err
263 | }
264 |
265 | bindTextureToUnit(gltex, texunit, true)
266 |
267 | gl.Uniform1i(u.Location, int(texunit))
268 |
269 | // increment global shader texture id counter for this texture unit, if we haven't already
270 | if !shader.activeTexUnits[texunit-1].Valid() {
271 | glState.textureCounters[texunit-1]++
272 | }
273 |
274 | // store texture id so it can be re-bound to the proper texture unit later
275 | shader.activeTexUnits[texunit-1] = gltex
276 |
277 | return nil
278 | }
279 |
280 | func (shader *Shader) getTextureUnit(name string) int {
281 | unit, found := shader.texUnitPool[name]
282 | if found {
283 | return unit
284 | }
285 |
286 | texunit := -1
287 | // prefer texture units which are unused by all other shaders
288 | for i := 0; i < len(glState.textureCounters); i++ {
289 | if glState.textureCounters[i] == 0 {
290 | texunit = i + 1
291 | break
292 | }
293 | }
294 |
295 | if texunit == -1 {
296 | // no completely unused texture units exist, try to use next free slot in our own list
297 | for i := 0; i < len(shader.activeTexUnits); i++ {
298 | if !shader.activeTexUnits[i].Valid() {
299 | texunit = i + 1
300 | break
301 | }
302 | }
303 |
304 | if texunit == -1 {
305 | panic("No more texture units available for shader.")
306 | }
307 | }
308 |
309 | shader.texUnitPool[name] = texunit
310 | return shader.texUnitPool[name]
311 | }
312 |
313 | func createCode(header, code, footer string) string {
314 | var templateWriter bytes.Buffer
315 | if err := shaderTemplate.Execute(&templateWriter, struct {
316 | Header, Code, Footer string
317 | }{Header: header, Code: code, Footer: footer}); err != nil {
318 | panic(err)
319 | }
320 | return templateWriter.String()
321 | }
322 |
323 | func isVertexCode(code string) bool {
324 | match, _ := regexp.MatchString(`vec4\s+position\s*\(`, code)
325 | return match
326 | }
327 |
328 | func isFragmentCode(code string) bool {
329 | match, _ := regexp.MatchString(`vec4\s+effect\s*\(`, code)
330 | return match
331 | }
332 |
333 | //convert paths to strings of code
334 | //if string is already code just pass it along
335 | func pathsToCode(paths ...string) []string {
336 | code := []string{}
337 | if paths != nil {
338 | for _, path := range paths {
339 | if path == "" {
340 | continue
341 | }
342 | //if this is not code it must be a path
343 | if !isVertexCode(path) && !isFragmentCode(path) {
344 | code = append(code, file.ReadString(path))
345 | } else { //it is code!
346 | code = append(code, path)
347 | }
348 | }
349 | }
350 | return code
351 | }
352 |
353 | func shaderCodeToGLSL(code ...string) (string, string) {
354 | vertexcode := defaultVertexShaderCode
355 | fragmentCode := defaultFragmentShaderCode
356 | for _, shaderCode := range code {
357 | if isVertexCode(shaderCode) {
358 | vertexcode = shaderCode
359 | }
360 | if isFragmentCode(shaderCode) {
361 | fragmentCode = shaderCode
362 | }
363 | }
364 | return createCode(vertexHeader, vertexcode, vertexFooter), createCode(fragmentHeader, fragmentCode, fragmentFooter)
365 | }
366 |
367 | func compileCode(shaderType gl.Enum, src string) gl.Shader {
368 | shader := gl.CreateShader(shaderType)
369 | if !shader.Valid() {
370 | panic(fmt.Errorf("could not create shader (type %v)", shaderType))
371 | }
372 | gl.ShaderSource(shader, src)
373 | gl.CompileShader(shader)
374 | if gl.GetShaderi(shader, gl.COMPILE_STATUS) == 0 {
375 | defer gl.DeleteShader(shader)
376 | panic(fmt.Errorf("shader compile: %s", gl.GetShaderInfoLog(shader)))
377 | }
378 | return shader
379 | }
380 |
--------------------------------------------------------------------------------
/audio/openal/al/al.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin linux
6 |
7 | // Package al provides OpenAL Soft bindings for Go.
8 | //
9 | // Calls are not safe for concurrent use.
10 | //
11 | // More information about OpenAL Soft is available at
12 | // http://www.openal.org/documentation/openal-1.1-specification.pdf.
13 | //
14 | // In order to use this package on Linux desktop distros,
15 | // you will need OpenAL library as an external dependency.
16 | // On Ubuntu 14.04 'Trusty', you may have to install this library
17 | // by running the command below.
18 | //
19 | // sudo apt-get install libopenal-dev
20 | //
21 | // When compiled for Android, this package uses OpenAL Soft. Please add its
22 | // license file to the open source notices of your application.
23 | // OpenAL Soft's license file could be found at
24 | // http://repo.or.cz/w/openal-soft.git/blob/HEAD:/COPYING.
25 | //
26 | // Vendored and extended from github.com/golang/mobile for extending functionality
27 | package al
28 |
29 | // Capability represents OpenAL extension capabilities.
30 | type Capability int32
31 |
32 | // Enable enables a capability.
33 | func Enable(c Capability) {
34 | alEnable(int32(c))
35 | }
36 |
37 | // Disable disables a capability.
38 | func Disable(c Capability) {
39 | alDisable(int32(c))
40 | }
41 |
42 | // Enabled returns true if the specified capability is enabled.
43 | func Enabled(c Capability) bool {
44 | return alIsEnabled(int32(c))
45 | }
46 |
47 | // Vector represents an vector in a Cartesian coordinate system.
48 | type Vector [3]float32
49 |
50 | // Cone is the audio cone
51 | type Cone struct {
52 | InnerAngle int32
53 | OuterAngle int32
54 | OuterVolume float32
55 | }
56 |
57 | // Orientation represents the angular position of an object in a
58 | // right-handed Cartesian coordinate system.
59 | // A cross product between the forward and up vector returns a vector
60 | // that points to the right.
61 | type Orientation struct {
62 | // Forward vector is the direction that the object is looking at.
63 | Forward Vector
64 | // Up vector represents the rotation of the object.
65 | Up Vector
66 | }
67 |
68 | func orientationFromSlice(v []float32) Orientation {
69 | return Orientation{
70 | Forward: Vector{v[0], v[1], v[2]},
71 | Up: Vector{v[3], v[4], v[5]},
72 | }
73 | }
74 |
75 | func (v Orientation) slice() []float32 {
76 | return []float32{v.Forward[0], v.Forward[1], v.Forward[2], v.Up[0], v.Up[1], v.Up[2]}
77 | }
78 |
79 | func geti(param int) int32 {
80 | return alGetInteger(param)
81 | }
82 |
83 | func getf(param int) float32 {
84 | return alGetFloat(param)
85 | }
86 |
87 | func getString(param int) string {
88 | return alGetString(param)
89 | }
90 |
91 | // DistanceModel returns the distance model.
92 | func DistanceModel() int32 {
93 | return geti(paramDistanceModel)
94 | }
95 |
96 | // SetDistanceModel sets the distance model.
97 | func SetDistanceModel(v int32) {
98 | alDistanceModel(v)
99 | }
100 |
101 | // DopplerFactor returns the doppler factor.
102 | func DopplerFactor() float32 {
103 | return getf(paramDopplerFactor)
104 | }
105 |
106 | // SetDopplerFactor sets the doppler factor.
107 | func SetDopplerFactor(v float32) {
108 | alDopplerFactor(v)
109 | }
110 |
111 | // DopplerVelocity returns the doppler velocity.
112 | func DopplerVelocity() float32 {
113 | return getf(paramDopplerVelocity)
114 | }
115 |
116 | // SetDopplerVelocity sets the doppler velocity.
117 | func SetDopplerVelocity(v float32) {
118 | alDopplerVelocity(v)
119 | }
120 |
121 | // SpeedOfSound is the speed of sound in meters per second (m/s).
122 | func SpeedOfSound() float32 {
123 | return getf(paramSpeedOfSound)
124 | }
125 |
126 | // SetSpeedOfSound sets the speed of sound, its unit should be meters per second (m/s).
127 | func SetSpeedOfSound(v float32) {
128 | alSpeedOfSound(v)
129 | }
130 |
131 | // Vendor returns the vendor.
132 | func Vendor() string {
133 | return getString(paramVendor)
134 | }
135 |
136 | // Version returns the version string.
137 | func Version() string {
138 | return getString(paramVersion)
139 | }
140 |
141 | // Renderer returns the renderer information.
142 | func Renderer() string {
143 | return getString(paramRenderer)
144 | }
145 |
146 | // Extensions returns the enabled extensions.
147 | func Extensions() string {
148 | return getString(paramExtensions)
149 | }
150 |
151 | // Error returns the most recently generated error.
152 | func Error() int32 {
153 | return alGetError()
154 | }
155 |
156 | // Source represents an individual sound source in 3D-space.
157 | // They take PCM data, apply modifications and then submit them to
158 | // be mixed according to their spatial location.
159 | type Source uint32
160 |
161 | // GenSources generates n new sources. These sources should be deleted
162 | // once they are not in use.
163 | func GenSources(n int) []Source {
164 | return alGenSources(n)
165 | }
166 |
167 | // PlaySources plays the sources.
168 | func PlaySources(source ...Source) {
169 | alSourcePlayv(source)
170 | }
171 |
172 | // PauseSources pauses the sources.
173 | func PauseSources(source ...Source) {
174 | alSourcePausev(source)
175 | }
176 |
177 | // StopSources stops the sources.
178 | func StopSources(source ...Source) {
179 | alSourceStopv(source)
180 | }
181 |
182 | // RewindSources rewinds the sources to their beginning positions.
183 | func RewindSources(source ...Source) {
184 | alSourceRewindv(source)
185 | }
186 |
187 | // DeleteSources deletes the sources.
188 | func DeleteSources(source ...Source) {
189 | alDeleteSources(source)
190 | }
191 |
192 | // Gain returns the source gain.
193 | func (s Source) Gain() float32 {
194 | return getSourcef(s, paramGain)
195 | }
196 |
197 | // SetGain sets the source gain.
198 | func (s Source) SetGain(v float32) {
199 | setSourcef(s, paramGain, v)
200 | }
201 |
202 | // Pitch todo
203 | func (s Source) Pitch() float32 {
204 | return getSourcef(s, paramPitch)
205 | }
206 |
207 | // SetPitch todo
208 | func (s Source) SetPitch(p float32) {
209 | setSourcef(s, paramPitch, p)
210 | }
211 |
212 | // Rolloff todo
213 | func (s Source) Rolloff() float32 {
214 | return getSourcef(s, paramRolloffFactor)
215 | }
216 |
217 | // SetRolloff todo
218 | func (s Source) SetRolloff(rolloff float32) {
219 | setSourcef(s, paramRolloffFactor, rolloff)
220 | }
221 |
222 | // ReferenceDistance todo
223 | func (s Source) ReferenceDistance() float32 {
224 | return getSourcef(s, paramReferenceDistance)
225 | }
226 |
227 | // SetReferenceDistance todo
228 | func (s Source) SetReferenceDistance(dis float32) {
229 | setSourcef(s, paramReferenceDistance, dis)
230 | }
231 |
232 | // MaxDistance todo
233 | func (s Source) MaxDistance() float32 {
234 | return getSourcef(s, paramMaxDistance)
235 | }
236 |
237 | // SetMaxDistance todo
238 | func (s Source) SetMaxDistance(dis float32) {
239 | setSourcef(s, paramMaxDistance, dis)
240 | }
241 |
242 | // Looping returns the source looping.
243 | func (s Source) Looping() bool {
244 | return getSourcei(s, paramLooping) == 1
245 | }
246 |
247 | // SetLooping sets the source looping
248 | func (s Source) SetLooping(shouldloop bool) {
249 | if shouldloop {
250 | setSourcei(s, paramLooping, 1)
251 | } else {
252 | setSourcei(s, paramLooping, 0)
253 | }
254 | }
255 |
256 | // Relative todo
257 | func (s Source) Relative() bool {
258 | return getSourcei(s, paramSourceRelative) == 1
259 | }
260 |
261 | // SetRelative todo
262 | func (s Source) SetRelative(isRelative bool) {
263 | if isRelative {
264 | setSourcei(s, paramSourceRelative, 1)
265 | } else {
266 | setSourcei(s, paramSourceRelative, 0)
267 | }
268 | }
269 |
270 | // MinGain returns the source's minimum gain setting.
271 | func (s Source) MinGain() float32 {
272 | return getSourcef(s, paramMinGain)
273 | }
274 |
275 | // SetMinGain sets the source's minimum gain setting.
276 | func (s Source) SetMinGain(v float32) {
277 | setSourcef(s, paramMinGain, v)
278 | }
279 |
280 | // MaxGain returns the source's maximum gain setting.
281 | func (s Source) MaxGain() float32 {
282 | return getSourcef(s, paramMaxGain)
283 | }
284 |
285 | // SetMaxGain sets the source's maximum gain setting.
286 | func (s Source) SetMaxGain(v float32) {
287 | setSourcef(s, paramMaxGain, v)
288 | }
289 |
290 | // Position returns the position of the source.
291 | func (s Source) Position() Vector {
292 | v := Vector{}
293 | getSourcefv(s, paramPosition, v[:])
294 | return v
295 | }
296 |
297 | // SetPosition sets the position of the source.
298 | func (s Source) SetPosition(v Vector) {
299 | setSourcefv(s, paramPosition, v[:])
300 | }
301 |
302 | // Direction returns the direction of the source.
303 | func (s Source) Direction() Vector {
304 | v := Vector{}
305 | getSourcefv(s, paramDirection, v[:])
306 | return v
307 | }
308 |
309 | // SetDirection sets the direction of the source.
310 | func (s Source) SetDirection(v Vector) {
311 | setSourcefv(s, paramDirection, v[:])
312 | }
313 |
314 | // Cone returns the audio cone
315 | func (s Source) Cone() Cone {
316 | return Cone{
317 | InnerAngle: getSourcei(s, paramConeInnerAngle),
318 | OuterAngle: getSourcei(s, paramConeOuterAngle),
319 | OuterVolume: getSourcef(s, paramConeOuterGain),
320 | }
321 | }
322 |
323 | // SetCone sets the audio cone
324 | func (s Source) SetCone(c Cone) {
325 | setSourcei(s, paramConeInnerAngle, c.InnerAngle)
326 | setSourcei(s, paramConeOuterAngle, c.OuterAngle)
327 | setSourcef(s, paramConeOuterGain, c.OuterVolume)
328 | }
329 |
330 | // Velocity returns the source's velocity.
331 | func (s Source) Velocity() Vector {
332 | v := Vector{}
333 | getSourcefv(s, paramVelocity, v[:])
334 | return v
335 | }
336 |
337 | // SetVelocity sets the source's velocity.
338 | func (s Source) SetVelocity(v Vector) {
339 | setSourcefv(s, paramVelocity, v[:])
340 | }
341 |
342 | // Orientation returns the orientation of the source.
343 | func (s Source) Orientation() Orientation {
344 | v := make([]float32, 6)
345 | getSourcefv(s, paramOrientation, v)
346 | return orientationFromSlice(v)
347 | }
348 |
349 | // SetOrientation sets the orientation of the source.
350 | func (s Source) SetOrientation(o Orientation) {
351 | setSourcefv(s, paramOrientation, o.slice())
352 | }
353 |
354 | // State returns the playing state of the source.
355 | func (s Source) State() int32 {
356 | return getSourcei(s, paramSourceState)
357 | }
358 |
359 | // SetBuffer returns the number of the queued buffers.
360 | func (s Source) SetBuffer(b Buffer) {
361 | setSourcei(s, paramBuffer, int32(b))
362 | }
363 |
364 | // Buffer returns the number of the queued buffers.
365 | func (s Source) Buffer() Buffer {
366 | return Buffer(getSourcei(s, paramBuffer))
367 | }
368 |
369 | // ClearBuffers returns the number of the queued buffers.
370 | func (s Source) ClearBuffers() {
371 | setSourcei(s, paramBuffer, 0)
372 | }
373 |
374 | // BuffersQueued returns the number of the queued buffers.
375 | func (s Source) BuffersQueued() int32 {
376 | return getSourcei(s, paramBuffersQueued)
377 | }
378 |
379 | // BuffersProcessed returns the number of the processed buffers.
380 | func (s Source) BuffersProcessed() int32 {
381 | return getSourcei(s, paramBuffersProcessed)
382 | }
383 |
384 | // OffsetSeconds returns the current playback position of the source in seconds.
385 | func (s Source) OffsetSeconds() float32 {
386 | return getSourcef(s, paramSecOffset)
387 | }
388 |
389 | // SetOffsetSeconds returns the current playback position of the source in seconds.
390 | func (s Source) SetOffsetSeconds(seconds float32) {
391 | setSourcef(s, paramSecOffset, seconds)
392 | }
393 |
394 | // OffsetSample returns the sample offset of the current playback position.
395 | func (s Source) OffsetSample() float32 {
396 | return getSourcef(s, paramSampleOffset)
397 | }
398 |
399 | // SetOffsetSample returns the sample offset of the current playback position.
400 | func (s Source) SetOffsetSample(samples float32) {
401 | setSourcef(s, paramSampleOffset, samples)
402 | }
403 |
404 | // OffsetByte returns the byte offset of the current playback position.
405 | func (s Source) OffsetByte() int32 {
406 | return getSourcei(s, paramByteOffset)
407 | }
408 |
409 | // SetOffsetBytes returns the sample offset of the current playback position.
410 | func (s Source) SetOffsetBytes(bytes int32) {
411 | setSourcei(s, paramByteOffset, bytes)
412 | }
413 |
414 | func getSourcei(s Source, param int) int32 {
415 | return alGetSourcei(s, param)
416 | }
417 |
418 | func getSourcef(s Source, param int) float32 {
419 | return alGetSourcef(s, param)
420 | }
421 |
422 | func getSourcefv(s Source, param int, v []float32) {
423 | alGetSourcefv(s, param, v)
424 | }
425 |
426 | func setSourcei(s Source, param int, v int32) {
427 | alSourcei(s, param, v)
428 | }
429 |
430 | func setSourcef(s Source, param int, v float32) {
431 | alSourcef(s, param, v)
432 | }
433 |
434 | func setSourcefv(s Source, param int, v []float32) {
435 | alSourcefv(s, param, v)
436 | }
437 |
438 | // QueueBuffers adds the buffers to the buffer queue.
439 | func (s Source) QueueBuffers(buffer ...Buffer) {
440 | alSourceQueueBuffers(s, buffer)
441 | }
442 |
443 | // UnqueueBuffer removes the specified buffers from the buffer queue.
444 | func (s Source) UnqueueBuffer() Buffer {
445 | buffers := make([]Buffer, 1)
446 | alSourceUnqueueBuffers(s, buffers)
447 | return buffers[0]
448 | }
449 |
450 | // ListenerGain returns the total gain applied to the final mix.
451 | func ListenerGain() float32 {
452 | return getListenerf(paramGain)
453 | }
454 |
455 | // ListenerPosition returns the position of the listener.
456 | func ListenerPosition() Vector {
457 | v := Vector{}
458 | getListenerfv(paramPosition, v[:])
459 | return v
460 | }
461 |
462 | // ListenerVelocity returns the velocity of the listener.
463 | func ListenerVelocity() Vector {
464 | v := Vector{}
465 | getListenerfv(paramVelocity, v[:])
466 | return v
467 | }
468 |
469 | // ListenerOrientation returns the orientation of the listener.
470 | func ListenerOrientation() Orientation {
471 | v := make([]float32, 6)
472 | getListenerfv(paramOrientation, v)
473 | return orientationFromSlice(v)
474 | }
475 |
476 | // SetListenerGain sets the total gain that will be applied to the final mix.
477 | func SetListenerGain(v float32) {
478 | setListenerf(paramGain, v)
479 | }
480 |
481 | // SetListenerPosition sets the position of the listener.
482 | func SetListenerPosition(v Vector) {
483 | setListenerfv(paramPosition, v[:])
484 | }
485 |
486 | // SetListenerVelocity sets the velocity of the listener.
487 | func SetListenerVelocity(v Vector) {
488 | setListenerfv(paramVelocity, v[:])
489 | }
490 |
491 | // SetListenerOrientation sets the orientation of the listener.
492 | func SetListenerOrientation(v Orientation) {
493 | setListenerfv(paramOrientation, v.slice())
494 | }
495 |
496 | func getListenerf(param int) float32 {
497 | return alGetListenerf(param)
498 | }
499 |
500 | func getListenerfv(param int, v []float32) {
501 | alGetListenerfv(param, v)
502 | }
503 |
504 | func setListenerf(param int, v float32) {
505 | alListenerf(param, v)
506 | }
507 |
508 | func setListenerfv(param int, v []float32) {
509 | alListenerfv(param, v)
510 | }
511 |
512 | // Buffer represents a chunk of PCM audio data that could be buffered to an audio
513 | // source. A single buffer could be shared between multiple sources.
514 | type Buffer uint32
515 |
516 | // GenBuffers generates n new buffers. The generated buffers should be deleted
517 | // once they are no longer in use.
518 | func GenBuffers(n int) []Buffer {
519 | return alGenBuffers(n)
520 | }
521 |
522 | // DeleteBuffers deletes the buffers.
523 | func DeleteBuffers(buffer ...Buffer) {
524 | alDeleteBuffers(buffer)
525 | }
526 |
527 | func getBufferi(b Buffer, param int) int32 {
528 | return alGetBufferi(b, param)
529 | }
530 |
531 | // Frequency returns the frequency of the buffer data in Hertz (Hz).
532 | func (b Buffer) Frequency() int32 {
533 | return getBufferi(b, paramFreq)
534 | }
535 |
536 | // Bits return the number of bits used to represent a sample.
537 | func (b Buffer) Bits() int32 {
538 | return getBufferi(b, paramBits)
539 | }
540 |
541 | // Channels return the number of the audio channels.
542 | func (b Buffer) Channels() int32 {
543 | return getBufferi(b, paramChannels)
544 | }
545 |
546 | // Size returns the size of the data.
547 | func (b Buffer) Size() int32 {
548 | return getBufferi(b, paramSize)
549 | }
550 |
551 | // BufferData buffers PCM data to the current buffer.
552 | func (b Buffer) BufferData(format uint32, data []byte, freq int32) {
553 | alBufferData(b, format, data, freq)
554 | }
555 |
556 | // Valid returns true if the buffer exists and is valid.
557 | func (b Buffer) Valid() bool {
558 | return alIsBuffer(b)
559 | }
560 |
--------------------------------------------------------------------------------