Merge branch 'master' into audio
@ -4,12 +4,14 @@ go:
|
|||||||
- 1.4
|
- 1.4
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- "export DISPLAY=:99.0"
|
- export DISPLAY=:99.0
|
||||||
- "sh -e /etc/init.d/xvfb start"
|
- sh -e /etc/init.d/xvfb start
|
||||||
- sudo add-apt-repository 'deb http://us.archive.ubuntu.com/ubuntu/ utopic main restricted universe multiverse'
|
- sudo add-apt-repository 'deb http://us.archive.ubuntu.com/ubuntu/ utopic main restricted universe multiverse'
|
||||||
- sudo add-apt-repository 'deb http://us.archive.ubuntu.com/ubuntu/ utopic-updates main restricted universe multiverse'
|
- sudo add-apt-repository 'deb http://us.archive.ubuntu.com/ubuntu/ utopic-updates main restricted universe multiverse'
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -qq libglew-dev libglfw3-dev
|
- sudo apt-get install -qq libglew-dev libglfw3-dev
|
||||||
|
- export NODE_PATH=$(npm config get prefix)/lib/node_modules
|
||||||
|
- npm install --global gl
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- go get -t -v ./...
|
- go get -t -v ./...
|
||||||
@ -18,6 +20,7 @@ install:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
|
- gopherjs test -v github.com/hajimehoshi/ebiten github.com/hajimehoshi/ebiten/internal
|
||||||
- gopherjs build -v github.com/hajimehoshi/ebiten/example/blocks
|
- gopherjs build -v github.com/hajimehoshi/ebiten/example/blocks
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
|
18
_docs/gen.go
@ -151,23 +151,29 @@ func clear() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if m, _ := regexp.MatchString("~$", path); m {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
// Remove auto-generated html files.
|
// Remove auto-generated html files.
|
||||||
m, err := regexp.MatchString(".html$", path)
|
m, err := regexp.MatchString(".html$", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !m {
|
if m {
|
||||||
return nil
|
return os.Remove(path)
|
||||||
}
|
}
|
||||||
// Remove example resources that are copied.
|
// Remove example resources that are copied.
|
||||||
m, err = regexp.MatchString("public/example/images", path)
|
m, err = regexp.MatchString("^public/example/images$", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !m {
|
if m {
|
||||||
return nil
|
if err := os.RemoveAll(path); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return os.Remove(path)
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ pre {
|
|||||||
<h2>Features</h2>
|
<h2>Features</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>2D Graphics</li>
|
<li>2D Graphics</li>
|
||||||
<li>Input (Mouse, Keyboard)</li>
|
<li>Input (Mouse, Keyboard, Gamepad)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>Example</h2>
|
<h2>Example</h2>
|
||||||
@ -61,7 +61,7 @@ pre {
|
|||||||
:; go get github.com/gopherjs/webgl</code></pre>
|
:; go get github.com/gopherjs/webgl</code></pre>
|
||||||
|
|
||||||
<h2>Execute the example</h2>
|
<h2>Execute the example</h2>
|
||||||
<pre><code>:; cd $GOHOME/src/github.com/hajimehoshi/ebiten/example
|
<pre><code>:; cd $GOPATH/src/github.com/hajimehoshi/ebiten/example
|
||||||
:; go run rotate/main.go</code></pre>
|
:; go run rotate/main.go</code></pre>
|
||||||
|
|
||||||
<h2>Run your game on a desktop</h2>
|
<h2>Run your game on a desktop</h2>
|
||||||
@ -76,6 +76,35 @@ pre {
|
|||||||
<p>NOTE: <code>file://</code> URL may not work with Ebiten. Execute your game on a HTTP server.</p>
|
<p>NOTE: <code>file://</code> URL may not work with Ebiten. Execute your game on a HTTP server.</p>
|
||||||
|
|
||||||
<h2>Change Log</h2>
|
<h2>Change Log</h2>
|
||||||
|
<h3>2015-??-??</h3>
|
||||||
|
<ul>
|
||||||
|
<li>v1.2.0-rc1 released.
|
||||||
|
<ul>
|
||||||
|
<li>Support for gamepads</li>
|
||||||
|
<li>Support for touch events</li>
|
||||||
|
<li>Added new functions for image rendering:
|
||||||
|
<ul>
|
||||||
|
<li>Image.DrawFilledRect</li>
|
||||||
|
<li>Image.DrawFilledRects</li>
|
||||||
|
<li>Image.DrawLine</li>
|
||||||
|
<li>Image.DrawLines</li>
|
||||||
|
<li>Image.DrawRect</li>
|
||||||
|
<li>Image.DrawRects</li>
|
||||||
|
<li>Image.ReplacePixels</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Some bug fix</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>2015-??-??</h3>
|
||||||
|
<ul>
|
||||||
|
<li>v1.1.0 released.
|
||||||
|
<ul>
|
||||||
|
<li>Some bug fix</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<h3>2015-01-10</h3>
|
<h3>2015-01-10</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>v1.1.0-rc1 released.
|
<li>v1.1.0-rc1 released.
|
||||||
|
Before Width: | Height: | Size: 1008 B After Width: | Height: | Size: 1008 B |
Before Width: | Height: | Size: 2.1 KiB |
BIN
_docs/public/example/images/keyboard/keyboard.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
@ -1,6 +1,6 @@
|
|||||||
# License
|
# License
|
||||||
|
|
||||||
## blocks/font.png
|
## arcadefont.png
|
||||||
|
|
||||||
```
|
```
|
||||||
9031 Font ReadMe
|
9031 Font ReadMe
|
||||||
|
@ -41,10 +41,9 @@ pre {
|
|||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
"github.com/hajimehoshi/ebiten/example/keyboard/keyboard"
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -52,13 +51,23 @@ const (
|
|||||||
screenHeight = 240
|
screenHeight = 240
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var keyboardImage *ebiten.Image
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
keyboardImage, _, err = ebitenutil.NewImageFromFile("images/keyboard/keyboard.png", ebiten.FilterNearest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var keyNames = map[ebiten.Key]string{
|
var keyNames = map[ebiten.Key]string{
|
||||||
ebiten.KeyBackspace: "Backspace",
|
ebiten.KeyBackspace: "BS",
|
||||||
ebiten.KeyComma: "','",
|
ebiten.KeyComma: ",",
|
||||||
ebiten.KeyDelete: "Delete",
|
ebiten.KeyDelete: "Del",
|
||||||
ebiten.KeyEnter: "Enter",
|
ebiten.KeyEnter: "Enter",
|
||||||
ebiten.KeyEscape: "Esc",
|
ebiten.KeyEscape: "Esc",
|
||||||
ebiten.KeyPeriod: "'.'",
|
ebiten.KeyPeriod: ".",
|
||||||
ebiten.KeySpace: "Space",
|
ebiten.KeySpace: "Space",
|
||||||
ebiten.KeyTab: "Tab",
|
ebiten.KeyTab: "Tab",
|
||||||
|
|
||||||
@ -74,7 +83,32 @@ var keyNames = map[ebiten.Key]string{
|
|||||||
ebiten.KeyAlt: "Alt",
|
ebiten.KeyAlt: "Alt",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pressedKeysParts []string
|
||||||
|
|
||||||
|
func (p pressedKeysParts) Len() int {
|
||||||
|
return len(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pressedKeysParts) Dst(i int) (x0, y0, x1, y1 int) {
|
||||||
|
k := p[i]
|
||||||
|
r, ok := keyboard.KeyRect(k)
|
||||||
|
if !ok {
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
return r.Min.X, r.Min.Y, r.Max.X, r.Max.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pressedKeysParts) Src(i int) (x0, y0, x1, y1 int) {
|
||||||
|
return p.Dst(i)
|
||||||
|
}
|
||||||
|
|
||||||
func update(screen *ebiten.Image) error {
|
func update(screen *ebiten.Image) error {
|
||||||
|
const offsetX, offsetY = 24, 40
|
||||||
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
op.GeoM.Translate(offsetX, offsetY)
|
||||||
|
op.ColorM.Scale(0.5, 0.5, 0.5, 1)
|
||||||
|
screen.DrawImage(keyboardImage, op)
|
||||||
|
|
||||||
pressed := []string{}
|
pressed := []string{}
|
||||||
for i := 0; i <= 9; i++ {
|
for i := 0; i <= 9; i++ {
|
||||||
if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.Key0) {
|
if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.Key0) {
|
||||||
@ -96,9 +130,13 @@ func update(screen *ebiten.Image) error {
|
|||||||
pressed = append(pressed, name)
|
pressed = append(pressed, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(pressed)
|
|
||||||
str := "Pressed Keys: " + strings.Join(pressed, ", ")
|
op = &ebiten.DrawImageOptions{
|
||||||
ebitenutil.DebugPrint(screen, str)
|
ImageParts: pressedKeysParts(pressed),
|
||||||
|
}
|
||||||
|
op.GeoM.Translate(offsetX, offsetY)
|
||||||
|
screen.DrawImage(keyboardImage, op)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ pre {
|
|||||||
<h2>Features</h2>
|
<h2>Features</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>2D Graphics</li>
|
<li>2D Graphics</li>
|
||||||
<li>Input (Mouse, Keyboard)</li>
|
<li>Input (Mouse, Keyboard, Gamepad)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>Example</h2>
|
<h2>Example</h2>
|
||||||
@ -87,7 +87,7 @@ pre {
|
|||||||
:; go get github.com/gopherjs/webgl</code></pre>
|
:; go get github.com/gopherjs/webgl</code></pre>
|
||||||
|
|
||||||
<h2>Execute the example</h2>
|
<h2>Execute the example</h2>
|
||||||
<pre><code>:; cd $GOHOME/src/github.com/hajimehoshi/ebiten/example
|
<pre><code>:; cd $GOPATH/src/github.com/hajimehoshi/ebiten/example
|
||||||
:; go run rotate/main.go</code></pre>
|
:; go run rotate/main.go</code></pre>
|
||||||
|
|
||||||
<h2>Run your game on a desktop</h2>
|
<h2>Run your game on a desktop</h2>
|
||||||
@ -102,6 +102,35 @@ pre {
|
|||||||
<p>NOTE: <code>file://</code> URL may not work with Ebiten. Execute your game on a HTTP server.</p>
|
<p>NOTE: <code>file://</code> URL may not work with Ebiten. Execute your game on a HTTP server.</p>
|
||||||
|
|
||||||
<h2>Change Log</h2>
|
<h2>Change Log</h2>
|
||||||
|
<h3>2015-??-??</h3>
|
||||||
|
<ul>
|
||||||
|
<li>v1.2.0-rc1 released.
|
||||||
|
<ul>
|
||||||
|
<li>Support for gamepads</li>
|
||||||
|
<li>Support for touch events</li>
|
||||||
|
<li>Added new functions for image rendering:
|
||||||
|
<ul>
|
||||||
|
<li>Image.DrawFilledRect</li>
|
||||||
|
<li>Image.DrawFilledRects</li>
|
||||||
|
<li>Image.DrawLine</li>
|
||||||
|
<li>Image.DrawLines</li>
|
||||||
|
<li>Image.DrawRect</li>
|
||||||
|
<li>Image.DrawRects</li>
|
||||||
|
<li>Image.ReplacePixels</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Some bug fix</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>2015-??-??</h3>
|
||||||
|
<ul>
|
||||||
|
<li>v1.1.0 released.
|
||||||
|
<ul>
|
||||||
|
<li>Some bug fix</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<h3>2015-01-10</h3>
|
<h3>2015-01-10</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>v1.1.0-rc1 released.
|
<li>v1.1.0-rc1 released.
|
||||||
|
@ -23,6 +23,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewImageFromFile loads the file path and returns ebiten.Image and image.Image.
|
// NewImageFromFile loads the file path and returns ebiten.Image and image.Image.
|
||||||
|
//
|
||||||
|
// The current directory for path depends on your environment. This will vary on your desktop or web browser.
|
||||||
|
// It'll be safer to embed your resource, e.g., with github.com/jteeuwen/go-bindata instead of using this function.
|
||||||
func NewImageFromFile(path string, filter ebiten.Filter) (*ebiten.Image, image.Image, error) {
|
func NewImageFromFile(path string, filter ebiten.Filter) (*ebiten.Image, image.Image, error) {
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -16,98 +16,18 @@ package blocks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
"github.com/hajimehoshi/ebiten/example/internal"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var imageFont *ebiten.Image
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
imageFont, _, err = ebitenutil.NewImageFromFile("images/blocks/font.png", ebiten.FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const charWidth = 8
|
|
||||||
const charHeight = 8
|
|
||||||
|
|
||||||
func textWidth(str string) int {
|
|
||||||
// TODO: Take care about '\n'
|
|
||||||
return charWidth * len(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
type fontImageParts string
|
|
||||||
|
|
||||||
func (f fontImageParts) Len() int {
|
|
||||||
return len(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fontImageParts) Dst(i int) (x0, y0, x1, y1 int) {
|
|
||||||
x := i - strings.LastIndex(string(f)[:i], "\n") - 1
|
|
||||||
y := strings.Count(string(f)[:i], "\n")
|
|
||||||
x *= charWidth
|
|
||||||
y *= charHeight
|
|
||||||
if x < 0 {
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
return x, y, x + charWidth, y + charHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fontImageParts) Src(i int) (x0, y0, x1, y1 int) {
|
|
||||||
code := int(f[i])
|
|
||||||
if code == '\n' {
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
x := (code % 16) * charWidth
|
|
||||||
y := ((code - 32) / 16) * charHeight
|
|
||||||
return x, y, x + charWidth, y + charHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawText(rt *ebiten.Image, str string, ox, oy, scale int, c color.Color) error {
|
|
||||||
options := &ebiten.DrawImageOptions{
|
|
||||||
ImageParts: fontImageParts(str),
|
|
||||||
}
|
|
||||||
options.GeoM.Scale(float64(scale), float64(scale))
|
|
||||||
options.GeoM.Translate(float64(ox), float64(oy))
|
|
||||||
|
|
||||||
ur, ug, ub, ua := c.RGBA()
|
|
||||||
const max = math.MaxUint16
|
|
||||||
r := float64(ur) / max
|
|
||||||
g := float64(ug) / max
|
|
||||||
b := float64(ub) / max
|
|
||||||
a := float64(ua) / max
|
|
||||||
if 0 < a {
|
|
||||||
r /= a
|
|
||||||
g /= a
|
|
||||||
b /= a
|
|
||||||
}
|
|
||||||
options.ColorM.Scale(r, g, b, a)
|
|
||||||
|
|
||||||
return rt.DrawImage(imageFont, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawTextWithShadow(rt *ebiten.Image, str string, x, y, scale int, clr color.Color) error {
|
|
||||||
if err := drawText(rt, str, x+1, y+1, scale, color.NRGBA{0, 0, 0, 0x80}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := drawText(rt, str, x, y, scale, clr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawTextWithShadowCenter(rt *ebiten.Image, str string, x, y, scale int, clr color.Color, width int) error {
|
func drawTextWithShadowCenter(rt *ebiten.Image, str string, x, y, scale int, clr color.Color, width int) error {
|
||||||
w := textWidth(str) * scale
|
w := internal.ArcadeFont.TextWidth(str) * scale
|
||||||
x += (width - w) / 2
|
x += (width - w) / 2
|
||||||
return drawTextWithShadow(rt, str, x, y, scale, clr)
|
return internal.ArcadeFont.DrawTextWithShadow(rt, str, x, y, scale, clr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawTextWithShadowRight(rt *ebiten.Image, str string, x, y, scale int, clr color.Color, width int) error {
|
func drawTextWithShadowRight(rt *ebiten.Image, str string, x, y, scale int, clr color.Color, width int) error {
|
||||||
w := textWidth(str) * scale
|
w := internal.ArcadeFont.TextWidth(str) * scale
|
||||||
x += width - w
|
x += width - w
|
||||||
return drawTextWithShadow(rt, str, x, y, scale, clr)
|
return internal.ArcadeFont.DrawTextWithShadow(rt, str, x, y, scale, clr)
|
||||||
}
|
}
|
||||||
|
@ -30,22 +30,20 @@ type GameState struct {
|
|||||||
type Game struct {
|
type Game struct {
|
||||||
once sync.Once
|
once sync.Once
|
||||||
sceneManager *SceneManager
|
sceneManager *SceneManager
|
||||||
input *Input
|
input Input
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGame() *Game {
|
func NewGame() *Game {
|
||||||
game := &Game{
|
return &Game{
|
||||||
sceneManager: NewSceneManager(NewTitleScene()),
|
sceneManager: NewSceneManager(NewTitleScene()),
|
||||||
input: NewInput(),
|
|
||||||
}
|
}
|
||||||
return game
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (game *Game) Update(r *ebiten.Image) error {
|
func (game *Game) Update(r *ebiten.Image) error {
|
||||||
game.input.Update()
|
game.input.Update()
|
||||||
game.sceneManager.Update(&GameState{
|
game.sceneManager.Update(&GameState{
|
||||||
SceneManager: game.sceneManager,
|
SceneManager: game.sceneManager,
|
||||||
Input: game.input,
|
Input: &game.input,
|
||||||
})
|
})
|
||||||
game.sceneManager.Draw(r)
|
game.sceneManager.Draw(r)
|
||||||
return nil
|
return nil
|
||||||
|
110
example/blocks/blocks/gamepadscene.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package blocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/example/internal"
|
||||||
|
"image/color"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GamepadScene struct {
|
||||||
|
currentIndex int
|
||||||
|
countAfterSetting int
|
||||||
|
buttonStates []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGamepadScene() *GamepadScene {
|
||||||
|
return &GamepadScene{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GamepadScene) Update(state *GameState) error {
|
||||||
|
if s.currentIndex == 0 {
|
||||||
|
state.Input.gamepadConfig.Reset()
|
||||||
|
}
|
||||||
|
if state.Input.StateForKey(ebiten.KeyEscape) == 1 {
|
||||||
|
state.Input.gamepadConfig.Reset()
|
||||||
|
state.SceneManager.GoTo(NewTitleScene())
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.buttonStates == nil {
|
||||||
|
s.buttonStates = make([]string, len(gamepadStdButtons))
|
||||||
|
}
|
||||||
|
for i, b := range gamepadStdButtons {
|
||||||
|
if i < s.currentIndex {
|
||||||
|
s.buttonStates[i] = strings.ToUpper(state.Input.gamepadConfig.Name(b))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.currentIndex == i {
|
||||||
|
s.buttonStates[i] = "_"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.buttonStates[i] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 < s.countAfterSetting {
|
||||||
|
s.countAfterSetting--
|
||||||
|
if s.countAfterSetting <= 0 {
|
||||||
|
state.SceneManager.GoTo(NewTitleScene())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b := gamepadStdButtons[s.currentIndex]
|
||||||
|
if state.Input.gamepadConfig.Scan(0, b) {
|
||||||
|
s.currentIndex++
|
||||||
|
if s.currentIndex == len(gamepadStdButtons) {
|
||||||
|
s.countAfterSetting = 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GamepadScene) Draw(screen *ebiten.Image) error {
|
||||||
|
screen.Fill(color.Black)
|
||||||
|
|
||||||
|
if s.buttonStates == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f := `GAMEPAD CONFIGURATION
|
||||||
|
(PRESS ESC TO CANCEL)
|
||||||
|
|
||||||
|
|
||||||
|
MOVE LEFT: %s
|
||||||
|
|
||||||
|
MOVE RIGHT: %s
|
||||||
|
|
||||||
|
DROP: %s
|
||||||
|
|
||||||
|
ROTATE LEFT: %s
|
||||||
|
|
||||||
|
ROTATE RIGHT: %s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%s`
|
||||||
|
msg := ""
|
||||||
|
if s.currentIndex == len(gamepadStdButtons) {
|
||||||
|
msg = "OK!"
|
||||||
|
}
|
||||||
|
str := fmt.Sprintf(f, s.buttonStates[0], s.buttonStates[1], s.buttonStates[2], s.buttonStates[3], s.buttonStates[4], msg)
|
||||||
|
if err := internal.ArcadeFont.DrawTextWithShadow(screen, str, 16, 16, 1, color.White); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -17,6 +17,7 @@ package blocks
|
|||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
"github.com/hajimehoshi/ebiten/example/internal"
|
||||||
"image/color"
|
"image/color"
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@ -25,7 +26,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
imageEmpty *ebiten.Image
|
|
||||||
imageGameBG *ebiten.Image
|
imageGameBG *ebiten.Image
|
||||||
imageWindows *ebiten.Image
|
imageWindows *ebiten.Image
|
||||||
imageGameover *ebiten.Image
|
imageGameover *ebiten.Image
|
||||||
@ -67,12 +67,6 @@ func linesTextBoxPosition() (x, y int) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
imageEmpty, err = ebiten.NewImage(16, 16, ebiten.FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
imageEmpty.Fill(color.White)
|
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
imageGameBG, _, err = ebitenutil.NewImageFromFile("images/gophers.jpg", ebiten.FilterLinear)
|
imageGameBG, _, err = ebitenutil.NewImageFromFile("images/gophers.jpg", ebiten.FilterLinear)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -91,7 +85,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
// Windows: Next
|
// Windows: Next
|
||||||
x, y = nextWindowLabelPosition()
|
x, y = nextWindowLabelPosition()
|
||||||
if err := drawTextWithShadow(imageWindows, "NEXT", x, y, 1, fontColor); err != nil {
|
if err := internal.ArcadeFont.DrawTextWithShadow(imageWindows, "NEXT", x, y, 1, fontColor); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
x, y = nextWindowPosition()
|
x, y = nextWindowPosition()
|
||||||
@ -127,18 +121,13 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func drawWindow(r *ebiten.Image, x, y, width, height int) error {
|
func drawWindow(r *ebiten.Image, x, y, width, height int) error {
|
||||||
w, h := imageEmpty.Size()
|
return r.DrawFilledRect(x, y, width, height, color.NRGBA{0, 0, 0, 0xc0})
|
||||||
op := &ebiten.DrawImageOptions{}
|
|
||||||
op.GeoM.Scale(float64(width)/float64(w), float64(height)/float64(h))
|
|
||||||
op.GeoM.Translate(float64(x), float64(y))
|
|
||||||
op.ColorM.Scale(0.0, 0.0, 0.0, 0.75)
|
|
||||||
return r.DrawImage(imageEmpty, op)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var fontColor = color.NRGBA{0x40, 0x40, 0xff, 0xff}
|
var fontColor = color.NRGBA{0x40, 0x40, 0xff, 0xff}
|
||||||
|
|
||||||
func drawTextBox(r *ebiten.Image, label string, x, y, width int) error {
|
func drawTextBox(r *ebiten.Image, label string, x, y, width int) error {
|
||||||
if err := drawTextWithShadow(r, label, x, y, 1, fontColor); err != nil {
|
if err := internal.ArcadeFont.DrawTextWithShadow(r, label, x, y, 1, fontColor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
y += blockWidth
|
y += blockWidth
|
||||||
@ -249,6 +238,7 @@ func (s *GameScene) Update(state *GameState) error {
|
|||||||
s.field.Update()
|
s.field.Update()
|
||||||
|
|
||||||
if s.gameover {
|
if s.gameover {
|
||||||
|
// TODO: Gamepad key?
|
||||||
if state.Input.StateForKey(ebiten.KeySpace) == 1 {
|
if state.Input.StateForKey(ebiten.KeySpace) == 1 {
|
||||||
state.SceneManager.GoTo(NewTitleScene())
|
state.SceneManager.GoTo(NewTitleScene())
|
||||||
}
|
}
|
||||||
@ -274,23 +264,23 @@ func (s *GameScene) Update(state *GameState) error {
|
|||||||
piece := s.currentPiece
|
piece := s.currentPiece
|
||||||
x := s.currentPieceX
|
x := s.currentPieceX
|
||||||
y := s.currentPieceY
|
y := s.currentPieceY
|
||||||
if state.Input.StateForKey(ebiten.KeySpace) == 1 || state.Input.StateForKey(ebiten.KeyX) == 1 {
|
if state.Input.IsRotateRightTrigger() {
|
||||||
s.currentPieceAngle = s.field.RotatePieceRight(piece, x, y, angle)
|
s.currentPieceAngle = s.field.RotatePieceRight(piece, x, y, angle)
|
||||||
moved = angle != s.currentPieceAngle
|
moved = angle != s.currentPieceAngle
|
||||||
}
|
}
|
||||||
if state.Input.StateForKey(ebiten.KeyZ) == 1 {
|
if state.Input.IsRotateLeftTrigger() {
|
||||||
s.currentPieceAngle = s.field.RotatePieceLeft(piece, x, y, angle)
|
s.currentPieceAngle = s.field.RotatePieceLeft(piece, x, y, angle)
|
||||||
moved = angle != s.currentPieceAngle
|
moved = angle != s.currentPieceAngle
|
||||||
}
|
}
|
||||||
if l := state.Input.StateForKey(ebiten.KeyLeft); l == 1 || (10 <= l && l%2 == 0) {
|
if l := state.Input.StateForLeft(); l == 1 || (10 <= l && l%2 == 0) {
|
||||||
s.currentPieceX = s.field.MovePieceToLeft(piece, x, y, angle)
|
s.currentPieceX = s.field.MovePieceToLeft(piece, x, y, angle)
|
||||||
moved = x != s.currentPieceX
|
moved = x != s.currentPieceX
|
||||||
}
|
}
|
||||||
if r := state.Input.StateForKey(ebiten.KeyRight); r == 1 || (10 <= r && r%2 == 0) {
|
if r := state.Input.StateForRight(); r == 1 || (10 <= r && r%2 == 0) {
|
||||||
s.currentPieceX = s.field.MovePieceToRight(piece, x, y, angle)
|
s.currentPieceX = s.field.MovePieceToRight(piece, x, y, angle)
|
||||||
moved = y != s.currentPieceX
|
moved = y != s.currentPieceX
|
||||||
}
|
}
|
||||||
if d := state.Input.StateForKey(ebiten.KeyDown); (d-1)%2 == 0 {
|
if d := state.Input.StateForDown(); (d-1)%2 == 0 {
|
||||||
s.currentPieceY = s.field.DropPiece(piece, x, y, angle)
|
s.currentPieceY = s.field.DropPiece(piece, x, y, angle)
|
||||||
moved = y != s.currentPieceY
|
moved = y != s.currentPieceY
|
||||||
if moved {
|
if moved {
|
||||||
@ -314,7 +304,7 @@ func (s *GameScene) Update(state *GameState) error {
|
|||||||
if moved {
|
if moved {
|
||||||
s.landingCount = 0
|
s.landingCount = 0
|
||||||
} else if !s.field.Flushing() && !s.field.PieceDroppable(piece, s.currentPieceX, s.currentPieceY, angle) {
|
} else if !s.field.Flushing() && !s.field.PieceDroppable(piece, s.currentPieceX, s.currentPieceY, angle) {
|
||||||
if 0 < state.Input.StateForKey(ebiten.KeyDown) {
|
if 0 < state.Input.StateForDown() {
|
||||||
s.landingCount += 10
|
s.landingCount += 10
|
||||||
} else {
|
} else {
|
||||||
s.landingCount++
|
s.landingCount++
|
||||||
|
@ -16,26 +16,97 @@ package blocks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/exp/gamepad"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Input struct {
|
var gamepadStdButtons = []gamepad.StdButton{
|
||||||
states [256]int
|
gamepad.StdButtonLL,
|
||||||
|
gamepad.StdButtonLR,
|
||||||
|
gamepad.StdButtonLD,
|
||||||
|
gamepad.StdButtonRD,
|
||||||
|
gamepad.StdButtonRR,
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInput() *Input {
|
type Input struct {
|
||||||
return &Input{}
|
keyStates [256]int
|
||||||
|
gamepadButtonStates [256]int
|
||||||
|
gamepadStdButtonStates [16]int
|
||||||
|
gamepadConfig gamepad.Configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) StateForKey(key ebiten.Key) int {
|
func (i *Input) StateForKey(key ebiten.Key) int {
|
||||||
return i.states[key]
|
return i.keyStates[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) StateForGamepadButton(b ebiten.GamepadButton) int {
|
||||||
|
return i.gamepadButtonStates[b]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) stateForGamepadStdButton(b gamepad.StdButton) int {
|
||||||
|
return i.gamepadStdButtonStates[b]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) Update() {
|
func (i *Input) Update() {
|
||||||
for key := range i.states {
|
for key := range i.keyStates {
|
||||||
if !ebiten.IsKeyPressed(ebiten.Key(key)) {
|
if !ebiten.IsKeyPressed(ebiten.Key(key)) {
|
||||||
i.states[key] = 0
|
i.keyStates[key] = 0
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
i.states[key]++
|
i.keyStates[key]++
|
||||||
|
}
|
||||||
|
|
||||||
|
const gamepadID = 0
|
||||||
|
for b := range i.gamepadButtonStates {
|
||||||
|
if !ebiten.IsGamepadButtonPressed(gamepadID, ebiten.GamepadButton(b)) {
|
||||||
|
i.gamepadButtonStates[b] = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i.gamepadButtonStates[b]++
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range gamepadStdButtons {
|
||||||
|
if !i.gamepadConfig.IsButtonPressed(gamepadID, b) {
|
||||||
|
i.gamepadStdButtonStates[b] = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i.gamepadStdButtonStates[b]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Input) IsRotateRightTrigger() bool {
|
||||||
|
if i.StateForKey(ebiten.KeySpace) == 1 || i.StateForKey(ebiten.KeyX) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return i.stateForGamepadStdButton(gamepad.StdButtonRR) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) IsRotateLeftTrigger() bool {
|
||||||
|
if i.StateForKey(ebiten.KeyZ) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return i.stateForGamepadStdButton(gamepad.StdButtonRD) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) StateForLeft() int {
|
||||||
|
v := i.StateForKey(ebiten.KeyLeft)
|
||||||
|
if 0 < v {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return i.stateForGamepadStdButton(gamepad.StdButtonLL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) StateForRight() int {
|
||||||
|
v := i.StateForKey(ebiten.KeyRight)
|
||||||
|
if 0 < v {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return i.stateForGamepadStdButton(gamepad.StdButtonLR)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) StateForDown() int {
|
||||||
|
v := i.StateForKey(ebiten.KeyDown)
|
||||||
|
if 0 < v {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return i.stateForGamepadStdButton(gamepad.StdButtonLD)
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ package blocks
|
|||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
"github.com/hajimehoshi/ebiten/example/internal"
|
||||||
"image/color"
|
"image/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,10 +67,38 @@ func NewTitleScene() *TitleScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func anyGamepadStdButtonPressed(i *Input) bool {
|
||||||
|
for _, b := range gamepadStdButtons {
|
||||||
|
if i.gamepadConfig.IsButtonPressed(0, b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func anyGamepadButtonPressed(i *Input) bool {
|
||||||
|
bn := ebiten.GamepadButton(ebiten.GamepadButtonNum(0))
|
||||||
|
for b := ebiten.GamepadButton(0); b < bn; b++ {
|
||||||
|
if i.StateForGamepadButton(b) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TitleScene) Update(state *GameState) error {
|
func (s *TitleScene) Update(state *GameState) error {
|
||||||
s.count++
|
s.count++
|
||||||
if state.Input.StateForKey(ebiten.KeySpace) == 1 {
|
if state.Input.StateForKey(ebiten.KeySpace) == 1 {
|
||||||
state.SceneManager.GoTo(NewGameScene())
|
state.SceneManager.GoTo(NewGameScene())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if anyGamepadStdButtonPressed(state.Input) {
|
||||||
|
state.SceneManager.GoTo(NewGameScene())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if anyGamepadButtonPressed(state.Input) {
|
||||||
|
state.SceneManager.GoTo(NewGamepadScene())
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -83,9 +112,12 @@ func (s *TitleScene) Draw(r *ebiten.Image) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message := "PRESS SPACE TO START"
|
message := "PRESS SPACE TO START"
|
||||||
x := (ScreenWidth - textWidth(message)) / 2
|
x := (ScreenWidth - internal.ArcadeFont.TextWidth(message)) / 2
|
||||||
y := ScreenHeight - 48
|
y := ScreenHeight - 48
|
||||||
return drawTextWithShadow(r, message, x, y, 1, color.NRGBA{0x80, 0, 0, 0xff})
|
if err := internal.ArcadeFont.DrawTextWithShadow(r, message, x, y, 1, color.NRGBA{0x80, 0, 0, 0xff}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TitleScene) drawTitleBackground(r *ebiten.Image, c int) error {
|
func (s *TitleScene) drawTitleBackground(r *ebiten.Image, c int) error {
|
||||||
@ -97,8 +129,8 @@ func (s *TitleScene) drawTitleBackground(r *ebiten.Image, c int) error {
|
|||||||
|
|
||||||
func drawLogo(r *ebiten.Image, str string) error {
|
func drawLogo(r *ebiten.Image, str string) error {
|
||||||
scale := 4
|
scale := 4
|
||||||
textWidth := textWidth(str) * scale
|
textWidth := internal.ArcadeFont.TextWidth(str) * scale
|
||||||
x := (ScreenWidth - textWidth) / 2
|
x := (ScreenWidth - textWidth) / 2
|
||||||
y := 32
|
y := 32
|
||||||
return drawTextWithShadow(r, str, x, y, scale, color.NRGBA{0x00, 0x00, 0x80, 0xff})
|
return internal.ArcadeFont.DrawTextWithShadow(r, str, x, y, scale, color.NRGBA{0x00, 0x00, 0x80, 0xff})
|
||||||
}
|
}
|
||||||
|
64
example/gamepad/main.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
screenWidth = 320
|
||||||
|
screenHeight = 240
|
||||||
|
)
|
||||||
|
|
||||||
|
func update(screen *ebiten.Image) error {
|
||||||
|
// TODO: API to get the available, lowest ID
|
||||||
|
const gamepadID = 0
|
||||||
|
axes := []string{}
|
||||||
|
pressedButtons := []string{}
|
||||||
|
|
||||||
|
maxAxis := ebiten.GamepadAxisNum(gamepadID)
|
||||||
|
for a := 0; a < maxAxis; a++ {
|
||||||
|
v := ebiten.GamepadAxis(gamepadID, a)
|
||||||
|
axes = append(axes, fmt.Sprintf("%d: %0.6f", a, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
maxButton := ebiten.GamepadButton(ebiten.GamepadButtonNum(gamepadID))
|
||||||
|
for b := ebiten.GamepadButton(gamepadID); b < maxButton; b++ {
|
||||||
|
if ebiten.IsGamepadButtonPressed(gamepadID, b) {
|
||||||
|
pressedButtons = append(pressedButtons, strconv.Itoa(int(b)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str := `Gamepad
|
||||||
|
Axes:
|
||||||
|
{{.Axes}}
|
||||||
|
Pressed Buttons: {{.Buttons}}`
|
||||||
|
str = strings.Replace(str, "{{.Axes}}", strings.Join(axes, "\n "), -1)
|
||||||
|
str = strings.Replace(str, "{{.Buttons}}", strings.Join(pressedButtons, ", "), -1)
|
||||||
|
ebitenutil.DebugPrint(screen, str)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Gamepad (Ebiten Demo)"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
18
example/generate.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:generate go run keyboard/keyboard/gen.go
|
||||||
|
//go:generate gofmt -w .
|
||||||
|
|
||||||
|
package example
|
Before Width: | Height: | Size: 1008 B After Width: | Height: | Size: 1008 B |
Before Width: | Height: | Size: 2.1 KiB |
BIN
example/images/keyboard/keyboard.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
@ -1,6 +1,6 @@
|
|||||||
# License
|
# License
|
||||||
|
|
||||||
## blocks/font.png
|
## arcadefont.png
|
||||||
|
|
||||||
```
|
```
|
||||||
9031 Font ReadMe
|
9031 Font ReadMe
|
||||||
|
122
example/internal/font.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ArcadeFont *Font
|
||||||
|
)
|
||||||
|
|
||||||
|
type Font struct {
|
||||||
|
image *ebiten.Image
|
||||||
|
offset int
|
||||||
|
charNumPerLine int
|
||||||
|
charWidth int
|
||||||
|
charHeight int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) TextWidth(str string) int {
|
||||||
|
// TODO: Take care about '\n'
|
||||||
|
return f.charWidth * len(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dir := ""
|
||||||
|
if runtime.GOARCH != "js" {
|
||||||
|
// Get the path of this file (font.go).
|
||||||
|
_, path, _, _ := runtime.Caller(0)
|
||||||
|
path = filepath.Dir(path)
|
||||||
|
dir = filepath.Join(path, "..")
|
||||||
|
}
|
||||||
|
arcadeFontPath := filepath.Join(dir, "images", "arcadefont.png")
|
||||||
|
|
||||||
|
arcadeFontImage, _, err := ebitenutil.NewImageFromFile(arcadeFontPath, ebiten.FilterNearest)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ArcadeFont = &Font{arcadeFontImage, 32, 16, 8, 8}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fontImageParts struct {
|
||||||
|
str string
|
||||||
|
font *Font
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fontImageParts) Len() int {
|
||||||
|
return len(f.str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fontImageParts) Dst(i int) (x0, y0, x1, y1 int) {
|
||||||
|
x := i - strings.LastIndex(f.str[:i], "\n") - 1
|
||||||
|
y := strings.Count(f.str[:i], "\n")
|
||||||
|
x *= f.font.charWidth
|
||||||
|
y *= f.font.charHeight
|
||||||
|
if x < 0 {
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
return x, y, x + f.font.charWidth, y + f.font.charHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fontImageParts) Src(i int) (x0, y0, x1, y1 int) {
|
||||||
|
code := int(f.str[i])
|
||||||
|
if code == '\n' {
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
x := (code % f.font.charNumPerLine) * f.font.charWidth
|
||||||
|
y := ((code - f.font.offset) / f.font.charNumPerLine) * f.font.charHeight
|
||||||
|
return x, y, x + f.font.charWidth, y + f.font.charHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) DrawText(rt *ebiten.Image, str string, ox, oy, scale int, c color.Color) error {
|
||||||
|
options := &ebiten.DrawImageOptions{
|
||||||
|
ImageParts: &fontImageParts{str, f},
|
||||||
|
}
|
||||||
|
options.GeoM.Scale(float64(scale), float64(scale))
|
||||||
|
options.GeoM.Translate(float64(ox), float64(oy))
|
||||||
|
|
||||||
|
ur, ug, ub, ua := c.RGBA()
|
||||||
|
const max = math.MaxUint16
|
||||||
|
r := float64(ur) / max
|
||||||
|
g := float64(ug) / max
|
||||||
|
b := float64(ub) / max
|
||||||
|
a := float64(ua) / max
|
||||||
|
if 0 < a {
|
||||||
|
r /= a
|
||||||
|
g /= a
|
||||||
|
b /= a
|
||||||
|
}
|
||||||
|
options.ColorM.Scale(r, g, b, a)
|
||||||
|
|
||||||
|
return rt.DrawImage(f.image, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) DrawTextWithShadow(rt *ebiten.Image, str string, x, y, scale int, clr color.Color) error {
|
||||||
|
if err := f.DrawText(rt, str, x+1, y+1, scale, color.NRGBA{0, 0, 0, 0x80}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := f.DrawText(rt, str, x, y, scale, clr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
176
example/keyboard/keyboard/gen.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/example/internal"
|
||||||
|
einternal "github.com/hajimehoshi/ebiten/internal"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keyboardKeys = [][]string{
|
||||||
|
{"Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", " ", " ", " ", "Del"},
|
||||||
|
{"Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", " ", " ", "BS"},
|
||||||
|
{"Ctrl", "A", "S", "D", "F", "G", "H", "J", "K", "L", " ", " ", "Enter"},
|
||||||
|
{"Shift", "Z", "X", "C", "V", "B", "N", "M", ",", ".", " ", " "},
|
||||||
|
{" ", "Alt", "Space", " ", " "},
|
||||||
|
{},
|
||||||
|
{"", "Up", ""},
|
||||||
|
{"Left", "Down", "Right"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawKey(t *ebiten.Image, name string, x, y, width int) {
|
||||||
|
const height = 16
|
||||||
|
width--
|
||||||
|
c := color.White
|
||||||
|
t.DrawLine(x, y+3, x, y+height-3, c)
|
||||||
|
t.DrawLine(x+width-1, y+3, x+width-1, y+height-3, c)
|
||||||
|
t.DrawLine(x+3, y, x+width-3, y, c)
|
||||||
|
t.DrawLine(x+3, y+height-1, x+width-3, y+height-1, c)
|
||||||
|
|
||||||
|
t.DrawLine(x, y+3, x+3, y, c)
|
||||||
|
t.DrawLine(x+width-4, y, x+width-1, y+3, c)
|
||||||
|
t.DrawLine(x, y+height-4, x+3, y+height-1, c)
|
||||||
|
t.DrawLine(x+width-1, y+height-4, x+width-4, y+height-1, c)
|
||||||
|
|
||||||
|
internal.ArcadeFont.DrawText(t, name, x+4, y+5, 1, color.White)
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputKeyboardImage() (map[string]image.Rectangle, error) {
|
||||||
|
keyMap := map[string]image.Rectangle{}
|
||||||
|
img, err := ebiten.NewImage(320, 240, ebiten.FilterNearest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x, y := 0, 0
|
||||||
|
for j, line := range keyboardKeys {
|
||||||
|
x = 0
|
||||||
|
const height = 18
|
||||||
|
for i, key := range line {
|
||||||
|
width := 16
|
||||||
|
switch j {
|
||||||
|
default:
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
width = 16 + 8*(j+2)
|
||||||
|
case len(line) - 1:
|
||||||
|
width = 16 + 8*(j+2)
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
width = 16 + 8*(j+2)
|
||||||
|
case 1:
|
||||||
|
width = 16 * 2
|
||||||
|
case 2:
|
||||||
|
width = 16 * 5
|
||||||
|
case 3:
|
||||||
|
width = 16 * 2
|
||||||
|
case 4:
|
||||||
|
width = 16 + 8*(j+2)
|
||||||
|
}
|
||||||
|
case 6, 7:
|
||||||
|
width = 16 * 3
|
||||||
|
}
|
||||||
|
if key != "" {
|
||||||
|
drawKey(img, key, x, y, width)
|
||||||
|
if key != " " {
|
||||||
|
keyMap[key] = image.Rect(x, y, x+width, y+height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x += width
|
||||||
|
}
|
||||||
|
y += height
|
||||||
|
}
|
||||||
|
|
||||||
|
palette := color.Palette([]color.Color{
|
||||||
|
color.Transparent, color.Opaque,
|
||||||
|
})
|
||||||
|
palettedImg := image.NewPaletted(img.Bounds(), palette)
|
||||||
|
draw.Draw(palettedImg, palettedImg.Bounds(), img, image.ZP, draw.Src)
|
||||||
|
|
||||||
|
f, err := os.Create("images/keyboard/keyboard.png")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if err := png.Encode(f, palettedImg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return keyMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyRectTmpl = `{{.License}}
|
||||||
|
|
||||||
|
// DO NOT EDIT: This file is auto-generated by genkeys.go.
|
||||||
|
|
||||||
|
package keyboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keyboardKeyRects = map[string]image.Rectangle{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
{{range $key, $rect := .KeyRectsMap}} keyboardKeyRects["{{$key}}"] = image.Rect({{$rect.Min.X}}, {{$rect.Min.Y}}, {{$rect.Max.X}}, {{$rect.Max.Y}})
|
||||||
|
{{end}}}
|
||||||
|
|
||||||
|
func KeyRect(name string) (image.Rectangle, bool) {
|
||||||
|
r, ok := keyboardKeyRects[name]
|
||||||
|
return r, ok
|
||||||
|
}`
|
||||||
|
|
||||||
|
func outputKeyRectsGo(k map[string]image.Rectangle) error {
|
||||||
|
license, err := einternal.LicenseComment()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
path := "keyboard/keyboard/keyrects.go"
|
||||||
|
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
tmpl, err := template.New(path).Parse(keyRectTmpl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tmpl.Execute(f, map[string]interface{}{
|
||||||
|
"License": license,
|
||||||
|
"KeyRectsMap": k,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m, err := outputKeyboardImage()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := outputKeyRectsGo(m); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
82
example/keyboard/keyboard/keyrects.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// DO NOT EDIT: This file is auto-generated by genkeys.go.
|
||||||
|
|
||||||
|
package keyboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keyboardKeyRects = map[string]image.Rectangle{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
keyboardKeyRects[","] = image.Rect(168, 54, 184, 72)
|
||||||
|
keyboardKeyRects["."] = image.Rect(184, 54, 200, 72)
|
||||||
|
keyboardKeyRects["0"] = image.Rect(176, 0, 192, 18)
|
||||||
|
keyboardKeyRects["1"] = image.Rect(32, 0, 48, 18)
|
||||||
|
keyboardKeyRects["2"] = image.Rect(48, 0, 64, 18)
|
||||||
|
keyboardKeyRects["3"] = image.Rect(64, 0, 80, 18)
|
||||||
|
keyboardKeyRects["4"] = image.Rect(80, 0, 96, 18)
|
||||||
|
keyboardKeyRects["5"] = image.Rect(96, 0, 112, 18)
|
||||||
|
keyboardKeyRects["6"] = image.Rect(112, 0, 128, 18)
|
||||||
|
keyboardKeyRects["7"] = image.Rect(128, 0, 144, 18)
|
||||||
|
keyboardKeyRects["8"] = image.Rect(144, 0, 160, 18)
|
||||||
|
keyboardKeyRects["9"] = image.Rect(160, 0, 176, 18)
|
||||||
|
keyboardKeyRects["A"] = image.Rect(48, 36, 64, 54)
|
||||||
|
keyboardKeyRects["Alt"] = image.Rect(64, 72, 96, 90)
|
||||||
|
keyboardKeyRects["B"] = image.Rect(120, 54, 136, 72)
|
||||||
|
keyboardKeyRects["BS"] = image.Rect(232, 18, 272, 36)
|
||||||
|
keyboardKeyRects["C"] = image.Rect(88, 54, 104, 72)
|
||||||
|
keyboardKeyRects["Ctrl"] = image.Rect(0, 36, 48, 54)
|
||||||
|
keyboardKeyRects["D"] = image.Rect(80, 36, 96, 54)
|
||||||
|
keyboardKeyRects["Del"] = image.Rect(240, 0, 272, 18)
|
||||||
|
keyboardKeyRects["Down"] = image.Rect(48, 126, 96, 144)
|
||||||
|
keyboardKeyRects["E"] = image.Rect(72, 18, 88, 36)
|
||||||
|
keyboardKeyRects["Enter"] = image.Rect(224, 36, 272, 54)
|
||||||
|
keyboardKeyRects["Esc"] = image.Rect(0, 0, 32, 18)
|
||||||
|
keyboardKeyRects["F"] = image.Rect(96, 36, 112, 54)
|
||||||
|
keyboardKeyRects["G"] = image.Rect(112, 36, 128, 54)
|
||||||
|
keyboardKeyRects["H"] = image.Rect(128, 36, 144, 54)
|
||||||
|
keyboardKeyRects["I"] = image.Rect(152, 18, 168, 36)
|
||||||
|
keyboardKeyRects["J"] = image.Rect(144, 36, 160, 54)
|
||||||
|
keyboardKeyRects["K"] = image.Rect(160, 36, 176, 54)
|
||||||
|
keyboardKeyRects["L"] = image.Rect(176, 36, 192, 54)
|
||||||
|
keyboardKeyRects["Left"] = image.Rect(0, 126, 48, 144)
|
||||||
|
keyboardKeyRects["M"] = image.Rect(152, 54, 168, 72)
|
||||||
|
keyboardKeyRects["N"] = image.Rect(136, 54, 152, 72)
|
||||||
|
keyboardKeyRects["O"] = image.Rect(168, 18, 184, 36)
|
||||||
|
keyboardKeyRects["P"] = image.Rect(184, 18, 200, 36)
|
||||||
|
keyboardKeyRects["Q"] = image.Rect(40, 18, 56, 36)
|
||||||
|
keyboardKeyRects["R"] = image.Rect(88, 18, 104, 36)
|
||||||
|
keyboardKeyRects["Right"] = image.Rect(96, 126, 144, 144)
|
||||||
|
keyboardKeyRects["S"] = image.Rect(64, 36, 80, 54)
|
||||||
|
keyboardKeyRects["Shift"] = image.Rect(0, 54, 56, 72)
|
||||||
|
keyboardKeyRects["Space"] = image.Rect(96, 72, 176, 90)
|
||||||
|
keyboardKeyRects["T"] = image.Rect(104, 18, 120, 36)
|
||||||
|
keyboardKeyRects["Tab"] = image.Rect(0, 18, 40, 36)
|
||||||
|
keyboardKeyRects["U"] = image.Rect(136, 18, 152, 36)
|
||||||
|
keyboardKeyRects["Up"] = image.Rect(48, 108, 96, 126)
|
||||||
|
keyboardKeyRects["V"] = image.Rect(104, 54, 120, 72)
|
||||||
|
keyboardKeyRects["W"] = image.Rect(56, 18, 72, 36)
|
||||||
|
keyboardKeyRects["X"] = image.Rect(72, 54, 88, 72)
|
||||||
|
keyboardKeyRects["Y"] = image.Rect(120, 18, 136, 36)
|
||||||
|
keyboardKeyRects["Z"] = image.Rect(56, 54, 72, 72)
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyRect(name string) (image.Rectangle, bool) {
|
||||||
|
r, ok := keyboardKeyRects[name]
|
||||||
|
return r, ok
|
||||||
|
}
|
@ -17,10 +17,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
"github.com/hajimehoshi/ebiten/example/keyboard/keyboard"
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -28,13 +27,23 @@ const (
|
|||||||
screenHeight = 240
|
screenHeight = 240
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var keyboardImage *ebiten.Image
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
keyboardImage, _, err = ebitenutil.NewImageFromFile("images/keyboard/keyboard.png", ebiten.FilterNearest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var keyNames = map[ebiten.Key]string{
|
var keyNames = map[ebiten.Key]string{
|
||||||
ebiten.KeyBackspace: "Backspace",
|
ebiten.KeyBackspace: "BS",
|
||||||
ebiten.KeyComma: "','",
|
ebiten.KeyComma: ",",
|
||||||
ebiten.KeyDelete: "Delete",
|
ebiten.KeyDelete: "Del",
|
||||||
ebiten.KeyEnter: "Enter",
|
ebiten.KeyEnter: "Enter",
|
||||||
ebiten.KeyEscape: "Esc",
|
ebiten.KeyEscape: "Esc",
|
||||||
ebiten.KeyPeriod: "'.'",
|
ebiten.KeyPeriod: ".",
|
||||||
ebiten.KeySpace: "Space",
|
ebiten.KeySpace: "Space",
|
||||||
ebiten.KeyTab: "Tab",
|
ebiten.KeyTab: "Tab",
|
||||||
|
|
||||||
@ -50,7 +59,32 @@ var keyNames = map[ebiten.Key]string{
|
|||||||
ebiten.KeyAlt: "Alt",
|
ebiten.KeyAlt: "Alt",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pressedKeysParts []string
|
||||||
|
|
||||||
|
func (p pressedKeysParts) Len() int {
|
||||||
|
return len(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pressedKeysParts) Dst(i int) (x0, y0, x1, y1 int) {
|
||||||
|
k := p[i]
|
||||||
|
r, ok := keyboard.KeyRect(k)
|
||||||
|
if !ok {
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
return r.Min.X, r.Min.Y, r.Max.X, r.Max.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pressedKeysParts) Src(i int) (x0, y0, x1, y1 int) {
|
||||||
|
return p.Dst(i)
|
||||||
|
}
|
||||||
|
|
||||||
func update(screen *ebiten.Image) error {
|
func update(screen *ebiten.Image) error {
|
||||||
|
const offsetX, offsetY = 24, 40
|
||||||
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
op.GeoM.Translate(offsetX, offsetY)
|
||||||
|
op.ColorM.Scale(0.5, 0.5, 0.5, 1)
|
||||||
|
screen.DrawImage(keyboardImage, op)
|
||||||
|
|
||||||
pressed := []string{}
|
pressed := []string{}
|
||||||
for i := 0; i <= 9; i++ {
|
for i := 0; i <= 9; i++ {
|
||||||
if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.Key0) {
|
if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.Key0) {
|
||||||
@ -72,9 +106,13 @@ func update(screen *ebiten.Image) error {
|
|||||||
pressed = append(pressed, name)
|
pressed = append(pressed, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(pressed)
|
|
||||||
str := "Pressed Keys: " + strings.Join(pressed, ", ")
|
op = &ebiten.DrawImageOptions{
|
||||||
ebitenutil.DebugPrint(screen, str)
|
ImageParts: pressedKeysParts(pressed),
|
||||||
|
}
|
||||||
|
op.GeoM.Translate(offsetX, offsetY)
|
||||||
|
screen.DrawImage(keyboardImage, op)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
example/noise/main.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
"image"
|
||||||
|
"log"
|
||||||
|
//"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
screenWidth = 320
|
||||||
|
screenHeight = 240
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
noiseImage *image.RGBA
|
||||||
|
)
|
||||||
|
|
||||||
|
type rand struct {
|
||||||
|
x, y, z, w uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rand) next() uint32 {
|
||||||
|
// math/rand is too slow to keep 60 FPS on web browsers.
|
||||||
|
// Use Xorshift instead: http://en.wikipedia.org/wiki/Xorshift
|
||||||
|
t := r.x ^ (r.x << 11)
|
||||||
|
r.x, r.y, r.z = r.y, r.z, r.w
|
||||||
|
r.w = (r.w ^ (r.w >> 19)) ^ (t ^ (t >> 8))
|
||||||
|
return r.w
|
||||||
|
}
|
||||||
|
|
||||||
|
var randInstance = &rand{12345678, 4185243, 776511, 45411}
|
||||||
|
|
||||||
|
func update(screen *ebiten.Image) error {
|
||||||
|
const l = screenWidth * screenHeight
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
x := randInstance.next()
|
||||||
|
noiseImage.Pix[4*i] = uint8(x >> 24)
|
||||||
|
noiseImage.Pix[4*i+1] = uint8(x >> 16)
|
||||||
|
noiseImage.Pix[4*i+2] = uint8(x >> 8)
|
||||||
|
noiseImage.Pix[4*i+3] = 0xff
|
||||||
|
}
|
||||||
|
screen.ReplacePixels(noiseImage.Pix)
|
||||||
|
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %f", ebiten.CurrentFPS()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
noiseImage = image.NewRGBA(image.Rect(0, 0, screenWidth, screenHeight))
|
||||||
|
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Noise (Ebiten Demo)"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
48
example/shapes/main.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/exp/shape"
|
||||||
|
"image/color"
|
||||||
|
"log"
|
||||||
|
//"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
screenWidth = 320
|
||||||
|
screenHeight = 240
|
||||||
|
)
|
||||||
|
|
||||||
|
func update(screen *ebiten.Image) error {
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
screen.DrawRect(2*i, 2*i, 100, 100, color.NRGBA{0x80, 0x80, 0xff, 0x80})
|
||||||
|
}
|
||||||
|
screen.DrawFilledRect(10, 10, 100, 100, color.NRGBA{0x80, 0x80, 0xff, 0x80})
|
||||||
|
screen.DrawFilledRect(20, 20, 100, 100, color.NRGBA{0x80, 0x80, 0xff, 0x80})
|
||||||
|
screen.DrawLine(130, 0, 140, 100, color.NRGBA{0xff, 0x80, 0x80, 0x80})
|
||||||
|
screen.DrawLine(140, 0, 150, 100, color.NRGBA{0xff, 0x80, 0x80, 0x80})
|
||||||
|
|
||||||
|
shape.DrawEllipse(screen, 0, 0, 200, 200, color.NRGBA{0x80, 0xff, 0x80, 0x80})
|
||||||
|
//shape.DrawArc(screen, 0, 0, 50, 50, 0, math.Pi/2, color.NRGBA{0xff, 0x80, 0x80, 0x80})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Shapes (Ebiten Demo)"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
125
example/sprites/main.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
_ "image/png"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
screenWidth = 320
|
||||||
|
screenHeight = 240
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ebitenImage *ebiten.Image
|
||||||
|
ebitenImageWidth = 0
|
||||||
|
ebitenImageHeight = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sprite struct {
|
||||||
|
image *ebiten.Image
|
||||||
|
x int
|
||||||
|
y int
|
||||||
|
vx int
|
||||||
|
vy int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sprite) Update() {
|
||||||
|
s.x += s.vx
|
||||||
|
s.y += s.vy
|
||||||
|
if s.x < 0 {
|
||||||
|
s.x = -s.x
|
||||||
|
s.vx = -s.vx
|
||||||
|
}
|
||||||
|
if s.y < 0 {
|
||||||
|
s.y = -s.y
|
||||||
|
s.vy = -s.vy
|
||||||
|
}
|
||||||
|
w, h := s.image.Size()
|
||||||
|
if screenWidth <= s.x+w {
|
||||||
|
s.x = 2*(screenWidth-w) - s.x
|
||||||
|
s.vx = -s.vx
|
||||||
|
}
|
||||||
|
if screenHeight <= s.y+h {
|
||||||
|
s.y = 2*(screenHeight-h) - s.y
|
||||||
|
s.vy = -s.vy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sprites []*Sprite
|
||||||
|
|
||||||
|
func (s Sprites) Update() {
|
||||||
|
for _, sprite := range s {
|
||||||
|
sprite.Update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Sprites) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Sprites) Dst(i int) (x0, y0, x1, y1 int) {
|
||||||
|
ss := s[i]
|
||||||
|
return ss.x, ss.y, ss.x + ebitenImageWidth, ss.y + ebitenImageHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Sprites) Src(i int) (x0, y0, x1, y1 int) {
|
||||||
|
return 0, 0, ebitenImageWidth, ebitenImageHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
var sprites = make(Sprites, 10000)
|
||||||
|
|
||||||
|
func update(screen *ebiten.Image) error {
|
||||||
|
sprites.Update()
|
||||||
|
op := &ebiten.DrawImageOptions{
|
||||||
|
ImageParts: sprites,
|
||||||
|
}
|
||||||
|
op.ColorM.Scale(1.0, 1.0, 1.0, 0.5)
|
||||||
|
if err := screen.DrawImage(ebitenImage, op); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f\nNum of sprites: %d", ebiten.CurrentFPS(), sprites.Len()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
ebitenImage, _, err = ebitenutil.NewImageFromFile("images/ebiten.png", ebiten.FilterNearest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
ebitenImageWidth, ebitenImageHeight = ebitenImage.Size()
|
||||||
|
for i, _ := range sprites {
|
||||||
|
w, h := ebitenImage.Size()
|
||||||
|
x, y := rand.Intn(screenWidth-w), rand.Intn(screenHeight-h)
|
||||||
|
vx, vy := 2*rand.Intn(2)-1, 2*rand.Intn(2)-1
|
||||||
|
sprites[i] = &Sprite{
|
||||||
|
image: ebitenImage,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
vx: vx,
|
||||||
|
vy: vy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Sprites (Ebiten Demo)"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
170
exp/gamepad/gamepad.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package gamepad offers abstract gamepad buttons and configuration.
|
||||||
|
package gamepad
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A StdButton represents a standard gamepad button.
|
||||||
|
// See also: http://www.w3.org/TR/gamepad/
|
||||||
|
// [UL0] [UR0]
|
||||||
|
// [UL1] [UR1]
|
||||||
|
//
|
||||||
|
// [LU] [CC] [RU]
|
||||||
|
// [LL][LR] [CL][CR] [RL][RR]
|
||||||
|
// [LD] [RD]
|
||||||
|
// [AL] [AR]
|
||||||
|
type StdButton int
|
||||||
|
|
||||||
|
const (
|
||||||
|
StdButtonNone StdButton = iota
|
||||||
|
StdButtonLL
|
||||||
|
StdButtonLR
|
||||||
|
StdButtonLU
|
||||||
|
StdButtonLD
|
||||||
|
StdButtonCL
|
||||||
|
StdButtonCC
|
||||||
|
StdButtonCR
|
||||||
|
StdButtonRL
|
||||||
|
StdButtonRR
|
||||||
|
StdButtonRU
|
||||||
|
StdButtonRD
|
||||||
|
StdButtonUL0
|
||||||
|
StdButtonUL1
|
||||||
|
StdButtonUR0
|
||||||
|
StdButtonUR1
|
||||||
|
StdButtonAL
|
||||||
|
StdButtonAR
|
||||||
|
)
|
||||||
|
|
||||||
|
const threshold = 0.75
|
||||||
|
|
||||||
|
type axis struct {
|
||||||
|
id int
|
||||||
|
positive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
current StdButton
|
||||||
|
buttons map[StdButton]ebiten.GamepadButton
|
||||||
|
axes map[StdButton]axis
|
||||||
|
assignedButtons map[ebiten.GamepadButton]struct{}
|
||||||
|
assignedAxes map[axis]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Configuration) initializeIfNeeded() {
|
||||||
|
if c.buttons == nil {
|
||||||
|
c.buttons = map[StdButton]ebiten.GamepadButton{}
|
||||||
|
}
|
||||||
|
if c.axes == nil {
|
||||||
|
c.axes = map[StdButton]axis{}
|
||||||
|
}
|
||||||
|
if c.assignedButtons == nil {
|
||||||
|
c.assignedButtons = map[ebiten.GamepadButton]struct{}{}
|
||||||
|
}
|
||||||
|
if c.assignedAxes == nil {
|
||||||
|
c.assignedAxes = map[axis]struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Configuration) Reset() {
|
||||||
|
c.buttons = nil
|
||||||
|
c.axes = nil
|
||||||
|
c.assignedButtons = nil
|
||||||
|
c.assignedAxes = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Configuration) Scan(index int, b StdButton) bool {
|
||||||
|
c.initializeIfNeeded()
|
||||||
|
|
||||||
|
delete(c.buttons, b)
|
||||||
|
delete(c.axes, b)
|
||||||
|
|
||||||
|
ebn := ebiten.GamepadButton(ebiten.GamepadButtonNum(index))
|
||||||
|
for eb := ebiten.GamepadButton(0); eb < ebn; eb++ {
|
||||||
|
if _, ok := c.assignedButtons[eb]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ebiten.IsGamepadButtonPressed(index, eb) {
|
||||||
|
c.buttons[b] = eb
|
||||||
|
c.assignedButtons[eb] = struct{}{}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
an := ebiten.GamepadAxisNum(index)
|
||||||
|
for a := 0; a < an; a++ {
|
||||||
|
v := ebiten.GamepadAxis(index, a)
|
||||||
|
// Check v <= 1.0 because there is a bug that a button returns an axis value wrongly and the value may be over 1.
|
||||||
|
if threshold <= v && v <= 1.0 {
|
||||||
|
if _, ok := c.assignedAxes[axis{a, true}]; !ok {
|
||||||
|
c.axes[b] = axis{a, true}
|
||||||
|
c.assignedAxes[axis{a, true}] = struct{}{}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if -1.0 <= v && v <= -threshold {
|
||||||
|
if _, ok := c.assignedAxes[axis{a, false}]; !ok {
|
||||||
|
c.axes[b] = axis{a, false}
|
||||||
|
c.assignedAxes[axis{a, false}] = struct{}{}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Configuration) IsButtonPressed(id int, b StdButton) bool {
|
||||||
|
c.initializeIfNeeded()
|
||||||
|
|
||||||
|
bb, ok := c.buttons[b]
|
||||||
|
if ok {
|
||||||
|
return ebiten.IsGamepadButtonPressed(0, bb)
|
||||||
|
}
|
||||||
|
a, ok := c.axes[b]
|
||||||
|
if ok {
|
||||||
|
v := ebiten.GamepadAxis(0, a.id)
|
||||||
|
if a.positive {
|
||||||
|
return threshold <= v && v <= 1.0
|
||||||
|
} else {
|
||||||
|
return -1.0 <= v && v <= -threshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Configuration) Name(b StdButton) string {
|
||||||
|
c.initializeIfNeeded()
|
||||||
|
|
||||||
|
bb, ok := c.buttons[b]
|
||||||
|
if ok {
|
||||||
|
return fmt.Sprintf("Button %d", bb)
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok := c.axes[b]
|
||||||
|
if ok {
|
||||||
|
if a.positive {
|
||||||
|
return fmt.Sprintf("Axis %d+", a.id)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("Axis %d-", a.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
98
exp/shape/ellipse.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package shape
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DrawEllipse(s *ebiten.Image, x, y, width, height int, clr color.Color) error {
|
||||||
|
return s.DrawLines(&ellipsesLines{&rect{x, y, width, height, clr}, 0, 2 * math.Pi})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DrawEllipses(s *ebiten.Image, rects ebiten.Rects) error {
|
||||||
|
return s.DrawLines(&ellipsesLines{rects, 0, 2 * math.Pi})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DrawArc(s *ebiten.Image, x, y, width, height int, angle0, angle1 float64, clr color.Color) error {
|
||||||
|
return s.DrawLines(&ellipsesLines{&rect{x, y, width, height, clr}, angle0, angle1})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ellipsesLines struct {
|
||||||
|
ebiten.Rects
|
||||||
|
angle0, angle1 float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ellipsesLines) lineNum() int {
|
||||||
|
return 64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ellipsesLines) Len() int {
|
||||||
|
return e.Rects.Len() * e.lineNum()
|
||||||
|
}
|
||||||
|
|
||||||
|
func round(x float64) int {
|
||||||
|
return int(x + 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ellipsesLines) Points(i int) (x0, y0, x1, y1 int) {
|
||||||
|
n := e.lineNum()
|
||||||
|
x, y, w, h := e.Rects.Rect(i / n)
|
||||||
|
part := float64(i % n)
|
||||||
|
theta0 := 2 * math.Pi * part / float64(n)
|
||||||
|
theta1 := 2 * math.Pi * (part + 1) / float64(n)
|
||||||
|
if theta0 < e.angle0 || e.angle1 < theta1 {
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
theta0 = math.Max(theta0, e.angle0)
|
||||||
|
theta1 = math.Min(theta1, e.angle1)
|
||||||
|
theta0 = math.Mod(theta0, 2*math.Pi)
|
||||||
|
theta1 = math.Mod(theta1, 2*math.Pi)
|
||||||
|
fy0, fx0 := math.Sincos(theta0)
|
||||||
|
fy1, fx1 := math.Sincos(theta1)
|
||||||
|
hw, hh := (float64(w)-1)/2, (float64(h)-1)/2
|
||||||
|
fx0 = fx0*hw + hw + float64(x)
|
||||||
|
fx1 = fx1*hw + hw + float64(x)
|
||||||
|
fy0 = fy0*hh + hh + float64(y)
|
||||||
|
fy1 = fy1*hh + hh + float64(y)
|
||||||
|
// TODO: The last fy1 may differ from first fy0 with very slightly difference,
|
||||||
|
// which makes the lack of 1 pixel.
|
||||||
|
return round(fx0), round(fy0), round(fx1), round(fy1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ellipsesLines) Color(i int) color.Color {
|
||||||
|
return e.Rects.Color(i / e.lineNum())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is same as ebiten.rect.
|
||||||
|
type rect struct {
|
||||||
|
x, y int
|
||||||
|
width, height int
|
||||||
|
color color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rect) Len() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rect) Rect(i int) (x, y, width, height int) {
|
||||||
|
return r.x, r.y, r.width, r.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rect) Color(i int) color.Color {
|
||||||
|
return r.color
|
||||||
|
}
|
@ -36,39 +36,93 @@ func IsMouseButtonPressed(mouseButton MouseButton) bool {
|
|||||||
return ui.IsMouseButtonPressed(ui.MouseButton(mouseButton))
|
return ui.IsMouseButtonPressed(ui.MouseButton(mouseButton))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GamepadAxisNum returns the number of axes of the gamepad.
|
||||||
|
//
|
||||||
|
// NOTE: Gamepad API is available only on desktops, Chrome and Firefox.
|
||||||
|
// To use this API, browsers might require rebooting the browser.
|
||||||
|
func GamepadAxisNum(id int) int {
|
||||||
|
return ui.GamepadAxisNum(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GamepadAxis returns the float value [-1.0 - 1.0] of the axis.
|
||||||
|
//
|
||||||
|
// NOTE: Gamepad API is available only on desktops, Chrome and Firefox.
|
||||||
|
// To use this API, browsers might require rebooting the browser.
|
||||||
|
func GamepadAxis(id int, axis int) float64 {
|
||||||
|
return ui.GamepadAxis(id, axis)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GamepadButtonNum returns the number of the buttons of the gamepad.
|
||||||
|
//
|
||||||
|
// NOTE: Gamepad API is available only on desktops, Chrome and Firefox.
|
||||||
|
// To use this API, browsers might require rebooting the browser.
|
||||||
|
func GamepadButtonNum(id int) int {
|
||||||
|
return ui.GamepadButtonNum(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGamepadButtonPressed returns the boolean indicating the buttons is pressed or not.
|
||||||
|
//
|
||||||
|
// NOTE: Gamepad API is available only on desktops, Chrome and Firefox.
|
||||||
|
// To use this API, browsers might require rebooting the browser.
|
||||||
|
func IsGamepadButtonPressed(id int, button GamepadButton) bool {
|
||||||
|
return ui.IsGamepadButtonPressed(id, ui.GamepadButton(button))
|
||||||
|
}
|
||||||
|
|
||||||
// NewImage returns an empty image.
|
// NewImage returns an empty image.
|
||||||
|
//
|
||||||
|
// NewImage generates a new texture and a new framebuffer.
|
||||||
|
// Be careful that image objects will never be released
|
||||||
|
// even though nothing refers the image object and GC works.
|
||||||
|
// It is because there is no way to define finalizers for Go objects if you use GopherJS.
|
||||||
func NewImage(width, height int, filter Filter) (*Image, error) {
|
func NewImage(width, height int, filter Filter) (*Image, error) {
|
||||||
var innerImage *innerImage
|
var img *Image
|
||||||
var err error
|
var err error
|
||||||
ui.Use(func(c *opengl.Context) {
|
ui.Use(func(c *opengl.Context) {
|
||||||
var texture *graphics.Texture
|
var texture *graphics.Texture
|
||||||
|
var framebuffer *graphics.Framebuffer
|
||||||
texture, err = graphics.NewTexture(c, width, height, glFilter(c, filter))
|
texture, err = graphics.NewTexture(c, width, height, glFilter(c, filter))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
innerImage, err = newInnerImage(c, texture)
|
framebuffer, err = graphics.NewFramebufferFromTexture(c, texture)
|
||||||
innerImage.Clear(c)
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
img = &Image{framebuffer: framebuffer, texture: texture}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Image{inner: innerImage}, nil
|
if err := img.Clear(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewImageFromImage creates a new image with the given image (img).
|
// NewImageFromImage creates a new image with the given image (img).
|
||||||
|
//
|
||||||
|
// NewImageFromImage generates a new texture and a new framebuffer.
|
||||||
|
// Be careful that image objects will never be released
|
||||||
|
// even though nothing refers the image object and GC works.
|
||||||
|
// It is because there is no way to define finalizers for Go objects if you use GopherJS.
|
||||||
func NewImageFromImage(img image.Image, filter Filter) (*Image, error) {
|
func NewImageFromImage(img image.Image, filter Filter) (*Image, error) {
|
||||||
var innerImage *innerImage
|
var eimg *Image
|
||||||
var err error
|
var err error
|
||||||
ui.Use(func(c *opengl.Context) {
|
ui.Use(func(c *opengl.Context) {
|
||||||
var texture *graphics.Texture
|
var texture *graphics.Texture
|
||||||
|
var framebuffer *graphics.Framebuffer
|
||||||
texture, err = graphics.NewTextureFromImage(c, img, glFilter(c, filter))
|
texture, err = graphics.NewTextureFromImage(c, img, glFilter(c, filter))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
innerImage, err = newInnerImage(c, texture)
|
framebuffer, err = graphics.NewFramebufferFromTexture(c, texture)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eimg = &Image{framebuffer: framebuffer, texture: texture}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Image{inner: innerImage}, nil
|
return eimg, nil
|
||||||
}
|
}
|
||||||
|
42
gamepad.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ebiten
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten/internal/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A GamepadButton represents a gamepad button.
|
||||||
|
type GamepadButton int
|
||||||
|
|
||||||
|
// GamepadButtons
|
||||||
|
const (
|
||||||
|
GamepadButton0 = GamepadButton(ui.GamepadButton0)
|
||||||
|
GamepadButton1 = GamepadButton(ui.GamepadButton1)
|
||||||
|
GamepadButton2 = GamepadButton(ui.GamepadButton2)
|
||||||
|
GamepadButton3 = GamepadButton(ui.GamepadButton3)
|
||||||
|
GamepadButton4 = GamepadButton(ui.GamepadButton4)
|
||||||
|
GamepadButton5 = GamepadButton(ui.GamepadButton5)
|
||||||
|
GamepadButton6 = GamepadButton(ui.GamepadButton6)
|
||||||
|
GamepadButton7 = GamepadButton(ui.GamepadButton7)
|
||||||
|
GamepadButton8 = GamepadButton(ui.GamepadButton8)
|
||||||
|
GamepadButton9 = GamepadButton(ui.GamepadButton9)
|
||||||
|
GamepadButton10 = GamepadButton(ui.GamepadButton10)
|
||||||
|
GamepadButton11 = GamepadButton(ui.GamepadButton11)
|
||||||
|
GamepadButton12 = GamepadButton(ui.GamepadButton12)
|
||||||
|
GamepadButton13 = GamepadButton(ui.GamepadButton13)
|
||||||
|
GamepadButton14 = GamepadButton(ui.GamepadButton14)
|
||||||
|
GamepadButton15 = GamepadButton(ui.GamepadButton15)
|
||||||
|
)
|
13
genkeys.go
@ -22,12 +22,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"github.com/hajimehoshi/ebiten/internal"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -224,12 +223,10 @@ func (k KeyNames) Swap(i, j int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
l, err := ioutil.ReadFile("license.txt")
|
license, err := internal.LicenseComment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(l), "\n")
|
|
||||||
license := "// " + strings.Join(lines[:len(lines)-1], "\n// ")
|
|
||||||
|
|
||||||
notice := "DO NOT EDIT: This file is auto-generated by genkeys.go."
|
notice := "DO NOT EDIT: This file is auto-generated by genkeys.go."
|
||||||
|
|
||||||
@ -263,13 +260,15 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
// NOTE: According to godoc, maps are automatically sorted by key.
|
// NOTE: According to godoc, maps are automatically sorted by key.
|
||||||
tmpl.Execute(f, map[string]interface{}{
|
if err := tmpl.Execute(f, map[string]interface{}{
|
||||||
"License": license,
|
"License": license,
|
||||||
"Notice": notice,
|
"Notice": notice,
|
||||||
"KeyCodeToName": keyCodeToName,
|
"KeyCodeToName": keyCodeToName,
|
||||||
"Codes": codes,
|
"Codes": codes,
|
||||||
"KeyNames": names,
|
"KeyNames": names,
|
||||||
"KeyNamesWithoutMods": namesWithoutMods,
|
"KeyNamesWithoutMods": namesWithoutMods,
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ const (
|
|||||||
FilterLinear
|
FilterLinear
|
||||||
)
|
)
|
||||||
|
|
||||||
func glFilter(c *opengl.Context, filter Filter) opengl.FilterType {
|
func glFilter(c *opengl.Context, filter Filter) opengl.Filter {
|
||||||
switch filter {
|
switch filter {
|
||||||
case FilterNearest:
|
case FilterNearest:
|
||||||
return c.Nearest
|
return c.Nearest
|
||||||
|
@ -29,14 +29,14 @@ func newGraphicsContext(c *opengl.Context, screenWidth, screenHeight, screenScal
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
screen, err := newInnerImage(c, texture)
|
screenF, err := graphics.NewFramebufferFromTexture(c, texture)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
screen := &Image{framebuffer: screenF, texture: texture}
|
||||||
return &graphicsContext{
|
return &graphicsContext{
|
||||||
glContext: c,
|
glContext: c,
|
||||||
defaultR: &innerImage{f, nil},
|
defaultR: &Image{framebuffer: f, texture: nil},
|
||||||
screen: screen,
|
screen: screen,
|
||||||
screenScale: screenScale,
|
screenScale: screenScale,
|
||||||
}, nil
|
}, nil
|
||||||
@ -44,8 +44,8 @@ func newGraphicsContext(c *opengl.Context, screenWidth, screenHeight, screenScal
|
|||||||
|
|
||||||
type graphicsContext struct {
|
type graphicsContext struct {
|
||||||
glContext *opengl.Context
|
glContext *opengl.Context
|
||||||
screen *innerImage
|
screen *Image
|
||||||
defaultR *innerImage
|
defaultR *Image
|
||||||
screenScale int
|
screenScale int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,18 +59,18 @@ func (c *graphicsContext) dispose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *graphicsContext) preUpdate() error {
|
func (c *graphicsContext) preUpdate() error {
|
||||||
return c.screen.Clear(c.glContext)
|
return c.screen.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *graphicsContext) postUpdate() error {
|
func (c *graphicsContext) postUpdate() error {
|
||||||
if err := c.defaultR.Clear(c.glContext); err != nil {
|
if err := c.defaultR.Clear(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
scale := float64(c.screenScale)
|
scale := float64(c.screenScale)
|
||||||
options := &DrawImageOptions{}
|
options := &DrawImageOptions{}
|
||||||
options.GeoM.Scale(scale, scale)
|
options.GeoM.Scale(scale, scale)
|
||||||
if err := c.defaultR.drawImage(c.glContext, c.screen, options); err != nil {
|
if err := c.defaultR.DrawImage(c.screen, options); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
213
image.go
@ -16,6 +16,7 @@ package ebiten
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/hajimehoshi/ebiten/internal"
|
"github.com/hajimehoshi/ebiten/internal"
|
||||||
"github.com/hajimehoshi/ebiten/internal/graphics"
|
"github.com/hajimehoshi/ebiten/internal/graphics"
|
||||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||||
@ -24,141 +25,32 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
type innerImage struct {
|
|
||||||
framebuffer *graphics.Framebuffer
|
|
||||||
texture *graphics.Texture
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInnerImage(c *opengl.Context, texture *graphics.Texture) (*innerImage, error) {
|
|
||||||
framebuffer, err := graphics.NewFramebufferFromTexture(c, texture)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &innerImage{framebuffer, texture}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *innerImage) size() (width, height int) {
|
|
||||||
return i.framebuffer.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *innerImage) Clear(c *opengl.Context) error {
|
|
||||||
return i.Fill(c, color.Transparent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *innerImage) Fill(c *opengl.Context, clr color.Color) error {
|
|
||||||
r, g, b, a := internal.RGBA(clr)
|
|
||||||
return i.framebuffer.Fill(c, r, g, b, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove this in the future.
|
|
||||||
type imageParts []ImagePart
|
|
||||||
|
|
||||||
func (p imageParts) Len() int {
|
|
||||||
return len(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p imageParts) Dst(i int) (x0, y0, x1, y1 int) {
|
|
||||||
dst := &p[i].Dst
|
|
||||||
return dst.Min.X, dst.Min.Y, dst.Max.X, dst.Max.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p imageParts) Src(i int) (x0, y0, x1, y1 int) {
|
|
||||||
src := &p[i].Src
|
|
||||||
return src.Min.X, src.Min.Y, src.Max.X, src.Max.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
type wholeImage struct {
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wholeImage) Len() int {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wholeImage) Dst(i int) (x0, y0, x1, y1 int) {
|
|
||||||
return 0, 0, w.width, w.height
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wholeImage) Src(i int) (x0, y0, x1, y1 int) {
|
|
||||||
return 0, 0, w.width, w.height
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *innerImage) drawImage(c *opengl.Context, img *innerImage, options *DrawImageOptions) error {
|
|
||||||
if options == nil {
|
|
||||||
options = &DrawImageOptions{}
|
|
||||||
}
|
|
||||||
parts := options.ImageParts
|
|
||||||
if parts == nil {
|
|
||||||
dparts := options.Parts
|
|
||||||
if dparts != nil {
|
|
||||||
parts = imageParts(dparts)
|
|
||||||
} else {
|
|
||||||
w, h := img.size()
|
|
||||||
parts = &wholeImage{w, h}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w, h := img.size()
|
|
||||||
quads := &textureQuads{parts: parts, width: w, height: h}
|
|
||||||
return i.framebuffer.DrawTexture(c, img.texture, quads, &options.GeoM, &options.ColorM)
|
|
||||||
}
|
|
||||||
|
|
||||||
func u(x float32, width int) float32 {
|
|
||||||
return x / float32(internal.NextPowerOf2Int(width))
|
|
||||||
}
|
|
||||||
|
|
||||||
func v(y float32, height int) float32 {
|
|
||||||
return y / float32(internal.NextPowerOf2Int(height))
|
|
||||||
}
|
|
||||||
|
|
||||||
type textureQuads struct {
|
|
||||||
parts ImageParts
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *textureQuads) Len() int {
|
|
||||||
return t.parts.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *textureQuads) Vertex(i int) (x0, y0, x1, y1 float32) {
|
|
||||||
ix0, iy0, ix1, iy1 := t.parts.Dst(i)
|
|
||||||
return float32(ix0), float32(iy0), float32(ix1), float32(iy1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *textureQuads) Texture(i int) (u0, v0, u1, v1 float32) {
|
|
||||||
x0, y0, x1, y1 := t.parts.Src(i)
|
|
||||||
w, h := t.width, t.height
|
|
||||||
return u(float32(x0), w), v(float32(y0), h), u(float32(x1), w), v(float32(y1), h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image represents an image.
|
// Image represents an image.
|
||||||
// The pixel format is alpha-premultiplied.
|
// The pixel format is alpha-premultiplied.
|
||||||
// Image implements image.Image.
|
// Image implements image.Image.
|
||||||
type Image struct {
|
type Image struct {
|
||||||
inner *innerImage
|
framebuffer *graphics.Framebuffer
|
||||||
|
texture *graphics.Texture
|
||||||
pixels []uint8
|
pixels []uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the size of the image.
|
// Size returns the size of the image.
|
||||||
func (i *Image) Size() (width, height int) {
|
func (i *Image) Size() (width, height int) {
|
||||||
return i.inner.size()
|
return i.framebuffer.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear resets the pixels of the image into 0.
|
// Clear resets the pixels of the image into 0.
|
||||||
func (i *Image) Clear() (err error) {
|
func (i *Image) Clear() (err error) {
|
||||||
i.pixels = nil
|
return i.Fill(color.Transparent)
|
||||||
ui.Use(func(c *opengl.Context) {
|
|
||||||
err = i.inner.Clear(c)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill fills the image with a solid color.
|
// Fill fills the image with a solid color.
|
||||||
func (i *Image) Fill(clr color.Color) (err error) {
|
func (i *Image) Fill(clr color.Color) (err error) {
|
||||||
i.pixels = nil
|
i.pixels = nil
|
||||||
|
r, g, b, a := internal.RGBA(clr)
|
||||||
ui.Use(func(c *opengl.Context) {
|
ui.Use(func(c *opengl.Context) {
|
||||||
err = i.inner.Fill(c, clr)
|
// TODO: Change to pass color.Color
|
||||||
|
err = i.framebuffer.Fill(c, r, g, b, a)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -181,20 +73,68 @@ func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) {
|
|||||||
if i == image {
|
if i == image {
|
||||||
return errors.New("Image.DrawImage: image should be different from the receiver")
|
return errors.New("Image.DrawImage: image should be different from the receiver")
|
||||||
}
|
}
|
||||||
return i.drawImage(image.inner, options)
|
i.pixels = nil
|
||||||
|
if options == nil {
|
||||||
|
options = &DrawImageOptions{}
|
||||||
|
}
|
||||||
|
parts := options.ImageParts
|
||||||
|
if parts == nil {
|
||||||
|
// Check options.Parts for backward-compatibility.
|
||||||
|
dparts := options.Parts
|
||||||
|
if dparts != nil {
|
||||||
|
parts = imageParts(dparts)
|
||||||
|
} else {
|
||||||
|
w, h := image.Size()
|
||||||
|
parts = &wholeImage{w, h}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w, h := image.Size()
|
||||||
|
quads := &textureQuads{parts: parts, width: w, height: h}
|
||||||
|
ui.Use(func(c *opengl.Context) {
|
||||||
|
err = i.framebuffer.DrawTexture(c, image.texture, quads, &options.GeoM, &options.ColorM)
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) drawImage(image *innerImage, option *DrawImageOptions) (err error) {
|
// DrawLine draws a line.
|
||||||
i.pixels = nil
|
func (i *Image) DrawLine(x0, y0, x1, y1 int, clr color.Color) error {
|
||||||
|
return i.DrawLines(&line{x0, y0, x1, y1, clr})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawLines draws lines.
|
||||||
|
func (i *Image) DrawLines(lines Lines) (err error) {
|
||||||
ui.Use(func(c *opengl.Context) {
|
ui.Use(func(c *opengl.Context) {
|
||||||
err = i.inner.drawImage(c, image, option)
|
err = i.framebuffer.DrawLines(c, lines)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawRect draws a rectangle.
|
||||||
|
func (i *Image) DrawRect(x, y, width, height int, clr color.Color) error {
|
||||||
|
return i.DrawLines(&rectsAsLines{&rect{x, y, width, height, clr}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawRect draws rectangles.
|
||||||
|
func (i *Image) DrawRects(rects Rects) error {
|
||||||
|
return i.DrawLines(&rectsAsLines{rects})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawFilledRect draws a filled rectangle.
|
||||||
|
func (i *Image) DrawFilledRect(x, y, width, height int, clr color.Color) error {
|
||||||
|
return i.DrawFilledRects(&rect{x, y, width, height, clr})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawFilledRects draws filled rectangles on the image.
|
||||||
|
func (i *Image) DrawFilledRects(rects Rects) (err error) {
|
||||||
|
ui.Use(func(c *opengl.Context) {
|
||||||
|
err = i.framebuffer.DrawFilledRects(c, rects)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bounds returns the bounds of the image.
|
// Bounds returns the bounds of the image.
|
||||||
func (i *Image) Bounds() image.Rectangle {
|
func (i *Image) Bounds() image.Rectangle {
|
||||||
w, h := i.inner.size()
|
w, h := i.Size()
|
||||||
return image.Rect(0, 0, w, h)
|
return image.Rect(0, 0, w, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,30 +150,35 @@ func (i *Image) At(x, y int) color.Color {
|
|||||||
if i.pixels == nil {
|
if i.pixels == nil {
|
||||||
ui.Use(func(c *opengl.Context) {
|
ui.Use(func(c *opengl.Context) {
|
||||||
var err error
|
var err error
|
||||||
i.pixels, err = i.inner.texture.Pixels(c)
|
i.pixels, err = i.framebuffer.Pixels(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
w, _ := i.inner.size()
|
w, _ := i.Size()
|
||||||
w = internal.NextPowerOf2Int(w)
|
w = internal.NextPowerOf2Int(w)
|
||||||
idx := 4*x + 4*y*w
|
idx := 4*x + 4*y*w
|
||||||
r, g, b, a := i.pixels[idx], i.pixels[idx+1], i.pixels[idx+2], i.pixels[idx+3]
|
r, g, b, a := i.pixels[idx], i.pixels[idx+1], i.pixels[idx+2], i.pixels[idx+3]
|
||||||
return color.RGBA{r, g, b, a}
|
return color.RGBA{r, g, b, a}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated (as of 1.1.0-alpha): Use ImageParts instead.
|
// ReplacePixels replaces the pixels of the image with p.
|
||||||
type ImagePart struct {
|
//
|
||||||
Dst image.Rectangle
|
// The given p must represent RGBA pre-multiplied alpha values. len(p) must equal to 4 * (image width) * (image height).
|
||||||
Src image.Rectangle
|
//
|
||||||
}
|
// This function may be slow (as for implementation, this calls glTexSubImage2D).
|
||||||
|
func (i *Image) ReplacePixels(p []uint8) error {
|
||||||
// An ImageParts represents the parts of the destination image and the parts of the source image.
|
w, h := i.Size()
|
||||||
type ImageParts interface {
|
l := 4 * w * h
|
||||||
Len() int
|
if len(p) != l {
|
||||||
Dst(i int) (x0, y0, x1, y1 int)
|
return errors.New(fmt.Sprintf("p's length must be %d", l))
|
||||||
Src(i int) (x0, y0, x1, y1 int)
|
}
|
||||||
|
var err error
|
||||||
|
ui.Use(func(c *opengl.Context) {
|
||||||
|
err = i.texture.ReplacePixels(c, p)
|
||||||
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// A DrawImageOptions represents options to render an image on an image.
|
// A DrawImageOptions represents options to render an image on an image.
|
||||||
|
@ -18,19 +18,29 @@ import (
|
|||||||
. "github.com/hajimehoshi/ebiten"
|
. "github.com/hajimehoshi/ebiten"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"os"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func openImage(path string) (*Image, image.Image, error) {
|
var ebitenImageBin = ""
|
||||||
file, err := os.Open(path)
|
|
||||||
|
func openImage(path string) (image.Image, error) {
|
||||||
|
file, err := readFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
img, _, err := image.Decode(file)
|
img, _, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openEbitenImage(path string) (*Image, image.Image, error) {
|
||||||
|
img, err := openImage(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -50,22 +60,22 @@ func diff(x, y uint8) uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestImagePixels(t *testing.T) {
|
func TestImagePixels(t *testing.T) {
|
||||||
eimg, img, err := openImage("testdata/ebiten.png")
|
img0, img, err := openEbitenImage("testdata/ebiten.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if got := eimg.Bounds().Size(); got != img.Bounds().Size() {
|
if got := img0.Bounds().Size(); got != img.Bounds().Size() {
|
||||||
t.Errorf("img size: got %d; want %d", got, img.Bounds().Size())
|
t.Errorf("img size: got %d; want %d", got, img.Bounds().Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
for j := 0; j < eimg.Bounds().Size().Y; j++ {
|
for j := 0; j < img0.Bounds().Size().Y; j++ {
|
||||||
for i := 0; i < eimg.Bounds().Size().X; i++ {
|
for i := 0; i < img0.Bounds().Size().X; i++ {
|
||||||
got := eimg.At(i, j)
|
got := img0.At(i, j)
|
||||||
want := color.RGBAModel.Convert(img.At(i, j))
|
want := color.RGBAModel.Convert(img.At(i, j))
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("img At(%d, %d): got %#v; want %#v", i, j, got, want)
|
t.Errorf("img0 At(%d, %d): got %#v; want %#v", i, j, got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +85,7 @@ func TestImageComposition(t *testing.T) {
|
|||||||
img2Color := color.NRGBA{0x24, 0x3f, 0x6a, 0x88}
|
img2Color := color.NRGBA{0x24, 0x3f, 0x6a, 0x88}
|
||||||
img3Color := color.NRGBA{0x85, 0xa3, 0x08, 0xd3}
|
img3Color := color.NRGBA{0x85, 0xa3, 0x08, 0xd3}
|
||||||
|
|
||||||
img1, _, err := openImage("testdata/ebiten.png")
|
img1, _, err := openEbitenImage("testdata/ebiten.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
@ -135,7 +145,7 @@ func TestImageComposition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestImageSelf(t *testing.T) {
|
func TestImageSelf(t *testing.T) {
|
||||||
img, _, err := openImage("testdata/ebiten.png")
|
img, _, err := openEbitenImage("testdata/ebiten.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
@ -145,4 +155,61 @@ func TestImageSelf(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageDotByDotInversion(t *testing.T) {
|
||||||
|
img0, _, err := openEbitenImage("testdata/ebiten.png")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w, h := img0.Size()
|
||||||
|
img1, err := NewImage(w, h, FilterNearest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
op := &DrawImageOptions{}
|
||||||
|
op.GeoM.Rotate(2 * math.Pi / 2)
|
||||||
|
op.GeoM.Translate(float64(w), float64(h))
|
||||||
|
img1.DrawImage(img0, op)
|
||||||
|
|
||||||
|
for j := 0; j < h; j++ {
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
c0 := img0.At(i, j).(color.RGBA)
|
||||||
|
c1 := img1.At(w-i-1, h-j-1).(color.RGBA)
|
||||||
|
if c0 != c1 {
|
||||||
|
t.Errorf("img0.At(%[1]d, %[2]d) should equal to img1.At(%[1]d, %[2]d) but not: %[3]v vs %[4]v", i, j, c0, c1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplacePixels(t *testing.T) {
|
||||||
|
origImg, err := openImage("testdata/ebiten.png")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Convert to RGBA
|
||||||
|
img := image.NewRGBA(origImg.Bounds())
|
||||||
|
draw.Draw(img, img.Bounds(), origImg, image.ZP, draw.Src)
|
||||||
|
|
||||||
|
size := img.Bounds().Size()
|
||||||
|
img0, err := NewImage(size.X, size.Y, FilterNearest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img0.ReplacePixels(img.Pix)
|
||||||
|
for j := 0; j < img0.Bounds().Size().Y; j++ {
|
||||||
|
for i := 0; i < img0.Bounds().Size().X; i++ {
|
||||||
|
got := img.At(i, j)
|
||||||
|
want := img0.At(i, j)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("img0 At(%d, %d): got %#v; want %#v", i, j, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add more tests (e.g. DrawImage with color matrix)
|
// TODO: Add more tests (e.g. DrawImage with color matrix)
|
||||||
|
96
imageparts.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ebiten
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten/internal"
|
||||||
|
"image"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated (as of 1.1.0-alpha): Use ImageParts instead.
|
||||||
|
type ImagePart struct {
|
||||||
|
Dst image.Rectangle
|
||||||
|
Src image.Rectangle
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ImageParts represents the parts of the destination image and the parts of the source image.
|
||||||
|
type ImageParts interface {
|
||||||
|
Len() int
|
||||||
|
Dst(i int) (x0, y0, x1, y1 int)
|
||||||
|
Src(i int) (x0, y0, x1, y1 int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this in the future.
|
||||||
|
type imageParts []ImagePart
|
||||||
|
|
||||||
|
func (p imageParts) Len() int {
|
||||||
|
return len(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p imageParts) Dst(i int) (x0, y0, x1, y1 int) {
|
||||||
|
dst := &p[i].Dst
|
||||||
|
return dst.Min.X, dst.Min.Y, dst.Max.X, dst.Max.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p imageParts) Src(i int) (x0, y0, x1, y1 int) {
|
||||||
|
src := &p[i].Src
|
||||||
|
return src.Min.X, src.Min.Y, src.Max.X, src.Max.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
type wholeImage struct {
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wholeImage) Len() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wholeImage) Dst(i int) (x0, y0, x1, y1 int) {
|
||||||
|
return 0, 0, w.width, w.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wholeImage) Src(i int) (x0, y0, x1, y1 int) {
|
||||||
|
return 0, 0, w.width, w.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func u(x int, width int) int {
|
||||||
|
return math.MaxInt16 * x / internal.NextPowerOf2Int(width)
|
||||||
|
}
|
||||||
|
|
||||||
|
func v(y int, height int) int {
|
||||||
|
return math.MaxInt16 * y / internal.NextPowerOf2Int(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
type textureQuads struct {
|
||||||
|
parts ImageParts
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *textureQuads) Len() int {
|
||||||
|
return t.parts.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *textureQuads) Vertex(i int) (x0, y0, x1, y1 int) {
|
||||||
|
return t.parts.Dst(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *textureQuads) Texture(i int) (u0, v0, u1, v1 int) {
|
||||||
|
x0, y0, x1, y1 := t.parts.Src(i)
|
||||||
|
w, h := t.width, t.height
|
||||||
|
return u(x0, w), v(y0, h), u(x1, w), v(y1, h)
|
||||||
|
}
|
@ -53,7 +53,7 @@ func text_png() (*asset, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := bindata_file_info{name: "text.png", size: 2058, mode: os.FileMode(420), modTime: time.Unix(1419689280, 0)}
|
info := bindata_file_info{name: "text.png", size: 2058, mode: os.FileMode(420), modTime: time.Unix(1420820510, 0)}
|
||||||
a := &asset{bytes: bytes, info: info}
|
a := &asset{bytes: bytes, info: info}
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/internal"
|
"github.com/hajimehoshi/ebiten/internal"
|
||||||
"github.com/hajimehoshi/ebiten/internal/graphics/internal/shader"
|
"github.com/hajimehoshi/ebiten/internal/graphics/internal/shader"
|
||||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||||
|
"image/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
func orthoProjectionMatrix(left, right, bottom, top int) *[4][4]float64 {
|
func orthoProjectionMatrix(left, right, bottom, top int) *[4][4]float64 {
|
||||||
@ -94,21 +95,55 @@ type Matrix interface {
|
|||||||
|
|
||||||
type TextureQuads interface {
|
type TextureQuads interface {
|
||||||
Len() int
|
Len() int
|
||||||
Vertex(i int) (x0, y0, x1, y1 float32)
|
Vertex(i int) (x0, y0, x1, y1 int)
|
||||||
Texture(i int) (u0, v0, u1, v1 float32)
|
Texture(i int) (u0, v0, u1, v1 int)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Framebuffer) Fill(c *opengl.Context, r, g, b, a float64) error {
|
func (f *Framebuffer) Fill(c *opengl.Context, r, g, b, a float64) error {
|
||||||
if err := f.setAsViewport(c); err != nil {
|
if err := f.setAsViewport(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.FillFramebuffer(f.native, r, g, b, a)
|
return c.FillFramebuffer(r, g, b, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Framebuffer) DrawTexture(c *opengl.Context, t *Texture, quads TextureQuads, geo, clr Matrix) error {
|
func (f *Framebuffer) DrawTexture(c *opengl.Context, t *Texture, quads TextureQuads, geo, clr Matrix) error {
|
||||||
if err := f.setAsViewport(c); err != nil {
|
if err := f.setAsViewport(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
projectionMatrix := f.projectionMatrix()
|
p := f.projectionMatrix()
|
||||||
return shader.DrawTexture(c, t.native, projectionMatrix, quads, geo, clr)
|
return shader.DrawTexture(c, t.native, p, quads, geo, clr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lines interface {
|
||||||
|
Len() int
|
||||||
|
Points(i int) (x0, y0, x1, y1 int)
|
||||||
|
Color(i int) color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Framebuffer) DrawLines(c *opengl.Context, lines Lines) error {
|
||||||
|
if err := f.setAsViewport(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p := f.projectionMatrix()
|
||||||
|
return shader.DrawLines(c, p, lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rects interface {
|
||||||
|
Len() int
|
||||||
|
Rect(i int) (x, y, width, height int)
|
||||||
|
Color(i int) color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Framebuffer) DrawFilledRects(c *opengl.Context, rects Rects) error {
|
||||||
|
if err := f.setAsViewport(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p := f.projectionMatrix()
|
||||||
|
return shader.DrawFilledRects(c, p, rects)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Framebuffer) Pixels(c *opengl.Context) ([]uint8, error) {
|
||||||
|
w, h := f.Size()
|
||||||
|
w, h = internal.NextPowerOf2Int(w), internal.NextPowerOf2Int(h)
|
||||||
|
return c.FramebufferPixels(f.native, w, h)
|
||||||
}
|
}
|
||||||
|
181
internal/graphics/internal/shader/draw.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// Copyright 2014 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package shader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
func glMatrix(m *[4][4]float64) []float32 {
|
||||||
|
return []float32{
|
||||||
|
float32(m[0][0]), float32(m[1][0]), float32(m[2][0]), float32(m[3][0]),
|
||||||
|
float32(m[0][1]), float32(m[1][1]), float32(m[2][1]), float32(m[3][1]),
|
||||||
|
float32(m[0][2]), float32(m[1][2]), float32(m[2][2]), float32(m[3][2]),
|
||||||
|
float32(m[0][3]), float32(m[1][3]), float32(m[2][3]), float32(m[3][3]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Matrix interface {
|
||||||
|
Element(i, j int) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextureQuads interface {
|
||||||
|
Len() int
|
||||||
|
Vertex(i int) (x0, y0, x1, y1 int)
|
||||||
|
Texture(i int) (u0, v0, u1, v1 int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var vertices = make([]int16, 0, 4*8*quadsMaxNum)
|
||||||
|
|
||||||
|
var initialized = false
|
||||||
|
|
||||||
|
func DrawTexture(c *opengl.Context, texture opengl.Texture, projectionMatrix *[4][4]float64, quads TextureQuads, geo Matrix, color Matrix) error {
|
||||||
|
// TODO: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
|
||||||
|
// Let's use them to compare to len(quads) in the future.
|
||||||
|
|
||||||
|
if !initialized {
|
||||||
|
if err := initialize(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if quads.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if quadsMaxNum < quads.Len() {
|
||||||
|
return errors.New(fmt.Sprintf("len(quads) must be equal to or less than %d", quadsMaxNum))
|
||||||
|
}
|
||||||
|
|
||||||
|
f := useProgramForTexture(c, glMatrix(projectionMatrix), texture, geo, color)
|
||||||
|
defer f.FinishProgram()
|
||||||
|
|
||||||
|
vertices := vertices[0:0]
|
||||||
|
num := 0
|
||||||
|
for i := 0; i < quads.Len(); i++ {
|
||||||
|
x0, y0, x1, y1 := quads.Vertex(i)
|
||||||
|
u0, v0, u1, v1 := quads.Texture(i)
|
||||||
|
if x0 == x1 || y0 == y1 || u0 == u1 || v0 == v1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vertices = append(vertices,
|
||||||
|
int16(x0), int16(y0), int16(u0), int16(v0),
|
||||||
|
int16(x1), int16(y0), int16(u1), int16(v0),
|
||||||
|
int16(x0), int16(y1), int16(u0), int16(v1),
|
||||||
|
int16(x1), int16(y1), int16(u1), int16(v1),
|
||||||
|
)
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
if len(vertices) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.BufferSubData(c.ArrayBuffer, vertices)
|
||||||
|
c.DrawElements(c.Triangles, 6*num)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lines interface {
|
||||||
|
Len() int
|
||||||
|
Points(i int) (x0, y0, x1, y1 int)
|
||||||
|
Color(i int) color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func DrawLines(c *opengl.Context, projectionMatrix *[4][4]float64, lines Lines) error {
|
||||||
|
if !initialized {
|
||||||
|
if err := initialize(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if lines.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f := useProgramForLines(c, glMatrix(projectionMatrix))
|
||||||
|
defer f.FinishProgram()
|
||||||
|
|
||||||
|
vertices := vertices[0:0]
|
||||||
|
num := 0
|
||||||
|
for i := 0; i < lines.Len(); i++ {
|
||||||
|
x0, y0, x1, y1 := lines.Points(i)
|
||||||
|
if x0 == x1 && y0 == y1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, g, b, a := lines.Color(i).RGBA()
|
||||||
|
vertices = append(vertices,
|
||||||
|
int16(x0), int16(y0), int16(r), int16(g), int16(b), int16(a),
|
||||||
|
int16(x1), int16(y1), int16(r), int16(g), int16(b), int16(a),
|
||||||
|
)
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
if len(vertices) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.BufferSubData(c.ArrayBuffer, vertices)
|
||||||
|
c.DrawElements(c.Lines, 2*num)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rects interface {
|
||||||
|
Len() int
|
||||||
|
Rect(i int) (x, y, width, height int)
|
||||||
|
Color(i int) color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func DrawFilledRects(c *opengl.Context, projectionMatrix *[4][4]float64, rects Rects) error {
|
||||||
|
if !initialized {
|
||||||
|
if err := initialize(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if rects.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f := useProgramForRects(c, glMatrix(projectionMatrix))
|
||||||
|
defer f.FinishProgram()
|
||||||
|
|
||||||
|
vertices := vertices[0:0]
|
||||||
|
num := 0
|
||||||
|
for i := 0; i < rects.Len(); i++ {
|
||||||
|
x, y, w, h := rects.Rect(i)
|
||||||
|
if w == 0 || h == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
x0, y0, x1, y1 := x, y, x+w, y+h
|
||||||
|
r, g, b, a := rects.Color(i).RGBA()
|
||||||
|
vertices = append(vertices,
|
||||||
|
int16(x0), int16(y0), int16(r), int16(g), int16(b), int16(a),
|
||||||
|
int16(x1), int16(y0), int16(r), int16(g), int16(b), int16(a),
|
||||||
|
int16(x0), int16(y1), int16(r), int16(g), int16(b), int16(a),
|
||||||
|
int16(x1), int16(y1), int16(r), int16(g), int16(b), int16(a),
|
||||||
|
)
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
if len(vertices) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.BufferSubData(c.ArrayBuffer, vertices)
|
||||||
|
c.DrawElements(c.Triangles, 6*num)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,96 +0,0 @@
|
|||||||
// Copyright 2014 Hajime Hoshi
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package shader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
|
||||||
)
|
|
||||||
|
|
||||||
func glMatrix(m *[4][4]float64) []float32 {
|
|
||||||
return []float32{
|
|
||||||
float32(m[0][0]), float32(m[1][0]), float32(m[2][0]), float32(m[3][0]),
|
|
||||||
float32(m[0][1]), float32(m[1][1]), float32(m[2][1]), float32(m[3][1]),
|
|
||||||
float32(m[0][2]), float32(m[1][2]), float32(m[2][2]), float32(m[3][2]),
|
|
||||||
float32(m[0][3]), float32(m[1][3]), float32(m[2][3]), float32(m[3][3]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Matrix interface {
|
|
||||||
Element(i, j int) float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type TextureQuads interface {
|
|
||||||
Len() int
|
|
||||||
Vertex(i int) (x0, y0, x1, y1 float32)
|
|
||||||
Texture(i int) (u0, v0, u1, v1 float32)
|
|
||||||
}
|
|
||||||
|
|
||||||
var initialized = false
|
|
||||||
|
|
||||||
func DrawTexture(c *opengl.Context, texture opengl.Texture, projectionMatrix *[4][4]float64, quads TextureQuads, geo Matrix, color Matrix) error {
|
|
||||||
// unsafe.SizeOf can't be used because unsafe doesn't work with GopherJS.
|
|
||||||
const float32Size = 4
|
|
||||||
|
|
||||||
// TODO: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
|
|
||||||
// Let's use them to compare to len(quads).
|
|
||||||
const stride = 4 * 4
|
|
||||||
if !initialized {
|
|
||||||
if err := initialize(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
initialized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if quads.Len() == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
program := useProgramColorMatrix(c, glMatrix(projectionMatrix), geo, color)
|
|
||||||
|
|
||||||
// We don't have to call gl.ActiveTexture here: GL_TEXTURE0 is the default active texture
|
|
||||||
// See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml
|
|
||||||
c.BindTexture(texture)
|
|
||||||
|
|
||||||
c.EnableVertexAttribArray(program, "vertex")
|
|
||||||
c.EnableVertexAttribArray(program, "tex_coord")
|
|
||||||
defer func() {
|
|
||||||
c.DisableVertexAttribArray(program, "tex_coord")
|
|
||||||
c.DisableVertexAttribArray(program, "vertex")
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.VertexAttribPointer(program, "vertex", stride, uintptr(float32Size*0))
|
|
||||||
c.VertexAttribPointer(program, "tex_coord", stride, uintptr(float32Size*2))
|
|
||||||
|
|
||||||
vertices := make([]float32, 0, stride*quads.Len())
|
|
||||||
for i := 0; i < quads.Len(); i++ {
|
|
||||||
x0, y0, x1, y1 := quads.Vertex(i)
|
|
||||||
u0, v0, u1, v1 := quads.Texture(i)
|
|
||||||
if x0 == x1 || y0 == y1 || u0 == u1 || v0 == v1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
vertices = append(vertices,
|
|
||||||
x0, y0, u0, v0,
|
|
||||||
x1, y0, u1, v0,
|
|
||||||
x0, y1, u0, v1,
|
|
||||||
x1, y1, u1, v1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if len(vertices) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.BufferSubData(c.ArrayBuffer, vertices)
|
|
||||||
c.DrawElements(6 * len(vertices) / 16)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -16,40 +16,88 @@ package shader
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||||
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
var programColorMatrix opengl.Program
|
var (
|
||||||
|
indexBufferLines opengl.Buffer
|
||||||
|
indexBufferQuads opengl.Buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
programTexture opengl.Program
|
||||||
|
programSolidRect opengl.Program
|
||||||
|
programSolidLine opengl.Program
|
||||||
|
)
|
||||||
|
|
||||||
|
const indicesNum = math.MaxUint16 + 1
|
||||||
|
const quadsMaxNum = indicesNum / 6
|
||||||
|
|
||||||
|
// unsafe.SizeOf can't be used because unsafe doesn't work with GopherJS.
|
||||||
|
const int16Size = 2
|
||||||
|
const float32Size = 4
|
||||||
|
|
||||||
func initialize(c *opengl.Context) error {
|
func initialize(c *opengl.Context) error {
|
||||||
const size = 10000
|
shaderVertexModelviewNative, err := c.NewShader(c.VertexShader, shader(c, shaderVertexModelview))
|
||||||
const uint16Size = 2
|
|
||||||
|
|
||||||
shaderVertexNative, err := c.NewShader(c.VertexShader, shader(c, shaderVertex))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer c.DeleteShader(shaderVertexNative)
|
defer c.DeleteShader(shaderVertexModelviewNative)
|
||||||
|
|
||||||
shaderColorMatrixNative, err := c.NewShader(c.FragmentShader, shader(c, shaderColorMatrix))
|
shaderVertexColorNative, err := c.NewShader(c.VertexShader, shader(c, shaderVertexColor))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer c.DeleteShader(shaderColorMatrixNative)
|
defer c.DeleteShader(shaderVertexColorNative)
|
||||||
|
|
||||||
shaders := []opengl.Shader{
|
shaderVertexColorLineNative, err := c.NewShader(c.VertexShader, shader(c, shaderVertexColorLine))
|
||||||
shaderVertexNative,
|
if err != nil {
|
||||||
shaderColorMatrixNative,
|
return err
|
||||||
}
|
}
|
||||||
programColorMatrix, err = c.NewProgram(shaders)
|
defer c.DeleteShader(shaderVertexColorLineNative)
|
||||||
|
|
||||||
|
shaderFragmentTextureNative, err := c.NewShader(c.FragmentShader, shader(c, shaderFragmentTexture))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.DeleteShader(shaderFragmentTextureNative)
|
||||||
|
|
||||||
|
shaderFragmentSolidNative, err := c.NewShader(c.FragmentShader, shader(c, shaderFragmentSolid))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.DeleteShader(shaderFragmentSolidNative)
|
||||||
|
|
||||||
|
programTexture, err = c.NewProgram([]opengl.Shader{
|
||||||
|
shaderVertexModelviewNative,
|
||||||
|
shaderFragmentTextureNative,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const stride = 4 * 4
|
programSolidRect, err = c.NewProgram([]opengl.Shader{
|
||||||
c.NewBuffer(c.ArrayBuffer, stride*size, c.DynamicDraw)
|
shaderVertexColorNative,
|
||||||
|
shaderFragmentSolidNative,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
indices := make([]uint16, 6*size)
|
programSolidLine, err = c.NewProgram([]opengl.Shader{
|
||||||
for i := uint16(0); i < size; i++ {
|
shaderVertexColorLineNative,
|
||||||
|
shaderFragmentSolidNative,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 16 [bytse] is an arbitrary number which seems enough to draw anything. Fix this if necessary.
|
||||||
|
const stride = 16
|
||||||
|
c.NewBuffer(c.ArrayBuffer, 4*stride*quadsMaxNum, c.DynamicDraw)
|
||||||
|
|
||||||
|
indices := make([]uint16, 6*quadsMaxNum)
|
||||||
|
for i := uint16(0); i < quadsMaxNum; i++ {
|
||||||
indices[6*i+0] = 4*i + 0
|
indices[6*i+0] = 4*i + 0
|
||||||
indices[6*i+1] = 4*i + 1
|
indices[6*i+1] = 4*i + 1
|
||||||
indices[6*i+2] = 4*i + 2
|
indices[6*i+2] = 4*i + 2
|
||||||
@ -57,19 +105,33 @@ func initialize(c *opengl.Context) error {
|
|||||||
indices[6*i+4] = 4*i + 2
|
indices[6*i+4] = 4*i + 2
|
||||||
indices[6*i+5] = 4*i + 3
|
indices[6*i+5] = 4*i + 3
|
||||||
}
|
}
|
||||||
c.NewBuffer(c.ElementArrayBuffer, indices, c.StaticDraw)
|
indexBufferQuads = c.NewBuffer(c.ElementArrayBuffer, indices, c.StaticDraw)
|
||||||
|
|
||||||
|
indices = make([]uint16, indicesNum)
|
||||||
|
for i := 0; i < len(indices); i++ {
|
||||||
|
indices[i] = uint16(i)
|
||||||
|
}
|
||||||
|
indexBufferLines = c.NewBuffer(c.ElementArrayBuffer, indices, c.StaticDraw)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastProgram opengl.Program
|
var lastProgram opengl.Program
|
||||||
|
|
||||||
func useProgramColorMatrix(c *opengl.Context, projectionMatrix []float32, geo Matrix, color Matrix) opengl.Program {
|
type programFinisher func()
|
||||||
if lastProgram != programColorMatrix {
|
|
||||||
c.UseProgram(programColorMatrix)
|
func (p programFinisher) FinishProgram() {
|
||||||
lastProgram = programColorMatrix
|
p()
|
||||||
|
}
|
||||||
|
|
||||||
|
func useProgramForTexture(c *opengl.Context, projectionMatrix []float32, texture opengl.Texture, geo Matrix, color Matrix) programFinisher {
|
||||||
|
if !lastProgram.Equals(programTexture) {
|
||||||
|
c.UseProgram(programTexture)
|
||||||
|
lastProgram = programTexture
|
||||||
}
|
}
|
||||||
program := programColorMatrix
|
program := programTexture
|
||||||
|
|
||||||
|
c.BindElementArrayBuffer(indexBufferQuads)
|
||||||
|
|
||||||
c.UniformFloats(program, "projection_matrix", projectionMatrix)
|
c.UniformFloats(program, "projection_matrix", projectionMatrix)
|
||||||
|
|
||||||
@ -107,5 +169,65 @@ func useProgramColorMatrix(c *opengl.Context, projectionMatrix []float32, geo Ma
|
|||||||
}
|
}
|
||||||
c.UniformFloats(program, "color_matrix_translation", glColorMatrixTranslation)
|
c.UniformFloats(program, "color_matrix_translation", glColorMatrixTranslation)
|
||||||
|
|
||||||
return program
|
// We don't have to call gl.ActiveTexture here: GL_TEXTURE0 is the default active texture
|
||||||
|
// See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml
|
||||||
|
c.BindTexture(texture)
|
||||||
|
|
||||||
|
c.EnableVertexAttribArray(program, "vertex")
|
||||||
|
c.EnableVertexAttribArray(program, "tex_coord")
|
||||||
|
|
||||||
|
c.VertexAttribPointer(program, "vertex", true, false, int16Size*4, 2, uintptr(int16Size*0))
|
||||||
|
c.VertexAttribPointer(program, "tex_coord", true, true, int16Size*4, 2, uintptr(int16Size*2))
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
c.DisableVertexAttribArray(program, "tex_coord")
|
||||||
|
c.DisableVertexAttribArray(program, "vertex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func useProgramForLines(c *opengl.Context, projectionMatrix []float32) programFinisher {
|
||||||
|
if !lastProgram.Equals(programSolidLine) {
|
||||||
|
c.UseProgram(programSolidLine)
|
||||||
|
lastProgram = programSolidLine
|
||||||
|
}
|
||||||
|
program := programSolidLine
|
||||||
|
|
||||||
|
c.BindElementArrayBuffer(indexBufferLines)
|
||||||
|
|
||||||
|
c.UniformFloats(program, "projection_matrix", projectionMatrix)
|
||||||
|
|
||||||
|
c.EnableVertexAttribArray(program, "vertex")
|
||||||
|
c.EnableVertexAttribArray(program, "color")
|
||||||
|
|
||||||
|
// TODO: Change to floats?
|
||||||
|
c.VertexAttribPointer(program, "vertex", true, false, int16Size*6, 2, uintptr(int16Size*0))
|
||||||
|
c.VertexAttribPointer(program, "color", false, true, int16Size*6, 4, uintptr(int16Size*2))
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
c.DisableVertexAttribArray(program, "color")
|
||||||
|
c.DisableVertexAttribArray(program, "vertex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func useProgramForRects(c *opengl.Context, projectionMatrix []float32) programFinisher {
|
||||||
|
if !lastProgram.Equals(programSolidRect) {
|
||||||
|
c.UseProgram(programSolidRect)
|
||||||
|
lastProgram = programSolidRect
|
||||||
|
}
|
||||||
|
program := programSolidRect
|
||||||
|
|
||||||
|
c.BindElementArrayBuffer(indexBufferQuads)
|
||||||
|
|
||||||
|
c.UniformFloats(program, "projection_matrix", projectionMatrix)
|
||||||
|
|
||||||
|
c.EnableVertexAttribArray(program, "vertex")
|
||||||
|
c.EnableVertexAttribArray(program, "color")
|
||||||
|
|
||||||
|
c.VertexAttribPointer(program, "vertex", true, false, int16Size*6, 2, uintptr(int16Size*0))
|
||||||
|
c.VertexAttribPointer(program, "color", false, true, int16Size*6, 4, uintptr(int16Size*2))
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
c.DisableVertexAttribArray(program, "color")
|
||||||
|
c.DisableVertexAttribArray(program, "vertex")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,11 @@ import (
|
|||||||
type shaderId int
|
type shaderId int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
shaderVertex shaderId = iota
|
shaderVertexModelview shaderId = iota
|
||||||
shaderColorMatrix
|
shaderVertexColor
|
||||||
|
shaderVertexColorLine
|
||||||
|
shaderFragmentTexture
|
||||||
|
shaderFragmentSolid
|
||||||
)
|
)
|
||||||
|
|
||||||
func shader(c *opengl.Context, id shaderId) string {
|
func shader(c *opengl.Context, id shaderId) string {
|
||||||
@ -36,7 +39,7 @@ func shader(c *opengl.Context, id shaderId) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var shaders = map[shaderId]string{
|
var shaders = map[shaderId]string{
|
||||||
shaderVertex: `
|
shaderVertexModelview: `
|
||||||
uniform highp mat4 projection_matrix;
|
uniform highp mat4 projection_matrix;
|
||||||
uniform highp mat4 modelview_matrix;
|
uniform highp mat4 modelview_matrix;
|
||||||
attribute highp vec2 vertex;
|
attribute highp vec2 vertex;
|
||||||
@ -48,7 +51,29 @@ void main(void) {
|
|||||||
gl_Position = projection_matrix * modelview_matrix * vec4(vertex, 0, 1);
|
gl_Position = projection_matrix * modelview_matrix * vec4(vertex, 0, 1);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
shaderColorMatrix: `
|
shaderVertexColor: `
|
||||||
|
uniform highp mat4 projection_matrix;
|
||||||
|
attribute highp vec2 vertex;
|
||||||
|
attribute lowp vec4 color;
|
||||||
|
varying lowp vec4 vertex_out_color;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
vertex_out_color = color;
|
||||||
|
gl_Position = projection_matrix * vec4(vertex, 0, 1);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
shaderVertexColorLine: `
|
||||||
|
uniform highp mat4 projection_matrix;
|
||||||
|
attribute highp vec2 vertex;
|
||||||
|
attribute lowp vec4 color;
|
||||||
|
varying lowp vec4 vertex_out_color;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
vertex_out_color = color;
|
||||||
|
gl_Position = projection_matrix * vec4(vertex + vec2(0.5, 0.5), 0, 1);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
shaderFragmentTexture: `
|
||||||
uniform lowp sampler2D texture;
|
uniform lowp sampler2D texture;
|
||||||
uniform lowp mat4 color_matrix;
|
uniform lowp mat4 color_matrix;
|
||||||
uniform lowp vec4 color_matrix_translation;
|
uniform lowp vec4 color_matrix_translation;
|
||||||
@ -69,5 +94,12 @@ void main(void) {
|
|||||||
|
|
||||||
gl_FragColor = color;
|
gl_FragColor = color;
|
||||||
}
|
}
|
||||||
|
`,
|
||||||
|
shaderFragmentSolid: `
|
||||||
|
varying lowp vec4 vertex_out_color;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = vertex_out_color;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ func adjustImageForTexture(img image.Image) *image.RGBA {
|
|||||||
internal.NextPowerOf2Int(height),
|
internal.NextPowerOf2Int(height),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if nrgba, ok := img.(*image.RGBA); ok && img.Bounds() == adjustedImageBounds {
|
if adjustedImage, ok := img.(*image.RGBA); ok && img.Bounds() == adjustedImageBounds {
|
||||||
return nrgba
|
return adjustedImage
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustedImage := image.NewRGBA(adjustedImageBounds)
|
adjustedImage := image.NewRGBA(adjustedImageBounds)
|
||||||
@ -54,7 +54,7 @@ func (t *Texture) Size() (width, height int) {
|
|||||||
return t.width, t.height
|
return t.width, t.height
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTexture(c *opengl.Context, width, height int, filter opengl.FilterType) (*Texture, error) {
|
func NewTexture(c *opengl.Context, width, height int, filter opengl.Filter) (*Texture, error) {
|
||||||
w := internal.NextPowerOf2Int(width)
|
w := internal.NextPowerOf2Int(width)
|
||||||
h := internal.NextPowerOf2Int(height)
|
h := internal.NextPowerOf2Int(height)
|
||||||
if w < 4 {
|
if w < 4 {
|
||||||
@ -70,7 +70,7 @@ func NewTexture(c *opengl.Context, width, height int, filter opengl.FilterType)
|
|||||||
return &Texture{native, width, height}, nil
|
return &Texture{native, width, height}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTextureFromImage(c *opengl.Context, img image.Image, filter opengl.FilterType) (*Texture, error) {
|
func NewTextureFromImage(c *opengl.Context, img image.Image, filter opengl.Filter) (*Texture, error) {
|
||||||
origSize := img.Bounds().Size()
|
origSize := img.Bounds().Size()
|
||||||
if origSize.X < 4 {
|
if origSize.X < 4 {
|
||||||
return nil, errors.New("width must be equal or more than 4.")
|
return nil, errors.New("width must be equal or more than 4.")
|
||||||
@ -91,7 +91,8 @@ func (t *Texture) Dispose(c *opengl.Context) {
|
|||||||
c.DeleteTexture(t.native)
|
c.DeleteTexture(t.native)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Texture) Pixels(c *opengl.Context) ([]uint8, error) {
|
func (t *Texture) ReplacePixels(c *opengl.Context, p []uint8) error {
|
||||||
w, h := internal.NextPowerOf2Int(t.width), internal.NextPowerOf2Int(t.height)
|
c.BindTexture(t.native)
|
||||||
return c.TexturePixels(t.native, w, h)
|
c.TexSubImage2D(p, t.width, t.height)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
34
internal/license.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LicenseComment() (string, error) {
|
||||||
|
_, path, _, _ := runtime.Caller(0)
|
||||||
|
licensePath := filepath.Join(filepath.Dir(path), "..", "license.txt")
|
||||||
|
l, err := ioutil.ReadFile(licensePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(l), "\n")
|
||||||
|
license := "// " + strings.Join(lines[:len(lines)-1], "\n// ")
|
||||||
|
return license, nil
|
||||||
|
}
|
@ -22,12 +22,25 @@ import (
|
|||||||
"github.com/go-gl/gl"
|
"github.com/go-gl/gl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Texture int
|
type Texture gl.Texture
|
||||||
type Framebuffer int
|
type Framebuffer gl.Framebuffer
|
||||||
type Shader int
|
type Shader gl.Shader
|
||||||
type Program int
|
type Program gl.Program
|
||||||
type UniformLocation int
|
type Buffer gl.Buffer
|
||||||
type AttribLocation int
|
|
||||||
|
// TODO: Remove this after the GopherJS bug was fixed (#159)
|
||||||
|
func (p Program) Equals(other Program) bool {
|
||||||
|
return p == other
|
||||||
|
}
|
||||||
|
|
||||||
|
type UniformLocation gl.UniformLocation
|
||||||
|
type AttribLocation gl.AttribLocation
|
||||||
|
|
||||||
|
type ProgramID int
|
||||||
|
|
||||||
|
func GetProgramID(p Program) ProgramID {
|
||||||
|
return ProgramID(p)
|
||||||
|
}
|
||||||
|
|
||||||
type context struct{}
|
type context struct{}
|
||||||
|
|
||||||
@ -41,6 +54,8 @@ func NewContext() *Context {
|
|||||||
ElementArrayBuffer: gl.ELEMENT_ARRAY_BUFFER,
|
ElementArrayBuffer: gl.ELEMENT_ARRAY_BUFFER,
|
||||||
DynamicDraw: gl.DYNAMIC_DRAW,
|
DynamicDraw: gl.DYNAMIC_DRAW,
|
||||||
StaticDraw: gl.STATIC_DRAW,
|
StaticDraw: gl.STATIC_DRAW,
|
||||||
|
Triangles: gl.TRIANGLES,
|
||||||
|
Lines: gl.LINES,
|
||||||
}
|
}
|
||||||
c.init()
|
c.init()
|
||||||
return c
|
return c
|
||||||
@ -53,7 +68,7 @@ func (c *Context) init() {
|
|||||||
gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) NewTexture(width, height int, pixels []uint8, filter FilterType) (Texture, error) {
|
func (c *Context) NewTexture(width, height int, pixels []uint8, filter Filter) (Texture, error) {
|
||||||
t := gl.GenTexture()
|
t := gl.GenTexture()
|
||||||
if t < 0 {
|
if t < 0 {
|
||||||
return 0, errors.New("glGenTexture failed")
|
return 0, errors.New("glGenTexture failed")
|
||||||
@ -69,12 +84,13 @@ func (c *Context) NewTexture(width, height int, pixels []uint8, filter FilterTyp
|
|||||||
return Texture(t), nil
|
return Texture(t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) TexturePixels(t Texture, width, height int) ([]uint8, error) {
|
func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8, error) {
|
||||||
gl.Flush()
|
gl.Flush()
|
||||||
// TODO: Use glGetTexLevelParameteri and GL_TEXTURE_WIDTH?
|
|
||||||
|
gl.Framebuffer(f).Bind()
|
||||||
|
|
||||||
pixels := make([]uint8, 4*width*height)
|
pixels := make([]uint8, 4*width*height)
|
||||||
gl.Texture(t).Bind(gl.TEXTURE_2D)
|
gl.ReadPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
|
||||||
gl.GetTexImage(gl.TEXTURE_2D, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
|
|
||||||
if e := gl.GetError(); e != gl.NO_ERROR {
|
if e := gl.GetError(); e != gl.NO_ERROR {
|
||||||
return nil, errors.New(fmt.Sprintf("gl error: %d", e))
|
return nil, errors.New(fmt.Sprintf("gl error: %d", e))
|
||||||
}
|
}
|
||||||
@ -89,6 +105,10 @@ func (c *Context) DeleteTexture(t Texture) {
|
|||||||
gl.Texture(t).Delete()
|
gl.Texture(t).Delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) TexSubImage2D(p []uint8, width, height int) {
|
||||||
|
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, p)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) NewFramebuffer(texture Texture) (Framebuffer, error) {
|
func (c *Context) NewFramebuffer(texture Texture) (Framebuffer, error) {
|
||||||
f := gl.GenFramebuffer()
|
f := gl.GenFramebuffer()
|
||||||
f.Bind()
|
f.Bind()
|
||||||
@ -115,7 +135,7 @@ func (c *Context) SetViewport(f Framebuffer, width, height int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) FillFramebuffer(f Framebuffer, r, g, b, a float64) error {
|
func (c *Context) FillFramebuffer(r, g, b, a float64) error {
|
||||||
gl.ClearColor(gl.GLclampf(r), gl.GLclampf(g), gl.GLclampf(b), gl.GLclampf(a))
|
gl.ClearColor(gl.GLclampf(r), gl.GLclampf(g), gl.GLclampf(b), gl.GLclampf(a))
|
||||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||||
return nil
|
return nil
|
||||||
@ -173,13 +193,17 @@ func (c *Context) UseProgram(p Program) {
|
|||||||
gl.Program(p).Use()
|
gl.Program(p).Use()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetUniformLocation(p Program, location string) UniformLocation {
|
||||||
|
return UniformLocation(gl.Program(p).GetUniformLocation(location))
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) UniformInt(p Program, location string, v int) {
|
func (c *Context) UniformInt(p Program, location string, v int) {
|
||||||
l := gl.Program(p).GetUniformLocation(location)
|
l := gl.UniformLocation(GetUniformLocation(c, p, location))
|
||||||
l.Uniform1i(v)
|
l.Uniform1i(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) UniformFloats(p Program, location string, v []float32) {
|
func (c *Context) UniformFloats(p Program, location string, v []float32) {
|
||||||
l := gl.Program(p).GetUniformLocation(location)
|
l := gl.UniformLocation(GetUniformLocation(c, p, location))
|
||||||
switch len(v) {
|
switch len(v) {
|
||||||
case 4:
|
case 4:
|
||||||
l.Uniform4fv(1, v)
|
l.Uniform4fv(1, v)
|
||||||
@ -192,20 +216,32 @@ func (c *Context) UniformFloats(p Program, location string, v []float32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) VertexAttribPointer(p Program, location string, stride int, v uintptr) {
|
func (c *Context) GetAttribLocation(p Program, location string) AttribLocation {
|
||||||
gl.Program(p).GetAttribLocation(location).AttribPointer(2, gl.FLOAT, false, stride, v)
|
return AttribLocation(gl.Program(p).GetAttribLocation(location))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) VertexAttribPointer(p Program, location string, signed bool, normalize bool, stride int, size int, v uintptr) {
|
||||||
|
l := gl.AttribLocation(GetAttribLocation(c, p, location))
|
||||||
|
t := gl.GLenum(gl.SHORT)
|
||||||
|
if !signed {
|
||||||
|
t = gl.UNSIGNED_SHORT
|
||||||
|
}
|
||||||
|
l.AttribPointer(uint(size), t, normalize, stride, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) EnableVertexAttribArray(p Program, location string) {
|
func (c *Context) EnableVertexAttribArray(p Program, location string) {
|
||||||
gl.Program(p).GetAttribLocation(location).EnableArray()
|
l := gl.AttribLocation(GetAttribLocation(c, p, location))
|
||||||
|
l.EnableArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) DisableVertexAttribArray(p Program, location string) {
|
func (c *Context) DisableVertexAttribArray(p Program, location string) {
|
||||||
gl.Program(p).GetAttribLocation(location).DisableArray()
|
l := gl.AttribLocation(GetAttribLocation(c, p, location))
|
||||||
|
l.DisableArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) NewBuffer(bufferType BufferType, v interface{}, bufferUsageType BufferUsageType) {
|
func (c *Context) NewBuffer(bufferType BufferType, v interface{}, bufferUsage BufferUsage) Buffer {
|
||||||
gl.GenBuffer().Bind(gl.GLenum(bufferType))
|
b := gl.GenBuffer()
|
||||||
|
b.Bind(gl.GLenum(bufferType))
|
||||||
size := 0
|
size := 0
|
||||||
ptr := v
|
ptr := v
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
@ -219,16 +255,21 @@ func (c *Context) NewBuffer(bufferType BufferType, v interface{}, bufferUsageTyp
|
|||||||
default:
|
default:
|
||||||
panic("not reach")
|
panic("not reach")
|
||||||
}
|
}
|
||||||
gl.BufferData(gl.GLenum(bufferType), size, ptr, gl.GLenum(bufferUsageType))
|
gl.BufferData(gl.GLenum(bufferType), size, ptr, gl.GLenum(bufferUsage))
|
||||||
|
return Buffer(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) BufferSubData(bufferType BufferType, data []float32) {
|
func (c *Context) BindElementArrayBuffer(b Buffer) {
|
||||||
const float32Size = 4
|
gl.Buffer(b).Bind(gl.ELEMENT_ARRAY_BUFFER)
|
||||||
gl.BufferSubData(gl.GLenum(bufferType), 0, float32Size*len(data), data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) DrawElements(len int) {
|
func (c *Context) BufferSubData(bufferType BufferType, data []int16) {
|
||||||
gl.DrawElements(gl.TRIANGLES, len, gl.UNSIGNED_SHORT, uintptr(0))
|
const int16Size = 2
|
||||||
|
gl.BufferSubData(gl.GLenum(bufferType), 0, int16Size*len(data), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) DrawElements(mode Mode, len int) {
|
||||||
|
gl.DrawElements(gl.GLenum(mode), len, gl.UNSIGNED_SHORT, uintptr(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) Flush() {
|
func (c *Context) Flush() {
|
||||||
|
@ -23,27 +23,61 @@ import (
|
|||||||
"github.com/gopherjs/webgl"
|
"github.com/gopherjs/webgl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Texture js.Object
|
type Texture struct {
|
||||||
type Framebuffer js.Object
|
js.Object
|
||||||
type Shader js.Object
|
}
|
||||||
type Program js.Object
|
|
||||||
type UniformLocation js.Object
|
type Framebuffer struct {
|
||||||
|
js.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
type Shader struct {
|
||||||
|
js.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
js.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
type Buffer struct {
|
||||||
|
js.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this after the GopherJS bug was fixed (#159)
|
||||||
|
func (p Program) Equals(other Program) bool {
|
||||||
|
return p.Object == other.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
type UniformLocation struct {
|
||||||
|
js.Object
|
||||||
|
}
|
||||||
|
|
||||||
type AttribLocation int
|
type AttribLocation int
|
||||||
|
|
||||||
|
type ProgramID int
|
||||||
|
|
||||||
|
func GetProgramID(p Program) ProgramID {
|
||||||
|
return ProgramID(p.Get("__ebiten_programId").Int())
|
||||||
|
}
|
||||||
|
|
||||||
type context struct {
|
type context struct {
|
||||||
gl *webgl.Context
|
gl *webgl.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lastFramebuffer Framebuffer
|
||||||
|
|
||||||
func NewContext(gl *webgl.Context) *Context {
|
func NewContext(gl *webgl.Context) *Context {
|
||||||
c := &Context{
|
c := &Context{
|
||||||
Nearest: FilterType(gl.NEAREST),
|
Nearest: Filter(gl.NEAREST),
|
||||||
Linear: FilterType(gl.LINEAR),
|
Linear: Filter(gl.LINEAR),
|
||||||
VertexShader: ShaderType(gl.VERTEX_SHADER),
|
VertexShader: ShaderType(gl.VERTEX_SHADER),
|
||||||
FragmentShader: ShaderType(gl.FRAGMENT_SHADER),
|
FragmentShader: ShaderType(gl.FRAGMENT_SHADER),
|
||||||
ArrayBuffer: BufferType(gl.ARRAY_BUFFER),
|
ArrayBuffer: BufferType(gl.ARRAY_BUFFER),
|
||||||
ElementArrayBuffer: BufferType(gl.ELEMENT_ARRAY_BUFFER),
|
ElementArrayBuffer: BufferType(gl.ELEMENT_ARRAY_BUFFER),
|
||||||
DynamicDraw: BufferUsageType(gl.DYNAMIC_DRAW),
|
DynamicDraw: BufferUsage(gl.DYNAMIC_DRAW),
|
||||||
StaticDraw: BufferUsageType(gl.STATIC_DRAW),
|
StaticDraw: BufferUsage(gl.STATIC_DRAW),
|
||||||
|
Triangles: Mode(gl.TRIANGLES),
|
||||||
|
Lines: Mode(gl.LINES),
|
||||||
}
|
}
|
||||||
c.gl = gl
|
c.gl = gl
|
||||||
c.init()
|
c.init()
|
||||||
@ -57,11 +91,11 @@ func (c *Context) init() {
|
|||||||
gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) NewTexture(width, height int, pixels []uint8, filter FilterType) (Texture, error) {
|
func (c *Context) NewTexture(width, height int, pixels []uint8, filter Filter) (Texture, error) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
t := gl.CreateTexture()
|
t := gl.CreateTexture()
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return nil, errors.New("glGenTexture failed")
|
return Texture{nil}, errors.New("glGenTexture failed")
|
||||||
}
|
}
|
||||||
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
|
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
|
||||||
gl.BindTexture(gl.TEXTURE_2D, t)
|
gl.BindTexture(gl.TEXTURE_2D, t)
|
||||||
@ -69,6 +103,8 @@ func (c *Context) NewTexture(width, height int, pixels []uint8, filter FilterTyp
|
|||||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, int(filter))
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, int(filter))
|
||||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, int(filter))
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, int(filter))
|
||||||
|
|
||||||
|
// TODO: Can we use glTexSubImage2D with linear filtering?
|
||||||
|
|
||||||
// void texImage2D(GLenum target, GLint level, GLenum internalformat,
|
// void texImage2D(GLenum target, GLint level, GLenum internalformat,
|
||||||
// GLsizei width, GLsizei height, GLint border, GLenum format,
|
// GLsizei width, GLsizei height, GLint border, GLenum format,
|
||||||
// GLenum type, ArrayBufferView? pixels);
|
// GLenum type, ArrayBufferView? pixels);
|
||||||
@ -78,15 +114,17 @@ func (c *Context) NewTexture(width, height int, pixels []uint8, filter FilterTyp
|
|||||||
}
|
}
|
||||||
gl.Call("texImage2D", gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, p)
|
gl.Call("texImage2D", gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, p)
|
||||||
|
|
||||||
return Texture(t), nil
|
return Texture{t}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) TexturePixels(t Texture, width, height int) ([]uint8, error) {
|
func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8, error) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
gl.Flush()
|
gl.Flush()
|
||||||
// TODO: Use glGetTexLevelParameteri and GL_TEXTURE_WIDTH?
|
|
||||||
|
lastFramebuffer = Framebuffer{nil}
|
||||||
|
gl.BindFramebuffer(gl.FRAMEBUFFER, f.Object)
|
||||||
|
|
||||||
pixels := js.Global.Get("Uint8Array").New(4 * width * height)
|
pixels := js.Global.Get("Uint8Array").New(4 * width * height)
|
||||||
gl.BindTexture(gl.TEXTURE_2D, t)
|
|
||||||
gl.ReadPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
|
gl.ReadPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
|
||||||
if e := gl.GetError(); e != gl.NO_ERROR {
|
if e := gl.GetError(); e != gl.NO_ERROR {
|
||||||
return nil, errors.New(fmt.Sprintf("gl error: %d", e))
|
return nil, errors.New(fmt.Sprintf("gl error: %d", e))
|
||||||
@ -96,42 +134,45 @@ func (c *Context) TexturePixels(t Texture, width, height int) ([]uint8, error) {
|
|||||||
|
|
||||||
func (c *Context) BindTexture(t Texture) {
|
func (c *Context) BindTexture(t Texture) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
gl.BindTexture(gl.TEXTURE_2D, t)
|
gl.BindTexture(gl.TEXTURE_2D, t.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) DeleteTexture(t Texture) {
|
func (c *Context) DeleteTexture(t Texture) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
gl.DeleteTexture(t)
|
gl.DeleteTexture(t.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GlslHighpSupported() bool {
|
func (c *Context) TexSubImage2D(p []uint8, width, height int) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
return gl.Call("getShaderPrecisionFormat", gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).Get("precision").Int() != 0
|
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||||
|
// GLsizei width, GLsizei height,
|
||||||
|
// GLenum format, GLenum type, ArrayBufferView? pixels);
|
||||||
|
gl.Call("texSubImage2D", gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) NewFramebuffer(texture Texture) (Framebuffer, error) {
|
func (c *Context) NewFramebuffer(t Texture) (Framebuffer, error) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
f := gl.CreateFramebuffer()
|
f := gl.CreateFramebuffer()
|
||||||
|
lastFramebuffer = Framebuffer{nil}
|
||||||
gl.BindFramebuffer(gl.FRAMEBUFFER, f)
|
gl.BindFramebuffer(gl.FRAMEBUFFER, f)
|
||||||
|
|
||||||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)
|
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.Object, 0)
|
||||||
if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
|
if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
|
||||||
return nil, errors.New("creating framebuffer failed")
|
return Framebuffer{nil}, errors.New("creating framebuffer failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return Framebuffer(f), nil
|
return Framebuffer{f}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastFramebuffer Framebuffer
|
|
||||||
|
|
||||||
func (c *Context) SetViewport(f Framebuffer, width, height int) error {
|
func (c *Context) SetViewport(f Framebuffer, width, height int) error {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
if lastFramebuffer != f {
|
// TODO: Fix this after the GopherJS bug was fixed (#159)
|
||||||
|
if lastFramebuffer.Object != f.Object {
|
||||||
gl.Flush()
|
gl.Flush()
|
||||||
lastFramebuffer = f
|
lastFramebuffer = f
|
||||||
}
|
}
|
||||||
if f != nil {
|
if f.Object != nil {
|
||||||
gl.BindFramebuffer(gl.FRAMEBUFFER, f)
|
gl.BindFramebuffer(gl.FRAMEBUFFER, f.Object)
|
||||||
} else {
|
} else {
|
||||||
gl.BindFramebuffer(gl.FRAMEBUFFER, nil)
|
gl.BindFramebuffer(gl.FRAMEBUFFER, nil)
|
||||||
}
|
}
|
||||||
@ -140,7 +181,8 @@ func (c *Context) SetViewport(f Framebuffer, width, height int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) FillFramebuffer(f Framebuffer, r, g, b, a float64) error {
|
func (c *Context) FillFramebuffer(r, g, b, a float64) error {
|
||||||
|
// TODO: Use f?
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
gl.ClearColor(float32(r), float32(g), float32(b), float32(a))
|
gl.ClearColor(float32(r), float32(g), float32(b), float32(a))
|
||||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||||
@ -149,7 +191,7 @@ func (c *Context) FillFramebuffer(f Framebuffer, r, g, b, a float64) error {
|
|||||||
|
|
||||||
func (c *Context) DeleteFramebuffer(f Framebuffer) {
|
func (c *Context) DeleteFramebuffer(f Framebuffer) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
gl.DeleteFramebuffer(f)
|
gl.DeleteFramebuffer(f.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) NewShader(shaderType ShaderType, source string) (Shader, error) {
|
func (c *Context) NewShader(shaderType ShaderType, source string) (Shader, error) {
|
||||||
@ -157,7 +199,7 @@ func (c *Context) NewShader(shaderType ShaderType, source string) (Shader, error
|
|||||||
s := gl.CreateShader(int(shaderType))
|
s := gl.CreateShader(int(shaderType))
|
||||||
if s == nil {
|
if s == nil {
|
||||||
println(gl.GetError())
|
println(gl.GetError())
|
||||||
return nil, errors.New("glCreateShader failed")
|
return Shader{nil}, errors.New("glCreateShader failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.ShaderSource(s, source)
|
gl.ShaderSource(s, source)
|
||||||
@ -165,111 +207,123 @@ func (c *Context) NewShader(shaderType ShaderType, source string) (Shader, error
|
|||||||
|
|
||||||
if !gl.GetShaderParameterb(s, gl.COMPILE_STATUS) {
|
if !gl.GetShaderParameterb(s, gl.COMPILE_STATUS) {
|
||||||
log := gl.GetShaderInfoLog(s)
|
log := gl.GetShaderInfoLog(s)
|
||||||
return nil, errors.New(fmt.Sprintf("shader compile failed: %s", log))
|
return Shader{nil}, errors.New(fmt.Sprintf("shader compile failed: %s", log))
|
||||||
}
|
}
|
||||||
return Shader(s), nil
|
return Shader{s}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) DeleteShader(s Shader) {
|
func (c *Context) DeleteShader(s Shader) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
gl.DeleteShader(s)
|
gl.DeleteShader(s.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) GlslHighpSupported() bool {
|
||||||
|
gl := c.gl
|
||||||
|
// headless-gl library may not define getShaderPrecisionFormat.
|
||||||
|
if gl.Get("getShaderPrecisionFormat") == js.Undefined {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return gl.Call("getShaderPrecisionFormat", gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).Get("precision").Int() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastProgramID ProgramID = 0
|
||||||
|
|
||||||
func (c *Context) NewProgram(shaders []Shader) (Program, error) {
|
func (c *Context) NewProgram(shaders []Shader) (Program, error) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
p := gl.CreateProgram()
|
p := gl.CreateProgram()
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil, errors.New("glCreateProgram failed")
|
return Program{nil}, errors.New("glCreateProgram failed")
|
||||||
}
|
}
|
||||||
|
p.Set("__ebiten_programId", lastProgramID)
|
||||||
|
lastProgramID++
|
||||||
|
|
||||||
for _, shader := range shaders {
|
for _, shader := range shaders {
|
||||||
gl.AttachShader(p, shader)
|
gl.AttachShader(p, shader.Object)
|
||||||
}
|
}
|
||||||
gl.LinkProgram(p)
|
gl.LinkProgram(p)
|
||||||
if !gl.GetProgramParameterb(p, gl.LINK_STATUS) {
|
if !gl.GetProgramParameterb(p, gl.LINK_STATUS) {
|
||||||
return nil, errors.New("program error")
|
return Program{nil}, errors.New("program error")
|
||||||
}
|
}
|
||||||
return Program(p), nil
|
return Program{p}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) UseProgram(p Program) {
|
func (c *Context) UseProgram(p Program) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
gl.UseProgram(p)
|
gl.UseProgram(p.Object)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetUniformLocation(p Program, location string) UniformLocation {
|
||||||
|
gl := c.gl
|
||||||
|
return UniformLocation{gl.GetUniformLocation(p.Object, location)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) UniformInt(p Program, location string, v int) {
|
func (c *Context) UniformInt(p Program, location string, v int) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
l, ok := uniformLocationCache[location]
|
l := GetUniformLocation(c, p, location)
|
||||||
if !ok {
|
gl.Uniform1i(l.Object, v)
|
||||||
l = gl.GetUniformLocation(p, location)
|
|
||||||
uniformLocationCache[location] = l
|
|
||||||
}
|
|
||||||
gl.Uniform1i(l, v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) UniformFloats(p Program, location string, v []float32) {
|
func (c *Context) UniformFloats(p Program, location string, v []float32) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
l, ok := uniformLocationCache[location]
|
l := GetUniformLocation(c, p, location)
|
||||||
if !ok {
|
|
||||||
l = gl.GetUniformLocation(p, location)
|
|
||||||
uniformLocationCache[location] = l
|
|
||||||
}
|
|
||||||
switch len(v) {
|
switch len(v) {
|
||||||
case 4:
|
case 4:
|
||||||
gl.Call("uniform4fv", l, v)
|
gl.Call("uniform4fv", l.Object, v)
|
||||||
case 16:
|
case 16:
|
||||||
gl.UniformMatrix4fv(l, false, v)
|
gl.UniformMatrix4fv(l.Object, false, v)
|
||||||
default:
|
default:
|
||||||
panic("not reach")
|
panic("not reach")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) VertexAttribPointer(p Program, location string, stride int, v uintptr) {
|
func (c *Context) GetAttribLocation(p Program, location string) AttribLocation {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
l, ok := attribLocationCache[location]
|
return AttribLocation(gl.GetAttribLocation(p.Object, location))
|
||||||
if !ok {
|
}
|
||||||
l = AttribLocation(gl.GetAttribLocation(p, location))
|
|
||||||
attribLocationCache[location] = l
|
func (c *Context) VertexAttribPointer(p Program, location string, signed bool, normalize bool, stride int, size int, v uintptr) {
|
||||||
|
gl := c.gl
|
||||||
|
l := GetAttribLocation(c, p, location)
|
||||||
|
t := gl.SHORT
|
||||||
|
if !signed {
|
||||||
|
t = gl.UNSIGNED_SHORT
|
||||||
}
|
}
|
||||||
gl.VertexAttribPointer(int(l), 2, gl.FLOAT, false, stride, int(v))
|
gl.VertexAttribPointer(int(l), size, t, normalize, stride, int(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) EnableVertexAttribArray(p Program, location string) {
|
func (c *Context) EnableVertexAttribArray(p Program, location string) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
l, ok := attribLocationCache[location]
|
l := GetAttribLocation(c, p, location)
|
||||||
if !ok {
|
|
||||||
l = AttribLocation(gl.GetAttribLocation(p, location))
|
|
||||||
attribLocationCache[location] = l
|
|
||||||
}
|
|
||||||
gl.EnableVertexAttribArray(int(l))
|
gl.EnableVertexAttribArray(int(l))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) DisableVertexAttribArray(p Program, location string) {
|
func (c *Context) DisableVertexAttribArray(p Program, location string) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
l, ok := attribLocationCache[location]
|
l := GetAttribLocation(c, p, location)
|
||||||
if !ok {
|
|
||||||
l = AttribLocation(gl.GetAttribLocation(p, location))
|
|
||||||
attribLocationCache[location] = l
|
|
||||||
}
|
|
||||||
gl.DisableVertexAttribArray(int(l))
|
gl.DisableVertexAttribArray(int(l))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) NewBuffer(bufferType BufferType, v interface{}, bufferUsageType BufferUsageType) {
|
func (c *Context) NewBuffer(bufferType BufferType, v interface{}, bufferUsage BufferUsage) Buffer {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
b := gl.CreateBuffer()
|
b := gl.CreateBuffer()
|
||||||
gl.BindBuffer(int(bufferType), b)
|
gl.BindBuffer(int(bufferType), b)
|
||||||
gl.BufferData(int(bufferType), v, int(bufferUsageType))
|
gl.BufferData(int(bufferType), v, int(bufferUsage))
|
||||||
|
return Buffer{b}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) BufferSubData(bufferType BufferType, data []float32) {
|
func (c *Context) BindElementArrayBuffer(b Buffer) {
|
||||||
|
gl := c.gl
|
||||||
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.Object)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) BufferSubData(bufferType BufferType, data []int16) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
const float32Size = 4
|
|
||||||
gl.BufferSubData(int(bufferType), 0, data)
|
gl.BufferSubData(int(bufferType), 0, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) DrawElements(len int) {
|
func (c *Context) DrawElements(mode Mode, len int) {
|
||||||
gl := c.gl
|
gl := c.gl
|
||||||
gl.DrawElements(gl.TRIANGLES, len, gl.UNSIGNED_SHORT, 0)
|
gl.DrawElements(int(mode), len, gl.UNSIGNED_SHORT, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) Flush() {
|
func (c *Context) Flush() {
|
||||||
|
@ -14,7 +14,41 @@
|
|||||||
|
|
||||||
package opengl
|
package opengl
|
||||||
|
|
||||||
// Note: This cache is created only for one program.
|
// Since js.Object (Program) can't be keys of a map, use integers (ProgramID) instead.
|
||||||
// If we use two or more programs, the key of the map should be changed.
|
|
||||||
var uniformLocationCache = map[string]UniformLocation{}
|
var uniformLocationCache = map[ProgramID]map[string]UniformLocation{}
|
||||||
var attribLocationCache = map[string]AttribLocation{}
|
var attribLocationCache = map[ProgramID]map[string]AttribLocation{}
|
||||||
|
|
||||||
|
type UniformLocationGetter interface {
|
||||||
|
GetUniformLocation(p Program, location string) UniformLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUniformLocation(g UniformLocationGetter, p Program, location string) UniformLocation {
|
||||||
|
id := GetProgramID(p)
|
||||||
|
if _, ok := uniformLocationCache[id]; !ok {
|
||||||
|
uniformLocationCache[id] = map[string]UniformLocation{}
|
||||||
|
}
|
||||||
|
l, ok := uniformLocationCache[id][location]
|
||||||
|
if !ok {
|
||||||
|
l = g.GetUniformLocation(p, location)
|
||||||
|
uniformLocationCache[id][location] = l
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttribLocationGetter interface {
|
||||||
|
GetAttribLocation(p Program, location string) AttribLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAttribLocation(g AttribLocationGetter, p Program, location string) AttribLocation {
|
||||||
|
id := GetProgramID(p)
|
||||||
|
if _, ok := attribLocationCache[id]; !ok {
|
||||||
|
attribLocationCache[id] = map[string]AttribLocation{}
|
||||||
|
}
|
||||||
|
l, ok := attribLocationCache[id][location]
|
||||||
|
if !ok {
|
||||||
|
l = g.GetAttribLocation(p, location)
|
||||||
|
attribLocationCache[id][location] = l
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
@ -14,19 +14,22 @@
|
|||||||
|
|
||||||
package opengl
|
package opengl
|
||||||
|
|
||||||
type FilterType int
|
type Filter int
|
||||||
type ShaderType int
|
type ShaderType int
|
||||||
type BufferType int
|
type BufferType int
|
||||||
type BufferUsageType int
|
type BufferUsage int
|
||||||
|
type Mode int
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Nearest FilterType
|
Nearest Filter
|
||||||
Linear FilterType
|
Linear Filter
|
||||||
VertexShader ShaderType
|
VertexShader ShaderType
|
||||||
FragmentShader ShaderType
|
FragmentShader ShaderType
|
||||||
ArrayBuffer BufferType
|
ArrayBuffer BufferType
|
||||||
ElementArrayBuffer BufferType
|
ElementArrayBuffer BufferType
|
||||||
DynamicDraw BufferUsageType
|
DynamicDraw BufferUsage
|
||||||
StaticDraw BufferUsageType
|
StaticDraw BufferUsage
|
||||||
|
Triangles Mode
|
||||||
|
Lines Mode
|
||||||
context
|
context
|
||||||
}
|
}
|
||||||
|
36
internal/ui/gamepadbutton.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ui
|
||||||
|
|
||||||
|
type GamepadButton int
|
||||||
|
|
||||||
|
const (
|
||||||
|
GamepadButton0 GamepadButton = iota
|
||||||
|
GamepadButton1
|
||||||
|
GamepadButton2
|
||||||
|
GamepadButton3
|
||||||
|
GamepadButton4
|
||||||
|
GamepadButton5
|
||||||
|
GamepadButton6
|
||||||
|
GamepadButton7
|
||||||
|
GamepadButton8
|
||||||
|
GamepadButton9
|
||||||
|
GamepadButton10
|
||||||
|
GamepadButton11
|
||||||
|
GamepadButton12
|
||||||
|
GamepadButton13
|
||||||
|
GamepadButton14
|
||||||
|
GamepadButton15
|
||||||
|
)
|
@ -14,21 +14,59 @@
|
|||||||
|
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
|
func IsKeyPressed(key Key) bool {
|
||||||
|
return currentInput.keyPressed[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func CursorPosition() (x, y int) {
|
||||||
|
return currentInput.cursorX, currentInput.cursorY
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsMouseButtonPressed(button MouseButton) bool {
|
||||||
|
return currentInput.mouseButtonPressed[button]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GamepadAxisNum(id int) int {
|
||||||
|
if len(currentInput.gamepads) <= id {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return currentInput.gamepads[id].axisNum
|
||||||
|
}
|
||||||
|
|
||||||
|
func GamepadAxis(id int, axis int) float64 {
|
||||||
|
if len(currentInput.gamepads) <= id {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return currentInput.gamepads[id].axes[axis]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GamepadButtonNum(id int) int {
|
||||||
|
if len(currentInput.gamepads) <= id {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return currentInput.gamepads[id].buttonNum
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsGamepadButtonPressed(id int, button GamepadButton) bool {
|
||||||
|
if len(currentInput.gamepads) <= id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return currentInput.gamepads[id].buttonPressed[button]
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentInput input
|
||||||
|
|
||||||
type input struct {
|
type input struct {
|
||||||
keyPressed [256]bool
|
keyPressed [256]bool
|
||||||
mouseButtonPressed [256]bool
|
mouseButtonPressed [256]bool
|
||||||
cursorX int
|
cursorX int
|
||||||
cursorY int
|
cursorY int
|
||||||
|
gamepads [16]gamePad
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *input) isKeyPressed(key Key) bool {
|
type gamePad struct {
|
||||||
return i.keyPressed[key]
|
axisNum int
|
||||||
}
|
axes [16]float64
|
||||||
|
buttonNum int
|
||||||
func (i *input) isMouseButtonPressed(button MouseButton) bool {
|
buttonPressed [256]bool
|
||||||
return i.mouseButtonPressed[button]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *input) cursorPosition() (x, y int) {
|
|
||||||
return i.cursorX, i.cursorY
|
|
||||||
}
|
}
|
||||||
|
@ -21,25 +21,13 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsKeyPressed(key Key) bool {
|
|
||||||
return current.input.isKeyPressed(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsMouseButtonPressed(button MouseButton) bool {
|
|
||||||
return current.input.isMouseButtonPressed(button)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CursorPosition() (x, y int) {
|
|
||||||
return current.input.cursorPosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
var glfwMouseButtonToMouseButton = map[glfw.MouseButton]MouseButton{
|
var glfwMouseButtonToMouseButton = map[glfw.MouseButton]MouseButton{
|
||||||
glfw.MouseButtonLeft: MouseButtonLeft,
|
glfw.MouseButtonLeft: MouseButtonLeft,
|
||||||
glfw.MouseButtonRight: MouseButtonRight,
|
glfw.MouseButtonRight: MouseButtonRight,
|
||||||
glfw.MouseButtonMiddle: MouseButtonMiddle,
|
glfw.MouseButtonMiddle: MouseButtonMiddle,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *input) update(window *glfw.Window, scale int) {
|
func (i *input) update(window *glfw.Window, scale int) error {
|
||||||
for g, e := range glfwKeyCodeToKey {
|
for g, e := range glfwKeyCodeToKey {
|
||||||
i.keyPressed[e] = window.GetKey(g) == glfw.Press
|
i.keyPressed[e] = window.GetKey(g) == glfw.Press
|
||||||
}
|
}
|
||||||
@ -49,4 +37,34 @@ func (i *input) update(window *glfw.Window, scale int) {
|
|||||||
x, y := window.GetCursorPosition()
|
x, y := window.GetCursorPosition()
|
||||||
i.cursorX = int(math.Floor(x)) / scale
|
i.cursorX = int(math.Floor(x)) / scale
|
||||||
i.cursorY = int(math.Floor(y)) / scale
|
i.cursorY = int(math.Floor(y)) / scale
|
||||||
|
for id := glfw.Joystick(0); id < glfw.Joystick(len(i.gamepads)); id++ {
|
||||||
|
if !glfw.JoystickPresent(id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
axes32, err := glfw.GetJoystickAxes(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.gamepads[id].axisNum = len(axes32)
|
||||||
|
for a := 0; a < len(i.gamepads[id].axes); a++ {
|
||||||
|
if len(axes32) <= a {
|
||||||
|
i.gamepads[id].axes[a] = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i.gamepads[id].axes[a] = float64(axes32[a])
|
||||||
|
}
|
||||||
|
buttons, err := glfw.GetJoystickButtons(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.gamepads[id].buttonNum = len(buttons)
|
||||||
|
for b := 0; b < len(i.gamepads[id].buttonPressed); b++ {
|
||||||
|
if len(buttons) <= b {
|
||||||
|
i.gamepads[id].buttonPressed[b] = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i.gamepads[id].buttonPressed[b] = glfw.Action(buttons[b]) == glfw.Press
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -16,19 +16,9 @@
|
|||||||
|
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
var currentInput input
|
import (
|
||||||
|
"github.com/gopherjs/gopherjs/js"
|
||||||
func IsKeyPressed(key Key) bool {
|
)
|
||||||
return currentInput.isKeyPressed(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsMouseButtonPressed(button MouseButton) bool {
|
|
||||||
return currentInput.isMouseButtonPressed(button)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CursorPosition() (x, y int) {
|
|
||||||
return currentInput.cursorPosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *input) keyDown(key int) {
|
func (i *input) keyDown(key int) {
|
||||||
k, ok := keyCodeToKey[key]
|
k, ok := keyCodeToKey[key]
|
||||||
@ -70,6 +60,43 @@ func (i *input) mouseUp(button int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *input) mouseMove(x, y int) {
|
func (i *input) setMouseCursor(x, y int) {
|
||||||
i.cursorX, i.cursorY = x, y
|
i.cursorX, i.cursorY = x, y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *input) updateGamepads() {
|
||||||
|
nav := js.Global.Get("navigator")
|
||||||
|
if nav.Get("getGamepads") == js.Undefined {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gamepads := nav.Call("getGamepads")
|
||||||
|
l := gamepads.Get("length").Int()
|
||||||
|
for id := 0; id < l; id++ {
|
||||||
|
gamepad := gamepads.Index(id)
|
||||||
|
if gamepad == js.Undefined || gamepad == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
axes := gamepad.Get("axes")
|
||||||
|
axesNum := axes.Get("length").Int()
|
||||||
|
i.gamepads[id].axisNum = axesNum
|
||||||
|
for a := 0; a < len(i.gamepads[id].axes); a++ {
|
||||||
|
if axesNum <= a {
|
||||||
|
i.gamepads[id].axes[a] = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i.gamepads[id].axes[a] = axes.Index(a).Float()
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons := gamepad.Get("buttons")
|
||||||
|
buttonsNum := buttons.Get("length").Int()
|
||||||
|
i.gamepads[id].buttonNum = buttonsNum
|
||||||
|
for b := 0; b < len(i.gamepads[id].buttonPressed); b++ {
|
||||||
|
if buttonsNum <= b {
|
||||||
|
i.gamepads[id].buttonPressed[b] = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i.gamepads[id].buttonPressed[b] = buttons.Index(b).Get("pressed").Bool()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,8 +36,8 @@ func Use(f func(*opengl.Context)) {
|
|||||||
<-ch
|
<-ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoEvents() {
|
func DoEvents() error {
|
||||||
current.doEvents()
|
return current.doEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Terminate() {
|
func Terminate() {
|
||||||
@ -92,7 +92,6 @@ type ui struct {
|
|||||||
window *glfw.Window
|
window *glfw.Window
|
||||||
scale int
|
scale int
|
||||||
glContext *opengl.Context
|
glContext *opengl.Context
|
||||||
input input
|
|
||||||
funcs chan func()
|
funcs chan func()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,17 +142,22 @@ func Start(width, height, scale int, title string) (actualScale int, err error)
|
|||||||
return actualScale, nil
|
return actualScale, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) pollEvents() {
|
func (u *ui) pollEvents() error {
|
||||||
glfw.PollEvents()
|
glfw.PollEvents()
|
||||||
u.input.update(u.window, u.scale)
|
return currentInput.update(u.window, u.scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) doEvents() {
|
func (u *ui) doEvents() error {
|
||||||
u.pollEvents()
|
if err := u.pollEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for current.window.GetAttribute(glfw.Focused) == 0 {
|
for current.window.GetAttribute(glfw.Focused) == 0 {
|
||||||
time.Sleep(time.Second / 60)
|
time.Sleep(time.Second / 60)
|
||||||
u.pollEvents()
|
if err := u.pollEvents(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) terminate() {
|
func (u *ui) terminate() {
|
||||||
|
@ -47,11 +47,13 @@ func vsync() {
|
|||||||
<-ch
|
<-ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoEvents() {
|
func DoEvents() error {
|
||||||
vsync()
|
vsync()
|
||||||
for !shown() {
|
for !shown() {
|
||||||
vsync()
|
vsync()
|
||||||
}
|
}
|
||||||
|
currentInput.updateGamepads()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Terminate() {
|
func Terminate() {
|
||||||
@ -67,33 +69,23 @@ func SwapBuffers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// TODO: Implement this with node-webgl mainly for testing.
|
if js.Global.Get("require") != js.Undefined {
|
||||||
|
// Use headless-gl for testing.
|
||||||
|
nodeGl := js.Global.Call("require", "gl")
|
||||||
|
webglContext := nodeGl.Call("createContext", 16, 16)
|
||||||
|
context = opengl.NewContext(&webgl.Context{Object: webglContext})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
doc := js.Global.Get("document")
|
doc := js.Global.Get("document")
|
||||||
|
window := js.Global.Get("window")
|
||||||
if doc.Get("body") == nil {
|
if doc.Get("body") == nil {
|
||||||
ch := make(chan struct{})
|
ch := make(chan struct{})
|
||||||
js.Global.Get("window").Call("addEventListener", "load", func() {
|
window.Call("addEventListener", "load", func() {
|
||||||
close(ch)
|
close(ch)
|
||||||
})
|
})
|
||||||
<-ch
|
<-ch
|
||||||
}
|
}
|
||||||
doc.Call("addEventListener", "keydown", func(e js.Object) bool {
|
|
||||||
code := e.Get("keyCode").Int()
|
|
||||||
// Backspace
|
|
||||||
if code == 8 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Functions
|
|
||||||
if 112 <= code && code <= 123 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Alt and arrows
|
|
||||||
if code == 37 && code == 39 {
|
|
||||||
// Don't need to check Alt.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
canvas = doc.Call("createElement", "canvas")
|
canvas = doc.Call("createElement", "canvas")
|
||||||
canvas.Set("width", 16)
|
canvas.Set("width", 16)
|
||||||
@ -131,33 +123,79 @@ func init() {
|
|||||||
canvas.Call("setAttribute", "tabindex", 1)
|
canvas.Call("setAttribute", "tabindex", 1)
|
||||||
canvas.Get("style").Set("outline", "none")
|
canvas.Get("style").Set("outline", "none")
|
||||||
|
|
||||||
canvas.Call("addEventListener", "keydown", func(e js.Object) bool {
|
// Keyboard
|
||||||
|
canvas.Call("addEventListener", "keydown", func(e js.Object) {
|
||||||
|
e.Call("preventDefault")
|
||||||
code := e.Get("keyCode").Int()
|
code := e.Get("keyCode").Int()
|
||||||
currentInput.keyDown(code)
|
currentInput.keyDown(code)
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
canvas.Call("addEventListener", "keyup", func(e js.Object) bool {
|
canvas.Call("addEventListener", "keyup", func(e js.Object) {
|
||||||
|
e.Call("preventDefault")
|
||||||
code := e.Get("keyCode").Int()
|
code := e.Get("keyCode").Int()
|
||||||
currentInput.keyUp(code)
|
currentInput.keyUp(code)
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
canvas.Call("addEventListener", "mousedown", func(e js.Object) bool {
|
|
||||||
|
// Mouse
|
||||||
|
canvas.Call("addEventListener", "mousedown", func(e js.Object) {
|
||||||
|
e.Call("preventDefault")
|
||||||
button := e.Get("button").Int()
|
button := e.Get("button").Int()
|
||||||
currentInput.mouseDown(button)
|
currentInput.mouseDown(button)
|
||||||
return false
|
setMouseCursorFromEvent(e)
|
||||||
})
|
})
|
||||||
canvas.Call("addEventListener", "mouseup", func(e js.Object) bool {
|
canvas.Call("addEventListener", "mouseup", func(e js.Object) {
|
||||||
|
e.Call("preventDefault")
|
||||||
button := e.Get("button").Int()
|
button := e.Get("button").Int()
|
||||||
currentInput.mouseUp(button)
|
currentInput.mouseUp(button)
|
||||||
return false
|
setMouseCursorFromEvent(e)
|
||||||
})
|
})
|
||||||
canvas.Call("addEventListener", "contextmenu", func(e js.Object) bool {
|
canvas.Call("addEventListener", "mousemove", func(e js.Object) {
|
||||||
return false
|
e.Call("preventDefault")
|
||||||
|
setMouseCursorFromEvent(e)
|
||||||
|
})
|
||||||
|
canvas.Call("addEventListener", "contextmenu", func(e js.Object) {
|
||||||
|
e.Call("preventDefault")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Touch (emulating mouse events)
|
||||||
|
// TODO: Need to create indimendent touch functions?
|
||||||
|
canvas.Call("addEventListener", "touchstart", func(e js.Object) {
|
||||||
|
e.Call("preventDefault")
|
||||||
|
currentInput.mouseDown(0)
|
||||||
|
touches := e.Get("changedTouches")
|
||||||
|
touch := touches.Index(0)
|
||||||
|
setMouseCursorFromEvent(touch)
|
||||||
|
})
|
||||||
|
canvas.Call("addEventListener", "touchend", func(e js.Object) {
|
||||||
|
e.Call("preventDefault")
|
||||||
|
currentInput.mouseUp(0)
|
||||||
|
touches := e.Get("changedTouches")
|
||||||
|
touch := touches.Index(0)
|
||||||
|
setMouseCursorFromEvent(touch)
|
||||||
|
})
|
||||||
|
canvas.Call("addEventListener", "touchmove", func(e js.Object) {
|
||||||
|
e.Call("preventDefault")
|
||||||
|
touches := e.Get("changedTouches")
|
||||||
|
touch := touches.Index(0)
|
||||||
|
setMouseCursorFromEvent(touch)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gamepad
|
||||||
|
window.Call("addEventListener", "gamepadconnected", func(e js.Object) {
|
||||||
|
// Do nothing.
|
||||||
})
|
})
|
||||||
|
|
||||||
audio.Init()
|
audio.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setMouseCursorFromEvent(e js.Object) {
|
||||||
|
scale := canvas.Get("dataset").Get("ebitenScale").Int() // TODO: Float?
|
||||||
|
rect := canvas.Call("getBoundingClientRect")
|
||||||
|
x, y := e.Get("clientX").Int(), e.Get("clientY").Int()
|
||||||
|
x -= rect.Get("left").Int()
|
||||||
|
y -= rect.Get("top").Int()
|
||||||
|
currentInput.setMouseCursor(x/scale, y/scale)
|
||||||
|
}
|
||||||
|
|
||||||
func devicePixelRatio() int {
|
func devicePixelRatio() int {
|
||||||
// TODO: What if ratio is not an integer but a float?
|
// TODO: What if ratio is not an integer but a float?
|
||||||
ratio := js.Global.Get("window").Get("devicePixelRatio").Int()
|
ratio := js.Global.Get("window").Get("devicePixelRatio").Int()
|
||||||
@ -173,6 +211,7 @@ func Start(width, height, scale int, title string) (actualScale int, err error)
|
|||||||
actualScale = scale * devicePixelRatio()
|
actualScale = scale * devicePixelRatio()
|
||||||
canvas.Set("width", width*actualScale)
|
canvas.Set("width", width*actualScale)
|
||||||
canvas.Set("height", height*actualScale)
|
canvas.Set("height", height*actualScale)
|
||||||
|
canvas.Get("dataset").Set("ebitenScale", scale)
|
||||||
canvasStyle := canvas.Get("style")
|
canvasStyle := canvas.Get("style")
|
||||||
|
|
||||||
cssWidth := width * scale
|
cssWidth := width * scale
|
||||||
@ -183,13 +222,6 @@ func Start(width, height, scale int, title string) (actualScale int, err error)
|
|||||||
canvasStyle.Set("left", "calc(50% - "+strconv.Itoa(cssWidth/2)+"px)")
|
canvasStyle.Set("left", "calc(50% - "+strconv.Itoa(cssWidth/2)+"px)")
|
||||||
canvasStyle.Set("top", "calc(50% - "+strconv.Itoa(cssHeight/2)+"px)")
|
canvasStyle.Set("top", "calc(50% - "+strconv.Itoa(cssHeight/2)+"px)")
|
||||||
|
|
||||||
canvas.Call("addEventListener", "mousemove", func(e js.Object) {
|
|
||||||
rect := canvas.Call("getBoundingClientRect")
|
|
||||||
x, y := e.Get("clientX").Int(), e.Get("clientY").Int()
|
|
||||||
x -= rect.Get("left").Int()
|
|
||||||
y -= rect.Get("top").Int()
|
|
||||||
currentInput.mouseMove(x/scale, y/scale)
|
|
||||||
})
|
|
||||||
canvas.Call("focus")
|
canvas.Call("focus")
|
||||||
|
|
||||||
audio.Start()
|
audio.Start()
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
* 2D Graphics
|
* 2D Graphics
|
||||||
* Input (Mouse, Keyboard)
|
* Input (Mouse, Keyboard, Gamepad)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
18
run.go
@ -54,26 +54,22 @@ func Run(f func(*Image) error, width, height, scale int, title string) error {
|
|||||||
frames := 0
|
frames := 0
|
||||||
t := time.Now().UnixNano()
|
t := time.Now().UnixNano()
|
||||||
for {
|
for {
|
||||||
ui.DoEvents()
|
if err := ui.DoEvents(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if ui.IsClosed() {
|
if ui.IsClosed() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ui.Use(func(*opengl.Context) {
|
if err := graphicsContext.preUpdate(); err != nil {
|
||||||
err = graphicsContext.preUpdate()
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := f(&Image{inner: graphicsContext.screen}); err != nil {
|
if err := f(graphicsContext.screen); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ui.Use(func(*opengl.Context) {
|
if err := graphicsContext.postUpdate(); err != nil {
|
||||||
err = graphicsContext.postUpdate()
|
return err
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
ui.SwapBuffers()
|
ui.SwapBuffers()
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
96
shapes.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright 2015 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ebiten
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Lines represents the set of lines.
|
||||||
|
type Lines interface {
|
||||||
|
Len() int
|
||||||
|
Points(i int) (x0, y0, x1, y1 int)
|
||||||
|
Color(i int) color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
type line struct {
|
||||||
|
x0, y0 int
|
||||||
|
x1, y1 int
|
||||||
|
color color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *line) Len() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *line) Points(i int) (x0, y0, x1, y1 int) {
|
||||||
|
return l.x0, l.y0, l.x1, l.y1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *line) Color(i int) color.Color {
|
||||||
|
return l.color
|
||||||
|
}
|
||||||
|
|
||||||
|
type rectsAsLines struct {
|
||||||
|
Rects
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rectsAsLines) Len() int {
|
||||||
|
return r.Rects.Len() * 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rectsAsLines) Points(i int) (x0, y0, x1, y1 int) {
|
||||||
|
x, y, w, h := r.Rects.Rect(i / 4)
|
||||||
|
switch i % 4 {
|
||||||
|
case 0:
|
||||||
|
return x, y, x + w, y
|
||||||
|
case 1:
|
||||||
|
return x, y + 1, x, y + h - 1
|
||||||
|
case 2:
|
||||||
|
return x, y + h - 1, x + w, y + h - 1
|
||||||
|
case 3:
|
||||||
|
return x + w - 1, y + 1, x + w - 1, y + h - 1
|
||||||
|
}
|
||||||
|
panic("not reach")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rectsAsLines) Color(i int) color.Color {
|
||||||
|
return r.Rects.Color(i / 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Rects represents the set of rectangles.
|
||||||
|
type Rects interface {
|
||||||
|
Len() int
|
||||||
|
Rect(i int) (x, y, width, height int)
|
||||||
|
Color(i int) color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
type rect struct {
|
||||||
|
x, y int
|
||||||
|
width, height int
|
||||||
|
color color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rect) Len() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rect) Rect(i int) (x, y, width, height int) {
|
||||||
|
return r.x, r.y, r.width, r.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rect) Color(i int) color.Color {
|
||||||
|
return r.color
|
||||||
|
}
|
124
util_test.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// Copyright 2014 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ebiten_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"github.com/gopherjs/gopherjs/js"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: Please update if any of testdata/* is updated.
|
||||||
|
var data = map[string]string{
|
||||||
|
"testdata/ebiten.png": `
|
||||||
|
iVBORw0KGgoAAAANSUhEUgAAADkAAAAaCAYAAAANIPQdAAAEJGlDQ1BJQ0Mg
|
||||||
|
UHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6Xt
|
||||||
|
Shal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqq
|
||||||
|
SUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8k
|
||||||
|
lZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zR
|
||||||
|
iSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1O
|
||||||
|
IT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/
|
||||||
|
Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4
|
||||||
|
H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKz
|
||||||
|
pBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAH
|
||||||
|
gD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66
|
||||||
|
dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJi
|
||||||
|
tqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/j
|
||||||
|
x4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUK
|
||||||
|
cm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb
|
||||||
|
7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+x
|
||||||
|
LfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/I
|
||||||
|
PszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRd
|
||||||
|
lb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8e
|
||||||
|
k6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZS
|
||||||
|
NsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo6
|
||||||
|
4G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3V
|
||||||
|
R4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN9
|
||||||
|
7RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydE
|
||||||
|
Ox83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqc
|
||||||
|
GAAACLlJREFUWAndWEtsXFcZ/s99z73zHr8mTqZO4tiJQ12FoFYFYaWtQh8q
|
||||||
|
RV0UibKgihBlgajKkg1iwQahLCohFkjhKaJWiCqLABUrUgQhiUrSPJzEjhMn
|
||||||
|
jjOxx/E878x9nHP4jiFxbNqo5OFSrnx1H3PmnP/7v+///jNmUkr6fz+0NQCo
|
||||||
|
Y43kGqxjYY0ETrZ6LWP1i/v87Pm1a19t3pj8cW79yOdMs/A+5u/cpzVMzJNp
|
||||||
|
Xx0f5JXZBC1MvCyEHEh/fs+rZNtTt6/xoEGm6/OnvhlXzxiLovPrns1jzxHZ
|
||||||
|
k7cHcHf3jULj/Pirojy7qf7H/S+Kv72b90YTpD36pSPEYqjGXjHtgwZ5zdKN
|
||||||
|
ViQ5hY3pYnX+0tey3cN7EcHiiihWPiTjTvXTzfrcC46Tf89Jd/1m5cdE8389
|
||||||
|
+HP/+F+e55OXSMQRsXSCAu5RcuSFKbK8K6vHP2iQxMkiJlok/CBZv/zua4Zu
|
||||||
|
n0rmB95cHci/n1OL5dNvdhbPjvKg3u3bxbpW9r6d7xn8oZUt/o4oHJ07uHef
|
||||||
|
f+bwTmEVSEQNap6dpszGNBlP73k/s+PR72Ce/0gguwd31Xx/8VniXHfN5IlQ
|
||||||
|
4xnL8s7fDN5vVHbLoDbm12eeDxYnt0r/BJFkpDn9RM5DZKdKR7s2Pj6G8cre
|
||||||
|
l8xicebosXb58HYZVYmMFBGvk6A+cjOltwvbnnl57siBdzpTh8Yi8igaP0rx
|
||||||
|
xDi524ZIH94hba1wgVemB7NPfWOPt+WR/Zgzwslx0t2AhObbmXaz+Xjj2tGf
|
||||||
|
8k4tS5pJWjhLDNdYaHjswvQ1hB+QECHJOCBqnSSZGCRm9pJU79C6mO4EicyG
|
||||||
|
smjPd8mw5gkB6QVTFLMukrqJnGShgGskI8zfhF/VGsTnLlBgdVNcniatXSdD
|
||||||
|
6mQGHdIijRhvkdadIntoJ+W+8qNnjHT+HQXyv5Jre3H2WyK4NhD6V4biYGGD
|
||||||
|
rJ/LajICFegSmquIIqH1wT+vkB5NIYMBaVqCeMRJMAee4JPgN0gDYI1ifM+2
|
||||||
|
w6b3kO5uxD2DKARpHMlnbczlEJchEoW5dUHCQAIMjOofJr0dEyuNkMkYsdkZ
|
||||||
|
iv+B+awkGYU02ZsGDiWGPnMKAI8ogOq4I8h2WPtya256t+VljouOv7k1f+x1
|
||||||
|
XTSR7XMINkSgbaBSAeVJKCkudQcBjaBdRQhAMWn3IsNgwyyBQZ1E4wKJTo00
|
||||||
|
2SaNoQtEBnE9hzkQSmsazDeRsCYJN0+sBSMOwHqIhBguUTcUYuH99ASSoZPk
|
||||||
|
UGTGJfPJMbL7t19Ob9y+19w++icrWRoHtlu7nDuCjBq1Jxrz57/Orrdquqh5
|
||||||
|
LJ5DlkMYCWpbAIxehBRnSEKmxAFITSsuk9DXK0ohIQSnWHGGITmfeBgAKMM4
|
||||||
|
CwynEXAv6fw6mPfxRY1kC/OCHZI1jEerY0gKEiZ59V8JFMYfUgO7vsdyQ6XK
|
||||||
|
yQO/lYZNiZEnKbX1uTErqc/ZqXWqPS3VIa63jjvW5PTJg5IhGF49RHZ0kkRi
|
||||||
|
FBjLCBDyYTYYvUrS2gIpZYj7FyGnHOIqEBeokdZZ0oIrAIQ1bYA2c3ifgUFW
|
||||||
|
iLfmYT5gGDLXNSgCBkP+JYoNlDfeE4+JazlcoRTNQj4NqAFStdZRZnBLqnvk
|
||||||
|
JdD90Y/VIG9ui7zL7+2bFbxj6PHCUr2wsIo6uwRQRSxsgD0sbmyA7OCAXCO+
|
||||||
|
8Hf0LJVEVGhmmLTkIJhBy2qcAhM+yfRO4g2AboOtCLVmokZxWgnI0E6RqE6R
|
||||||
|
CGBWiW5izkaFk2SnSVpcIco/diPZu/X1vh0v/vKjQ1seuUKucbP6WBhUdvt+
|
||||||
|
ZbMULR0tAHLhSyYiAUQoDGEDlewg0CvwhDmKrSHUWIXiAB/6V8FORHrvEzAN
|
||||||
|
mIMNlwwhaasfLgmAEWSoas/Qibn9pLsAp5jDWOlAsjG+o/ofi8GxiVN29MLD
|
||||||
|
v3L7th7OP3J3ABXUFSAD0VzfKB/9bhTAFYN5SA3LQJKiPUHC3Aw5Qk4Wit/0
|
||||||
|
UDpqb2+QqJ+lWDEJO5fNG2S4HhgBW2YGbYITh+EQmrasnYNEwZKVAlA4MaiK
|
||||||
|
IjhobQoG5IO9PKTqk+70kGX1nUxYubfMZM9s9lPP7lOB3stxO8hs3JrcFaOW
|
||||||
|
lG3rEgYSo1dF11ETDkQIiQZNPINJPYGg1kGySYqbp2E0PWAZTMLGJRIgY5QM
|
||||||
|
TEh0kKgQdQVnlM561DISY1oA76IuyyTnDytNwoEzZIBBaeYBsnjY7R5+pYuX
|
||||||
|
LtLICHR978cyyKDR2yoffwW9C644CZNAwJifwTKlUYCh2Ev1GPszRNXTJD0w
|
||||||
|
J1CXsH8R4t5UO5QW6qsLxoqAlRL8MhIE+SIJzOnDeDitRBKaFbK9IunZTeiN
|
||||||
|
HQqjFlloGZs2fzFBfo+g4fsD7mZ6lkFCQNBSS/LFtID9C7Uj0dDrCp9FXaE3
|
||||||
|
ttHfFJtLzR9XxTKMXQSQIrLB7CxkyCBdk0z0R1COP7QMVW9xSLqVFkxPzdle
|
||||||
|
b8Uo7X6p+PAXIJm1OZZB2unrbn74Z/WZqdcYAtNhLBRL1CO2o1YJoCG/8AaC
|
||||||
|
5mAW7UA14uY4HBI/a5TUEK/apaiE8E4VCYLtuyUymHFet7PH3NymtpXqebtr
|
||||||
|
29MH1wba8irLIGH2ppM+JtCsNWcABgPjCOfQnxiCh1QNsBeiPtUWDb8sJMGc
|
||||||
|
3AyxRD+knMLWDQmB7HS7SGYi2zSdvl9Yifyk6fWc6Bp+6s9YUiwvu7Z3t4PE
|
||||||
|
DgZsGNiiaWAHPUwyBA9blzhjWL+IFigGSK62cFIQSw2ASSQDcjQSfRczvYM/
|
||||||
|
MBLeBc3Jh5ajnckO7MLPiY//WAHSzQxebKUmznda80OCsO1SHZljVxN1YBoe
|
||||||
|
dRoVAMVeEi6ooQZZiA21U+hkS6Pfp9zAG8XiTvxU+PgY+7B0rt7x3Bp36cgb
|
||||||
|
Z2VcGVYtIw7CBd1wq6Y7EJnuup8YRu6t7pFd5VuD/8dvPhSkirsycWC/VP9e
|
||||||
|
MJP7uwad3xPtArWfvOOOID95cD444rX4v+sHr7yGb/8JabkxZC5Kp1sAAAAA
|
||||||
|
SUVORK5CYII=`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(path string) (io.Reader, error) {
|
||||||
|
if js.Global == nil {
|
||||||
|
b, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bytes.NewBuffer(b), nil
|
||||||
|
}
|
||||||
|
// According to https://github.com/gopherjs/gopherjs/blob/master/doc/syscalls.md,
|
||||||
|
// syscall can be available on the unstable version of npm.
|
||||||
|
// Let's not use os package here.
|
||||||
|
str, ok := data[path]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
return base64.NewDecoder(base64.StdEncoding, strings.NewReader(str)), nil
|
||||||
|
}
|